2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
9 * Software Foundation, Inc.
11 * Enhancements Copyright 2005 Alessandro Scotti
13 * The following terms apply to Digital Equipment Corporation's copyright
15 * ------------------------------------------------------------------------
18 * Permission to use, copy, modify, and distribute this software and its
19 * documentation for any purpose and without fee is hereby granted,
20 * provided that the above copyright notice appear in all copies and that
21 * both that copyright notice and this permission notice appear in
22 * supporting documentation, and that the name of Digital not be
23 * used in advertising or publicity pertaining to distribution of the
24 * software without specific, written prior permission.
26 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
33 * ------------------------------------------------------------------------
35 * The following terms apply to the enhanced version of XBoard
36 * distributed by the Free Software Foundation:
37 * ------------------------------------------------------------------------
39 * GNU XBoard is free software: you can redistribute it and/or modify
40 * it under the terms of the GNU General Public License as published by
41 * the Free Software Foundation, either version 3 of the License, or (at
42 * your option) any later version.
44 * GNU XBoard is distributed in the hope that it will be useful, but
45 * WITHOUT ANY WARRANTY; without even the implied warranty of
46 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47 * General Public License for more details.
49 * You should have received a copy of the GNU General Public License
50 * along with this program. If not, see http://www.gnu.org/licenses/. *
52 *------------------------------------------------------------------------
53 ** See the file ChangeLog for a revision history. */
55 /* [AS] Also useful here for debugging */
59 int flock(int f, int code);
64 # define EGBB_NAME "egbbdll64.dll"
66 # define EGBB_NAME "egbbdll.dll"
71 # include <sys/file.h>
76 # define EGBB_NAME "egbbso64.so"
78 # define EGBB_NAME "egbbso.so"
80 // kludge to allow Windows code in back-end by converting it to corresponding Linux code
82 # define HMODULE void *
83 # define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 # define GetProcAddress dlsym
94 #include <sys/types.h>
103 #else /* not STDC_HEADERS */
106 # else /* not HAVE_STRING_H */
107 # include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
124 # include <sys/time.h>
130 #if defined(_amigados) && !defined(__GNUC__)
135 extern int gettimeofday(struct timeval *, struct timezone *);
143 #include "frontend.h"
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173 char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175 char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188 /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199 char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201 int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
250 extern void ConsoleCreate();
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
264 int deadRanks, handSize, handOffsets;
266 extern int tinyLayout, smallLayout;
267 ChessProgramStats programStats;
268 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
270 static int exiting = 0; /* [HGM] moved to top */
271 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
272 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
273 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
274 int partnerHighlight[2];
275 Boolean partnerBoardValid = 0;
276 char partnerStatus[MSG_SIZ];
278 Boolean originalFlip;
279 Boolean twoBoards = 0;
280 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
281 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
282 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
283 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
284 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
285 int opponentKibitzes;
286 int lastSavedGame; /* [HGM] save: ID of game */
287 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
288 extern int chatCount;
290 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
291 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
292 char lastMsg[MSG_SIZ];
293 char lastTalker[MSG_SIZ];
294 ChessSquare pieceSweep = EmptySquare;
295 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
296 int promoDefaultAltered;
297 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
298 static int initPing = -1;
299 int border; /* [HGM] width of board rim, needed to size seek graph */
300 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
301 int solvingTime, totalTime;
303 /* States for ics_getting_history */
305 #define H_REQUESTED 1
306 #define H_GOT_REQ_HEADER 2
307 #define H_GOT_UNREQ_HEADER 3
308 #define H_GETTING_MOVES 4
309 #define H_GOT_UNWANTED_HEADER 5
311 /* whosays values for GameEnds */
320 /* Maximum number of games in a cmail message */
321 #define CMAIL_MAX_GAMES 20
323 /* Different types of move when calling RegisterMove */
325 #define CMAIL_RESIGN 1
327 #define CMAIL_ACCEPT 3
329 /* Different types of result to remember for each game */
330 #define CMAIL_NOT_RESULT 0
331 #define CMAIL_OLD_RESULT 1
332 #define CMAIL_NEW_RESULT 2
334 /* Telnet protocol constants */
345 safeStrCpy (char *dst, const char *src, size_t count)
348 assert( dst != NULL );
349 assert( src != NULL );
352 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
353 if( i == count && dst[count-1] != NULLCHAR)
355 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
356 if(appData.debugMode)
357 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
363 /* Some compiler can't cast u64 to double
364 * This function do the job for us:
366 * We use the highest bit for cast, this only
367 * works if the highest bit is not
368 * in use (This should not happen)
370 * We used this for all compiler
373 u64ToDouble (u64 value)
376 u64 tmp = value & u64Const(0x7fffffffffffffff);
377 r = (double)(s64)tmp;
378 if (value & u64Const(0x8000000000000000))
379 r += 9.2233720368547758080e18; /* 2^63 */
383 /* Fake up flags for now, as we aren't keeping track of castling
384 availability yet. [HGM] Change of logic: the flag now only
385 indicates the type of castlings allowed by the rule of the game.
386 The actual rights themselves are maintained in the array
387 castlingRights, as part of the game history, and are not probed
393 int flags = F_ALL_CASTLE_OK;
394 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
395 switch (gameInfo.variant) {
397 flags &= ~F_ALL_CASTLE_OK;
398 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
399 flags |= F_IGNORE_CHECK;
401 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
404 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
406 case VariantKriegspiel:
407 flags |= F_KRIEGSPIEL_CAPTURE;
409 case VariantCapaRandom:
410 case VariantFischeRandom:
411 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
412 case VariantNoCastle:
413 case VariantShatranj:
418 flags &= ~F_ALL_CASTLE_OK;
421 case VariantChuChess:
423 flags |= F_NULL_MOVE;
428 if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
432 FILE *gameFileFP, *debugFP, *serverFP;
433 char *currentDebugFile; // [HGM] debug split: to remember name
436 [AS] Note: sometimes, the sscanf() function is used to parse the input
437 into a fixed-size buffer. Because of this, we must be prepared to
438 receive strings as long as the size of the input buffer, which is currently
439 set to 4K for Windows and 8K for the rest.
440 So, we must either allocate sufficiently large buffers here, or
441 reduce the size of the input buffer in the input reading part.
444 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
445 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
446 char thinkOutput1[MSG_SIZ*10];
447 char promoRestrict[MSG_SIZ];
449 ChessProgramState first, second, pairing;
451 /* premove variables */
454 int premoveFromX = 0;
455 int premoveFromY = 0;
456 int premovePromoChar = 0;
458 Boolean alarmSounded;
459 /* end premove variables */
461 char *ics_prefix = "$";
462 enum ICS_TYPE ics_type = ICS_GENERIC;
464 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
465 int pauseExamForwardMostMove = 0;
466 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
467 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
468 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
469 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
470 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
471 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
472 int whiteFlag = FALSE, blackFlag = FALSE;
473 int userOfferedDraw = FALSE;
474 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
475 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
476 int cmailMoveType[CMAIL_MAX_GAMES];
477 long ics_clock_paused = 0;
478 ProcRef icsPR = NoProc, cmailPR = NoProc;
479 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
480 GameMode gameMode = BeginningOfGame;
481 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
482 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
483 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
484 int hiddenThinkOutputState = 0; /* [AS] */
485 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
486 int adjudicateLossPlies = 6;
487 char white_holding[64], black_holding[64];
488 TimeMark lastNodeCountTime;
489 long lastNodeCount=0;
490 int shiftKey, controlKey; // [HGM] set by mouse handler
492 int have_sent_ICS_logon = 0;
494 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
495 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
496 Boolean adjustedClock;
497 long timeControl_2; /* [AS] Allow separate time controls */
498 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
499 long timeRemaining[2][MAX_MOVES];
500 int matchGame = 0, nextGame = 0, roundNr = 0;
501 Boolean waitingForGame = FALSE, startingEngine = FALSE;
502 TimeMark programStartTime, pauseStart;
503 char ics_handle[MSG_SIZ];
504 int have_set_title = 0;
506 /* animateTraining preserves the state of appData.animate
507 * when Training mode is activated. This allows the
508 * response to be animated when appData.animate == TRUE and
509 * appData.animateDragging == TRUE.
511 Boolean animateTraining;
517 Board boards[MAX_MOVES];
518 /* [HGM] Following 7 needed for accurate legality tests: */
519 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
520 unsigned char initialRights[BOARD_FILES];
521 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
522 int initialRulePlies, FENrulePlies;
523 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
525 Boolean shuffleOpenings;
526 int mute; // mute all sounds
528 // [HGM] vari: next 12 to save and restore variations
529 #define MAX_VARIATIONS 10
530 int framePtr = MAX_MOVES-1; // points to free stack entry
532 int savedFirst[MAX_VARIATIONS];
533 int savedLast[MAX_VARIATIONS];
534 int savedFramePtr[MAX_VARIATIONS];
535 char *savedDetails[MAX_VARIATIONS];
536 ChessMove savedResult[MAX_VARIATIONS];
538 void PushTail P((int firstMove, int lastMove));
539 Boolean PopTail P((Boolean annotate));
540 void PushInner P((int firstMove, int lastMove));
541 void PopInner P((Boolean annotate));
542 void CleanupTail P((void));
544 ChessSquare FIDEArray[2][BOARD_FILES] = {
545 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
548 BlackKing, BlackBishop, BlackKnight, BlackRook }
551 ChessSquare twoKingsArray[2][BOARD_FILES] = {
552 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
553 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
554 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
555 BlackKing, BlackKing, BlackKnight, BlackRook }
558 ChessSquare KnightmateArray[2][BOARD_FILES] = {
559 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
560 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
561 { BlackRook, BlackMan, BlackBishop, BlackQueen,
562 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
565 ChessSquare SpartanArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
567 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
568 { BlackAlfil, BlackDragon, BlackKing, BlackTower,
569 BlackTower, BlackKing, BlackAngel, BlackAlfil }
572 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
573 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
574 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
575 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
576 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
579 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
580 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
581 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
582 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
583 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
586 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
587 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
588 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
589 { BlackRook, BlackKnight, BlackMan, BlackFerz,
590 BlackKing, BlackMan, BlackKnight, BlackRook }
593 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
594 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
595 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
596 { BlackRook, BlackKnight, BlackMan, BlackFerz,
597 BlackKing, BlackMan, BlackKnight, BlackRook }
600 ChessSquare lionArray[2][BOARD_FILES] = {
601 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
602 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
603 { BlackRook, BlackLion, BlackBishop, BlackQueen,
604 BlackKing, BlackBishop, BlackKnight, BlackRook }
608 #if (BOARD_FILES>=10)
609 ChessSquare ShogiArray[2][BOARD_FILES] = {
610 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
611 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
612 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
613 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
616 ChessSquare XiangqiArray[2][BOARD_FILES] = {
617 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
618 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
619 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
620 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
623 ChessSquare CapablancaArray[2][BOARD_FILES] = {
624 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
625 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
626 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
627 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
630 ChessSquare GreatArray[2][BOARD_FILES] = {
631 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
632 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
633 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
634 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
637 ChessSquare JanusArray[2][BOARD_FILES] = {
638 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
639 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
640 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
641 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
644 ChessSquare GrandArray[2][BOARD_FILES] = {
645 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
646 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
647 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
648 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
651 ChessSquare ChuChessArray[2][BOARD_FILES] = {
652 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
653 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
654 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
655 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
659 ChessSquare GothicArray[2][BOARD_FILES] = {
660 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
661 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
662 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
663 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
666 #define GothicArray CapablancaArray
670 ChessSquare FalconArray[2][BOARD_FILES] = {
671 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
672 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
673 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
674 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
677 #define FalconArray CapablancaArray
680 #else // !(BOARD_FILES>=10)
681 #define XiangqiPosition FIDEArray
682 #define CapablancaArray FIDEArray
683 #define GothicArray FIDEArray
684 #define GreatArray FIDEArray
685 #endif // !(BOARD_FILES>=10)
687 #if (BOARD_FILES>=12)
688 ChessSquare CourierArray[2][BOARD_FILES] = {
689 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
690 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
691 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
692 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
694 ChessSquare ChuArray[6][BOARD_FILES] = {
695 { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
696 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
697 { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
698 BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
699 { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
700 WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
701 { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
702 BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
703 { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
704 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
705 { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
706 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
708 #else // !(BOARD_FILES>=12)
709 #define CourierArray CapablancaArray
710 #define ChuArray CapablancaArray
711 #endif // !(BOARD_FILES>=12)
714 Board initialPosition;
717 /* Convert str to a rating. Checks for special cases of "----",
719 "++++", etc. Also strips ()'s */
721 string_to_rating (char *str)
723 while(*str && !isdigit(*str)) ++str;
725 return 0; /* One of the special "no rating" cases */
733 /* Init programStats */
734 programStats.movelist[0] = 0;
735 programStats.depth = 0;
736 programStats.nr_moves = 0;
737 programStats.moves_left = 0;
738 programStats.nodes = 0;
739 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
740 programStats.score = 0;
741 programStats.got_only_move = 0;
742 programStats.got_fail = 0;
743 programStats.line_is_book = 0;
748 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
749 if (appData.firstPlaysBlack) {
750 first.twoMachinesColor = "black\n";
751 second.twoMachinesColor = "white\n";
753 first.twoMachinesColor = "white\n";
754 second.twoMachinesColor = "black\n";
757 first.other = &second;
758 second.other = &first;
761 if(appData.timeOddsMode) {
762 norm = appData.timeOdds[0];
763 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
765 first.timeOdds = appData.timeOdds[0]/norm;
766 second.timeOdds = appData.timeOdds[1]/norm;
769 if(programVersion) free(programVersion);
770 if (appData.noChessProgram) {
771 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
772 sprintf(programVersion, "%s", PACKAGE_STRING);
774 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
775 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
776 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
781 UnloadEngine (ChessProgramState *cps)
783 /* Kill off first chess program */
784 if (cps->isr != NULL)
785 RemoveInputSource(cps->isr);
788 if (cps->pr != NoProc) {
790 DoSleep( appData.delayBeforeQuit );
791 SendToProgram("quit\n", cps);
792 DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
795 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
799 ClearOptions (ChessProgramState *cps)
802 cps->nrOptions = cps->comboCnt = 0;
803 for(i=0; i<MAX_OPTIONS; i++) {
804 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
805 cps->option[i].textValue = 0;
809 char *engineNames[] = {
810 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
811 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
813 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
814 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
819 InitEngine (ChessProgramState *cps, int n)
820 { // [HGM] all engine initialiation put in a function that does one engine
824 cps->which = engineNames[n];
825 cps->maybeThinking = FALSE;
829 cps->sendDrawOffers = 1;
831 cps->program = appData.chessProgram[n];
832 cps->host = appData.host[n];
833 cps->dir = appData.directory[n];
834 cps->initString = appData.engInitString[n];
835 cps->computerString = appData.computerString[n];
836 cps->useSigint = TRUE;
837 cps->useSigterm = TRUE;
838 cps->reuse = appData.reuse[n];
839 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
840 cps->useSetboard = FALSE;
842 cps->usePing = FALSE;
845 cps->usePlayother = FALSE;
846 cps->useColors = TRUE;
847 cps->useUsermove = FALSE;
848 cps->sendICS = FALSE;
849 cps->sendName = appData.icsActive;
850 cps->sdKludge = FALSE;
851 cps->stKludge = FALSE;
852 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
853 TidyProgramName(cps->program, cps->host, cps->tidy);
855 ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
856 cps->analysisSupport = 2; /* detect */
857 cps->analyzing = FALSE;
858 cps->initDone = FALSE;
860 cps->pseudo = appData.pseudo[n];
862 /* New features added by Tord: */
863 cps->useFEN960 = FALSE;
864 cps->useOOCastle = TRUE;
865 /* End of new features added by Tord. */
866 cps->fenOverride = appData.fenOverride[n];
868 /* [HGM] time odds: set factor for each machine */
869 cps->timeOdds = appData.timeOdds[n];
871 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
872 cps->accumulateTC = appData.accumulateTC[n];
873 cps->maxNrOfSessions = 1;
878 cps->drawDepth = appData.drawDepth[n];
879 cps->supportsNPS = UNKNOWN;
880 cps->memSize = FALSE;
881 cps->maxCores = FALSE;
882 ASSIGN(cps->egtFormats, "");
885 cps->optionSettings = appData.engOptions[n];
887 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
888 cps->isUCI = appData.isUCI[n]; /* [AS] */
889 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
892 if (appData.protocolVersion[n] > PROTOVER
893 || appData.protocolVersion[n] < 1)
898 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
899 appData.protocolVersion[n]);
900 if( (len >= MSG_SIZ) && appData.debugMode )
901 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
903 DisplayFatalError(buf, 0, 2);
907 cps->protocolVersion = appData.protocolVersion[n];
910 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
911 ParseFeatures(appData.featureDefaults, cps);
914 ChessProgramState *savCps;
916 GameMode oldMode, tryNr;
918 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
919 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
920 char *insert, *wbOptions, *currentEngine[2]; // point in ChessProgramNames were we should insert new engine
921 static char newEngineCommand[MSG_SIZ];
924 FloatToFront(char **list, char *engineLine)
926 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
928 if(appData.recentEngines <= 0) return;
929 TidyProgramName(engineLine, "localhost", tidy+1);
930 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
931 strncpy(buf+1, *list, MSG_SIZ-50);
932 if(p = strstr(buf, tidy)) { // tidy name appears in list
933 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
934 while(*p++ = *++q); // squeeze out
936 strcat(tidy, buf+1); // put list behind tidy name
937 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
938 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
939 ASSIGN(*list, tidy+1);
946 if(*engineListFile && (f = fopen(engineListFile, "w"))) {
947 fprintf(f, "-firstChessProgramNames {%s}\n", firstChessProgramNames);
953 AddToEngineList (int i)
957 char quote, buf[MSG_SIZ];
958 char *q = firstChessProgramNames, *p = newEngineCommand;
959 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
960 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
961 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s",
962 quote, p, quote, appData.directory[i],
963 useNick ? " -fn \"" : "",
964 useNick ? nickName : "",
966 v1 ? " -firstProtocolVersion 1" : "",
967 hasBook ? "" : " -fNoOwnBookUCI",
968 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
969 storeVariant ? " -variant " : "",
970 storeVariant ? VariantName(gameInfo.variant) : "");
971 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " %s", wbOptions);
972 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 2);
973 if(insert != q) insert[-1] = NULLCHAR;
974 snprintf(firstChessProgramNames, len, "%s\n%s\n%s", q, buf, insert);
977 FloatToFront(&appData.recentEngineList, buf);
978 ASSIGN(currentEngine[i], buf);
986 if(WaitForEngine(savCps, LoadEngine)) return;
987 if(tryNr == 1 && !isUCI) { SendToProgram("uci\n", savCps); tryNr = 2; ScheduleDelayedEvent(LoadEngine, FEATURE_TIMEOUT); return; }
988 if(tryNr) v1 |= (tryNr == 2), tryNr = 0, AddToEngineList(0); // deferred to after protocol determination
989 CommonEngineInit(); // recalculate time odds
990 if(gameInfo.variant != StringToVariant(appData.variant)) {
991 // we changed variant when loading the engine; this forces us to reset
992 Reset(TRUE, savCps != &first);
993 oldMode = BeginningOfGame; // to prevent restoring old mode
995 InitChessProgram(savCps, FALSE);
996 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
997 DisplayMessage("", "");
998 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
999 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
1002 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
1006 ReplaceEngine (ChessProgramState *cps, int n)
1008 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
1010 if(oldMode != BeginningOfGame) EditGameEvent();
1013 appData.noChessProgram = FALSE;
1014 appData.clockMode = TRUE;
1017 if(n && !tryNr) return; // only startup first engine immediately; second can wait (unless autodetect)
1018 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
1022 static char resetOptions[] =
1023 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
1024 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
1025 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
1026 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
1029 Load (ChessProgramState *cps, int i)
1031 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
1032 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
1033 ASSIGN(currentEngine[i], engineLine);
1034 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
1035 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
1036 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
1037 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
1038 appData.firstProtocolVersion = PROTOVER;
1039 ParseArgsFromString(buf);
1041 ReplaceEngine(cps, i);
1042 FloatToFront(&appData.recentEngineList, engineLine);
1043 if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1047 while(q = strchr(p, SLASH)) p = q+1;
1048 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1049 if(engineDir[0] != NULLCHAR) {
1050 ASSIGN(appData.directory[i], engineDir); p = engineName;
1051 } else if(p != engineName) { // derive directory from engine path, when not given
1053 ASSIGN(appData.directory[i], engineName);
1055 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1056 } else { ASSIGN(appData.directory[i], "."); }
1057 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1059 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1060 snprintf(command, MSG_SIZ, "%s %s", p, params);
1063 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1064 ASSIGN(appData.chessProgram[i], p);
1065 tryNr = 3; // requests adding to list without auto-detect
1066 if(isUCI == 3) tryNr = 1, isUCI = 0; // auto-detect
1067 appData.isUCI[i] = isUCI;
1068 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1069 appData.hasOwnBookUCI[i] = hasBook;
1070 if(!nickName[0]) useNick = FALSE;
1071 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1072 safeStrCpy(newEngineCommand, p, MSG_SIZ);
1073 ReplaceEngine(cps, i);
1079 int matched, min, sec;
1081 * Parse timeControl resource
1083 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1084 appData.movesPerSession)) {
1086 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1087 DisplayFatalError(buf, 0, 2);
1091 * Parse searchTime resource
1093 if (*appData.searchTime != NULLCHAR) {
1094 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1096 searchTime = min * 60;
1097 } else if (matched == 2) {
1098 searchTime = min * 60 + sec;
1101 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1102 DisplayFatalError(buf, 0, 2);
1111 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1112 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1114 GetTimeMark(&programStartTime);
1115 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1116 appData.seedBase = random() + (random()<<15);
1117 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1119 ClearProgramStats();
1120 programStats.ok_to_send = 1;
1121 programStats.seen_stat = 0;
1124 * Initialize game list
1130 * Internet chess server status
1132 if (appData.icsActive) {
1133 appData.matchMode = FALSE;
1134 appData.matchGames = 0;
1136 appData.noChessProgram = !appData.zippyPlay;
1138 appData.zippyPlay = FALSE;
1139 appData.zippyTalk = FALSE;
1140 appData.noChessProgram = TRUE;
1142 if (*appData.icsHelper != NULLCHAR) {
1143 appData.useTelnet = TRUE;
1144 appData.telnetProgram = appData.icsHelper;
1147 appData.zippyTalk = appData.zippyPlay = FALSE;
1150 /* [AS] Initialize pv info list [HGM] and game state */
1154 for( i=0; i<=framePtr; i++ ) {
1155 pvInfoList[i].depth = -1;
1156 boards[i][EP_STATUS] = EP_NONE;
1157 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1163 /* [AS] Adjudication threshold */
1164 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1166 InitEngine(&first, 0);
1167 InitEngine(&second, 1);
1170 pairing.which = "pairing"; // pairing engine
1171 pairing.pr = NoProc;
1173 pairing.program = appData.pairingEngine;
1174 pairing.host = "localhost";
1177 if (appData.icsActive) {
1178 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1179 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1180 appData.clockMode = FALSE;
1181 first.sendTime = second.sendTime = 0;
1185 /* Override some settings from environment variables, for backward
1186 compatibility. Unfortunately it's not feasible to have the env
1187 vars just set defaults, at least in xboard. Ugh.
1189 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1194 if (!appData.icsActive) {
1198 /* Check for variants that are supported only in ICS mode,
1199 or not at all. Some that are accepted here nevertheless
1200 have bugs; see comments below.
1202 VariantClass variant = StringToVariant(appData.variant);
1204 case VariantBughouse: /* need four players and two boards */
1205 case VariantKriegspiel: /* need to hide pieces and move details */
1206 /* case VariantFischeRandom: (Fabien: moved below) */
1207 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1208 if( (len >= MSG_SIZ) && appData.debugMode )
1209 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1211 DisplayFatalError(buf, 0, 2);
1214 case VariantUnknown:
1215 case VariantLoadable:
1225 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1226 if( (len >= MSG_SIZ) && appData.debugMode )
1227 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1229 DisplayFatalError(buf, 0, 2);
1232 case VariantNormal: /* definitely works! */
1233 if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1234 safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1237 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1238 case VariantFairy: /* [HGM] TestLegality definitely off! */
1239 case VariantGothic: /* [HGM] should work */
1240 case VariantCapablanca: /* [HGM] should work */
1241 case VariantCourier: /* [HGM] initial forced moves not implemented */
1242 case VariantShogi: /* [HGM] could still mate with pawn drop */
1243 case VariantChu: /* [HGM] experimental */
1244 case VariantKnightmate: /* [HGM] should work */
1245 case VariantCylinder: /* [HGM] untested */
1246 case VariantFalcon: /* [HGM] untested */
1247 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1248 offboard interposition not understood */
1249 case VariantWildCastle: /* pieces not automatically shuffled */
1250 case VariantNoCastle: /* pieces not automatically shuffled */
1251 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1252 case VariantLosers: /* should work except for win condition,
1253 and doesn't know captures are mandatory */
1254 case VariantSuicide: /* should work except for win condition,
1255 and doesn't know captures are mandatory */
1256 case VariantGiveaway: /* should work except for win condition,
1257 and doesn't know captures are mandatory */
1258 case VariantTwoKings: /* should work */
1259 case VariantAtomic: /* should work except for win condition */
1260 case Variant3Check: /* should work except for win condition */
1261 case VariantShatranj: /* should work except for all win conditions */
1262 case VariantMakruk: /* should work except for draw countdown */
1263 case VariantASEAN : /* should work except for draw countdown */
1264 case VariantBerolina: /* might work if TestLegality is off */
1265 case VariantCapaRandom: /* should work */
1266 case VariantJanus: /* should work */
1267 case VariantSuper: /* experimental */
1268 case VariantGreat: /* experimental, requires legality testing to be off */
1269 case VariantSChess: /* S-Chess, should work */
1270 case VariantGrand: /* should work */
1271 case VariantSpartan: /* should work */
1272 case VariantLion: /* should work */
1273 case VariantChuChess: /* should work */
1281 NextIntegerFromString (char ** str, long * value)
1286 while( *s == ' ' || *s == '\t' ) {
1292 if( *s >= '0' && *s <= '9' ) {
1293 while( *s >= '0' && *s <= '9' ) {
1294 *value = *value * 10 + (*s - '0');
1307 NextTimeControlFromString (char ** str, long * value)
1310 int result = NextIntegerFromString( str, &temp );
1313 *value = temp * 60; /* Minutes */
1314 if( **str == ':' ) {
1316 result = NextIntegerFromString( str, &temp );
1317 *value += temp; /* Seconds */
1325 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1326 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1327 int result = -1, type = 0; long temp, temp2;
1329 if(**str != ':') return -1; // old params remain in force!
1331 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1332 if( NextIntegerFromString( str, &temp ) ) return -1;
1333 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1336 /* time only: incremental or sudden-death time control */
1337 if(**str == '+') { /* increment follows; read it */
1339 if(**str == '!') type = *(*str)++; // Bronstein TC
1340 if(result = NextIntegerFromString( str, &temp2)) return -1;
1341 *inc = temp2 * 1000;
1342 if(**str == '.') { // read fraction of increment
1343 char *start = ++(*str);
1344 if(result = NextIntegerFromString( str, &temp2)) return -1;
1346 while(start++ < *str) temp2 /= 10;
1350 *moves = 0; *tc = temp * 1000; *incType = type;
1354 (*str)++; /* classical time control */
1355 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1367 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1368 { /* [HGM] get time to add from the multi-session time-control string */
1369 int incType, moves=1; /* kludge to force reading of first session */
1370 long time, increment;
1373 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1375 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1376 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1377 if(movenr == -1) return time; /* last move before new session */
1378 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1379 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1380 if(!moves) return increment; /* current session is incremental */
1381 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1382 } while(movenr >= -1); /* try again for next session */
1384 return 0; // no new time quota on this move
1388 ParseTimeControl (char *tc, float ti, int mps)
1392 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1395 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1396 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1397 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1401 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1403 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1406 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1408 snprintf(buf, MSG_SIZ, ":%s", mytc);
1410 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1412 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1417 /* Parse second time control */
1420 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1428 timeControl_2 = tc2 * 1000;
1438 timeControl = tc1 * 1000;
1441 timeIncrement = ti * 1000; /* convert to ms */
1442 movesPerSession = 0;
1445 movesPerSession = mps;
1453 if (appData.debugMode) {
1454 # ifdef __GIT_VERSION
1455 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1457 fprintf(debugFP, "Version: %s\n", programVersion);
1460 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1462 set_cont_sequence(appData.wrapContSeq);
1463 if (appData.matchGames > 0) {
1464 appData.matchMode = TRUE;
1465 } else if (appData.matchMode) {
1466 appData.matchGames = 1;
1468 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1469 appData.matchGames = appData.sameColorGames;
1470 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1471 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1472 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1475 if (appData.noChessProgram || first.protocolVersion == 1) {
1478 /* kludge: allow timeout for initial "feature" commands */
1480 DisplayMessage("", _("Starting chess program"));
1481 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1486 CalculateIndex (int index, int gameNr)
1487 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1489 if(index > 0) return index; // fixed nmber
1490 if(index == 0) return 1;
1491 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1492 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1497 LoadGameOrPosition (int gameNr)
1498 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1499 if (*appData.loadGameFile != NULLCHAR) {
1500 if (!LoadGameFromFile(appData.loadGameFile,
1501 CalculateIndex(appData.loadGameIndex, gameNr),
1502 appData.loadGameFile, FALSE)) {
1503 DisplayFatalError(_("Bad game file"), 0, 1);
1506 } else if (*appData.loadPositionFile != NULLCHAR) {
1507 if (!LoadPositionFromFile(appData.loadPositionFile,
1508 CalculateIndex(appData.loadPositionIndex, gameNr),
1509 appData.loadPositionFile)) {
1510 DisplayFatalError(_("Bad position file"), 0, 1);
1518 ReserveGame (int gameNr, char resChar)
1520 FILE *tf = fopen(appData.tourneyFile, "r+");
1521 char *p, *q, c, buf[MSG_SIZ];
1522 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1523 safeStrCpy(buf, lastMsg, MSG_SIZ);
1524 DisplayMessage(_("Pick new game"), "");
1525 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1526 ParseArgsFromFile(tf);
1527 p = q = appData.results;
1528 if(appData.debugMode) {
1529 char *r = appData.participants;
1530 fprintf(debugFP, "results = '%s'\n", p);
1531 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1532 fprintf(debugFP, "\n");
1534 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1536 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1537 safeStrCpy(q, p, strlen(p) + 2);
1538 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1539 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1540 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1541 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1544 fseek(tf, -(strlen(p)+4), SEEK_END);
1546 if(c != '"') // depending on DOS or Unix line endings we can be one off
1547 fseek(tf, -(strlen(p)+2), SEEK_END);
1548 else fseek(tf, -(strlen(p)+3), SEEK_END);
1549 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1550 DisplayMessage(buf, "");
1551 free(p); appData.results = q;
1552 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1553 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1554 int round = appData.defaultMatchGames * appData.tourneyType;
1555 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1556 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1557 UnloadEngine(&first); // next game belongs to other pairing;
1558 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1560 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1564 MatchEvent (int mode)
1565 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1567 if(matchMode) { // already in match mode: switch it off
1569 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1572 // if(gameMode != BeginningOfGame) {
1573 // DisplayError(_("You can only start a match from the initial position."), 0);
1577 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1578 /* Set up machine vs. machine match */
1580 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1581 if(appData.tourneyFile[0]) {
1583 if(nextGame > appData.matchGames) {
1585 if(strchr(appData.results, '*') == NULL) {
1587 appData.tourneyCycles++;
1588 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1590 NextTourneyGame(-1, &dummy);
1592 if(nextGame <= appData.matchGames) {
1593 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1595 ScheduleDelayedEvent(NextMatchGame, 10000);
1600 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1601 DisplayError(buf, 0);
1602 appData.tourneyFile[0] = 0;
1606 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1607 DisplayFatalError(_("Can't have a match with no chess programs"),
1612 matchGame = roundNr = 1;
1613 first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1618 InitBackEnd3 P((void))
1620 GameMode initialMode;
1624 ParseFeatures(appData.features[0], &first);
1625 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1626 !strcmp(appData.variant, "normal") && // no explicit variant request
1627 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1628 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1629 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1630 char c, *q = first.variants, *p = strchr(q, ',');
1631 if(p) *p = NULLCHAR;
1632 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1634 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1635 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1636 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1637 Reset(TRUE, FALSE); // and re-initialize
1642 InitChessProgram(&first, startedFromSetupPosition);
1644 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1645 free(programVersion);
1646 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1647 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1648 FloatToFront(&appData.recentEngineList, currentEngine[0] ? currentEngine[0] : appData.firstChessProgram);
1651 if (appData.icsActive) {
1653 /* [DM] Make a console window if needed [HGM] merged ifs */
1659 if (*appData.icsCommPort != NULLCHAR)
1660 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1661 appData.icsCommPort);
1663 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1664 appData.icsHost, appData.icsPort);
1666 if( (len >= MSG_SIZ) && appData.debugMode )
1667 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1669 DisplayFatalError(buf, err, 1);
1674 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1676 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1677 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1678 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1679 } else if (appData.noChessProgram) {
1685 if (*appData.cmailGameName != NULLCHAR) {
1687 OpenLoopback(&cmailPR);
1689 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1693 DisplayMessage("", "");
1694 if (StrCaseCmp(appData.initialMode, "") == 0) {
1695 initialMode = BeginningOfGame;
1696 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1697 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1698 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1699 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1702 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1703 initialMode = TwoMachinesPlay;
1704 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1705 initialMode = AnalyzeFile;
1706 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1707 initialMode = AnalyzeMode;
1708 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1709 initialMode = MachinePlaysWhite;
1710 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1711 initialMode = MachinePlaysBlack;
1712 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1713 initialMode = EditGame;
1714 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1715 initialMode = EditPosition;
1716 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1717 initialMode = Training;
1719 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1720 if( (len >= MSG_SIZ) && appData.debugMode )
1721 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1723 DisplayFatalError(buf, 0, 2);
1727 if (appData.matchMode) {
1728 if(appData.tourneyFile[0]) { // start tourney from command line
1730 if(f = fopen(appData.tourneyFile, "r")) {
1731 ParseArgsFromFile(f); // make sure tourney parmeters re known
1733 appData.clockMode = TRUE;
1735 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1738 } else if (*appData.cmailGameName != NULLCHAR) {
1739 /* Set up cmail mode */
1740 ReloadCmailMsgEvent(TRUE);
1742 /* Set up other modes */
1743 if (initialMode == AnalyzeFile) {
1744 if (*appData.loadGameFile == NULLCHAR) {
1745 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1749 if (*appData.loadGameFile != NULLCHAR) {
1750 (void) LoadGameFromFile(appData.loadGameFile,
1751 appData.loadGameIndex,
1752 appData.loadGameFile, TRUE);
1753 } else if (*appData.loadPositionFile != NULLCHAR) {
1754 (void) LoadPositionFromFile(appData.loadPositionFile,
1755 appData.loadPositionIndex,
1756 appData.loadPositionFile);
1757 /* [HGM] try to make self-starting even after FEN load */
1758 /* to allow automatic setup of fairy variants with wtm */
1759 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1760 gameMode = BeginningOfGame;
1761 setboardSpoiledMachineBlack = 1;
1763 /* [HGM] loadPos: make that every new game uses the setup */
1764 /* from file as long as we do not switch variant */
1765 if(!blackPlaysFirst) {
1766 startedFromPositionFile = TRUE;
1767 CopyBoard(filePosition, boards[0]);
1768 CopyBoard(initialPosition, boards[0]);
1770 } else if(*appData.fen != NULLCHAR) {
1771 if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1772 startedFromPositionFile = TRUE;
1776 if (initialMode == AnalyzeMode) {
1777 if (appData.noChessProgram) {
1778 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1781 if (appData.icsActive) {
1782 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1786 } else if (initialMode == AnalyzeFile) {
1787 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1788 ShowThinkingEvent();
1790 AnalysisPeriodicEvent(1);
1791 } else if (initialMode == MachinePlaysWhite) {
1792 if (appData.noChessProgram) {
1793 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1797 if (appData.icsActive) {
1798 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1802 MachineWhiteEvent();
1803 } else if (initialMode == MachinePlaysBlack) {
1804 if (appData.noChessProgram) {
1805 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1809 if (appData.icsActive) {
1810 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1814 MachineBlackEvent();
1815 } else if (initialMode == TwoMachinesPlay) {
1816 if (appData.noChessProgram) {
1817 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1821 if (appData.icsActive) {
1822 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1827 } else if (initialMode == EditGame) {
1829 } else if (initialMode == EditPosition) {
1830 EditPositionEvent();
1831 } else if (initialMode == Training) {
1832 if (*appData.loadGameFile == NULLCHAR) {
1833 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1842 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1844 DisplayBook(current+1);
1846 MoveHistorySet( movelist, first, last, current, pvInfoList );
1848 EvalGraphSet( first, last, current, pvInfoList );
1850 MakeEngineOutputTitle();
1854 * Establish will establish a contact to a remote host.port.
1855 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1856 * used to talk to the host.
1857 * Returns 0 if okay, error code if not.
1864 if (*appData.icsCommPort != NULLCHAR) {
1865 /* Talk to the host through a serial comm port */
1866 return OpenCommPort(appData.icsCommPort, &icsPR);
1868 } else if (*appData.gateway != NULLCHAR) {
1869 if (*appData.remoteShell == NULLCHAR) {
1870 /* Use the rcmd protocol to run telnet program on a gateway host */
1871 snprintf(buf, sizeof(buf), "%s %s %s",
1872 appData.telnetProgram, appData.icsHost, appData.icsPort);
1873 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1876 /* Use the rsh program to run telnet program on a gateway host */
1877 if (*appData.remoteUser == NULLCHAR) {
1878 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1879 appData.gateway, appData.telnetProgram,
1880 appData.icsHost, appData.icsPort);
1882 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1883 appData.remoteShell, appData.gateway,
1884 appData.remoteUser, appData.telnetProgram,
1885 appData.icsHost, appData.icsPort);
1887 return StartChildProcess(buf, "", &icsPR);
1890 } else if (appData.useTelnet) {
1891 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1894 /* TCP socket interface differs somewhat between
1895 Unix and NT; handle details in the front end.
1897 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1902 EscapeExpand (char *p, char *q)
1903 { // [HGM] initstring: routine to shape up string arguments
1904 while(*p++ = *q++) if(p[-1] == '\\')
1906 case 'n': p[-1] = '\n'; break;
1907 case 'r': p[-1] = '\r'; break;
1908 case 't': p[-1] = '\t'; break;
1909 case '\\': p[-1] = '\\'; break;
1910 case 0: *p = 0; return;
1911 default: p[-1] = q[-1]; break;
1916 show_bytes (FILE *fp, char *buf, int count)
1919 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1920 fprintf(fp, "\\%03o", *buf & 0xff);
1929 /* Returns an errno value */
1931 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1933 char buf[8192], *p, *q, *buflim;
1934 int left, newcount, outcount;
1936 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1937 *appData.gateway != NULLCHAR) {
1938 if (appData.debugMode) {
1939 fprintf(debugFP, ">ICS: ");
1940 show_bytes(debugFP, message, count);
1941 fprintf(debugFP, "\n");
1943 return OutputToProcess(pr, message, count, outError);
1946 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1953 if (appData.debugMode) {
1954 fprintf(debugFP, ">ICS: ");
1955 show_bytes(debugFP, buf, newcount);
1956 fprintf(debugFP, "\n");
1958 outcount = OutputToProcess(pr, buf, newcount, outError);
1959 if (outcount < newcount) return -1; /* to be sure */
1966 } else if (((unsigned char) *p) == TN_IAC) {
1967 *q++ = (char) TN_IAC;
1974 if (appData.debugMode) {
1975 fprintf(debugFP, ">ICS: ");
1976 show_bytes(debugFP, buf, newcount);
1977 fprintf(debugFP, "\n");
1979 outcount = OutputToProcess(pr, buf, newcount, outError);
1980 if (outcount < newcount) return -1; /* to be sure */
1985 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1987 int outError, outCount;
1988 static int gotEof = 0;
1991 /* Pass data read from player on to ICS */
1994 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1995 if (outCount < count) {
1996 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1998 if(have_sent_ICS_logon == 2) {
1999 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
2000 fprintf(ini, "%s", message);
2001 have_sent_ICS_logon = 3;
2003 have_sent_ICS_logon = 1;
2004 } else if(have_sent_ICS_logon == 3) {
2005 fprintf(ini, "%s", message);
2007 have_sent_ICS_logon = 1;
2009 } else if (count < 0) {
2010 RemoveInputSource(isr);
2011 DisplayFatalError(_("Error reading from keyboard"), error, 1);
2012 } else if (gotEof++ > 0) {
2013 RemoveInputSource(isr);
2014 DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
2020 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
2021 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2022 connectionAlive = FALSE; // only sticks if no response to 'date' command.
2023 SendToICS("date\n");
2024 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2027 /* added routine for printf style output to ics */
2029 ics_printf (char *format, ...)
2031 char buffer[MSG_SIZ];
2034 va_start(args, format);
2035 vsnprintf(buffer, sizeof(buffer), format, args);
2036 buffer[sizeof(buffer)-1] = '\0';
2044 int count, outCount, outError;
2046 if (icsPR == NoProc) return;
2049 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2050 if (outCount < count) {
2051 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2055 /* This is used for sending logon scripts to the ICS. Sending
2056 without a delay causes problems when using timestamp on ICC
2057 (at least on my machine). */
2059 SendToICSDelayed (char *s, long msdelay)
2061 int count, outCount, outError;
2063 if (icsPR == NoProc) return;
2066 if (appData.debugMode) {
2067 fprintf(debugFP, ">ICS: ");
2068 show_bytes(debugFP, s, count);
2069 fprintf(debugFP, "\n");
2071 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2073 if (outCount < count) {
2074 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2079 /* Remove all highlighting escape sequences in s
2080 Also deletes any suffix starting with '('
2083 StripHighlightAndTitle (char *s)
2085 static char retbuf[MSG_SIZ];
2088 while (*s != NULLCHAR) {
2089 while (*s == '\033') {
2090 while (*s != NULLCHAR && !isalpha(*s)) s++;
2091 if (*s != NULLCHAR) s++;
2093 while (*s != NULLCHAR && *s != '\033') {
2094 if (*s == '(' || *s == '[') {
2105 /* Remove all highlighting escape sequences in s */
2107 StripHighlight (char *s)
2109 static char retbuf[MSG_SIZ];
2112 while (*s != NULLCHAR) {
2113 while (*s == '\033') {
2114 while (*s != NULLCHAR && !isalpha(*s)) s++;
2115 if (*s != NULLCHAR) s++;
2117 while (*s != NULLCHAR && *s != '\033') {
2125 char engineVariant[MSG_SIZ];
2126 char *variantNames[] = VARIANT_NAMES;
2128 VariantName (VariantClass v)
2130 if(v == VariantUnknown || *engineVariant) return engineVariant;
2131 return variantNames[v];
2135 /* Identify a variant from the strings the chess servers use or the
2136 PGN Variant tag names we use. */
2138 StringToVariant (char *e)
2142 VariantClass v = VariantNormal;
2143 int i, found = FALSE;
2144 char buf[MSG_SIZ], c;
2149 /* [HGM] skip over optional board-size prefixes */
2150 if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2151 sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2152 while( *e++ != '_');
2155 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2159 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2160 if (p = StrCaseStr(e, variantNames[i])) {
2161 if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2162 v = (VariantClass) i;
2169 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2170 || StrCaseStr(e, "wild/fr")
2171 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2172 v = VariantFischeRandom;
2173 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2174 (i = 1, p = StrCaseStr(e, "w"))) {
2176 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2183 case 0: /* FICS only, actually */
2185 /* Castling legal even if K starts on d-file */
2186 v = VariantWildCastle;
2191 /* Castling illegal even if K & R happen to start in
2192 normal positions. */
2193 v = VariantNoCastle;
2206 /* Castling legal iff K & R start in normal positions */
2212 /* Special wilds for position setup; unclear what to do here */
2213 v = VariantLoadable;
2216 /* Bizarre ICC game */
2217 v = VariantTwoKings;
2220 v = VariantKriegspiel;
2226 v = VariantFischeRandom;
2229 v = VariantCrazyhouse;
2232 v = VariantBughouse;
2238 /* Not quite the same as FICS suicide! */
2239 v = VariantGiveaway;
2245 v = VariantShatranj;
2248 /* Temporary names for future ICC types. The name *will* change in
2249 the next xboard/WinBoard release after ICC defines it. */
2287 v = VariantCapablanca;
2290 v = VariantKnightmate;
2296 v = VariantCylinder;
2302 v = VariantCapaRandom;
2305 v = VariantBerolina;
2317 /* Found "wild" or "w" in the string but no number;
2318 must assume it's normal chess. */
2322 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2323 if( (len >= MSG_SIZ) && appData.debugMode )
2324 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2326 DisplayError(buf, 0);
2332 if (appData.debugMode) {
2333 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2334 e, wnum, VariantName(v));
2339 static int leftover_start = 0, leftover_len = 0;
2340 char star_match[STAR_MATCH_N][MSG_SIZ];
2342 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2343 advance *index beyond it, and set leftover_start to the new value of
2344 *index; else return FALSE. If pattern contains the character '*', it
2345 matches any sequence of characters not containing '\r', '\n', or the
2346 character following the '*' (if any), and the matched sequence(s) are
2347 copied into star_match.
2350 looking_at ( char *buf, int *index, char *pattern)
2352 char *bufp = &buf[*index], *patternp = pattern;
2354 char *matchp = star_match[0];
2357 if (*patternp == NULLCHAR) {
2358 *index = leftover_start = bufp - buf;
2362 if (*bufp == NULLCHAR) return FALSE;
2363 if (*patternp == '*') {
2364 if (*bufp == *(patternp + 1)) {
2366 matchp = star_match[++star_count];
2370 } else if (*bufp == '\n' || *bufp == '\r') {
2372 if (*patternp == NULLCHAR)
2377 *matchp++ = *bufp++;
2381 if (*patternp != *bufp) return FALSE;
2388 SendToPlayer (char *data, int length)
2390 int error, outCount;
2391 outCount = OutputToProcess(NoProc, data, length, &error);
2392 if (outCount < length) {
2393 DisplayFatalError(_("Error writing to display"), error, 1);
2398 PackHolding (char packed[], char *holding)
2408 switch (runlength) {
2419 sprintf(q, "%d", runlength);
2431 /* Telnet protocol requests from the front end */
2433 TelnetRequest (unsigned char ddww, unsigned char option)
2435 unsigned char msg[3];
2436 int outCount, outError;
2438 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2440 if (appData.debugMode) {
2441 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2457 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2466 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2469 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2474 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2476 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2483 if (!appData.icsActive) return;
2484 TelnetRequest(TN_DO, TN_ECHO);
2490 if (!appData.icsActive) return;
2491 TelnetRequest(TN_DONT, TN_ECHO);
2495 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2497 /* put the holdings sent to us by the server on the board holdings area */
2498 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2502 if(gameInfo.holdingsWidth < 2) return;
2503 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2504 return; // prevent overwriting by pre-board holdings
2506 if( (int)lowestPiece >= BlackPawn ) {
2509 holdingsStartRow = handSize-1;
2512 holdingsColumn = BOARD_WIDTH-1;
2513 countsColumn = BOARD_WIDTH-2;
2514 holdingsStartRow = 0;
2518 for(i=0; i<BOARD_RANKS-1; i++) { /* clear holdings */
2519 board[i][holdingsColumn] = EmptySquare;
2520 board[i][countsColumn] = (ChessSquare) 0;
2522 while( (p=*holdings++) != NULLCHAR ) {
2523 piece = CharToPiece( ToUpper(p) );
2524 if(piece == EmptySquare) continue;
2525 /*j = (int) piece - (int) WhitePawn;*/
2526 j = PieceToNumber(piece);
2527 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2528 if(j < 0) continue; /* should not happen */
2529 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2530 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2531 board[holdingsStartRow+j*direction][countsColumn]++;
2537 VariantSwitch (Board board, VariantClass newVariant)
2539 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2540 static Board oldBoard;
2542 startedFromPositionFile = FALSE;
2543 if(gameInfo.variant == newVariant) return;
2545 /* [HGM] This routine is called each time an assignment is made to
2546 * gameInfo.variant during a game, to make sure the board sizes
2547 * are set to match the new variant. If that means adding or deleting
2548 * holdings, we shift the playing board accordingly
2549 * This kludge is needed because in ICS observe mode, we get boards
2550 * of an ongoing game without knowing the variant, and learn about the
2551 * latter only later. This can be because of the move list we requested,
2552 * in which case the game history is refilled from the beginning anyway,
2553 * but also when receiving holdings of a crazyhouse game. In the latter
2554 * case we want to add those holdings to the already received position.
2558 if (appData.debugMode) {
2559 fprintf(debugFP, "Switch board from %s to %s\n",
2560 VariantName(gameInfo.variant), VariantName(newVariant));
2561 setbuf(debugFP, NULL);
2563 shuffleOpenings = 0; /* [HGM] shuffle */
2564 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2568 newWidth = 9; newHeight = 9;
2569 gameInfo.holdingsSize = 7;
2570 case VariantBughouse:
2571 case VariantCrazyhouse:
2572 newHoldingsWidth = 2; break;
2576 newHoldingsWidth = 2;
2577 gameInfo.holdingsSize = 8;
2580 case VariantCapablanca:
2581 case VariantCapaRandom:
2584 newHoldingsWidth = gameInfo.holdingsSize = 0;
2587 if(newWidth != gameInfo.boardWidth ||
2588 newHeight != gameInfo.boardHeight ||
2589 newHoldingsWidth != gameInfo.holdingsWidth ) {
2591 /* shift position to new playing area, if needed */
2592 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2593 for(i=0; i<BOARD_HEIGHT; i++)
2594 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2595 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2597 for(i=0; i<newHeight; i++) {
2598 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2599 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2601 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2602 for(i=0; i<BOARD_HEIGHT; i++)
2603 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2604 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2607 board[HOLDINGS_SET] = 0;
2608 gameInfo.boardWidth = newWidth;
2609 gameInfo.boardHeight = newHeight;
2610 gameInfo.holdingsWidth = newHoldingsWidth;
2611 gameInfo.variant = newVariant;
2612 InitDrawingSizes(-2, 0);
2613 } else gameInfo.variant = newVariant;
2614 CopyBoard(oldBoard, board); // remember correctly formatted board
2615 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2616 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2619 static int loggedOn = FALSE;
2621 /*-- Game start info cache: --*/
2623 char gs_kind[MSG_SIZ];
2624 static char player1Name[128] = "";
2625 static char player2Name[128] = "";
2626 static char cont_seq[] = "\n\\ ";
2627 static int player1Rating = -1;
2628 static int player2Rating = -1;
2629 /*----------------------------*/
2631 ColorClass curColor = ColorNormal;
2632 int suppressKibitz = 0;
2635 Boolean soughtPending = FALSE;
2636 Boolean seekGraphUp;
2637 #define MAX_SEEK_ADS 200
2639 char *seekAdList[MAX_SEEK_ADS];
2640 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2641 float tcList[MAX_SEEK_ADS];
2642 char colorList[MAX_SEEK_ADS];
2643 int nrOfSeekAds = 0;
2644 int minRating = 1010, maxRating = 2800;
2645 int hMargin = 10, vMargin = 20, h, w;
2646 extern int squareSize, lineGap;
2651 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2652 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2653 if(r < minRating+100 && r >=0 ) r = minRating+100;
2654 if(r > maxRating) r = maxRating;
2655 if(tc < 1.f) tc = 1.f;
2656 if(tc > 95.f) tc = 95.f;
2657 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2658 y = ((double)r - minRating)/(maxRating - minRating)
2659 * (h-vMargin-squareSize/8-1) + vMargin;
2660 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2661 if(strstr(seekAdList[i], " u ")) color = 1;
2662 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2663 !strstr(seekAdList[i], "bullet") &&
2664 !strstr(seekAdList[i], "blitz") &&
2665 !strstr(seekAdList[i], "standard") ) color = 2;
2666 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2667 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2671 PlotSingleSeekAd (int i)
2677 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2679 char buf[MSG_SIZ], *ext = "";
2680 VariantClass v = StringToVariant(type);
2681 if(strstr(type, "wild")) {
2682 ext = type + 4; // append wild number
2683 if(v == VariantFischeRandom) type = "chess960"; else
2684 if(v == VariantLoadable) type = "setup"; else
2685 type = VariantName(v);
2687 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2688 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2689 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2690 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2691 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2692 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2693 seekNrList[nrOfSeekAds] = nr;
2694 zList[nrOfSeekAds] = 0;
2695 seekAdList[nrOfSeekAds++] = StrSave(buf);
2696 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2701 EraseSeekDot (int i)
2703 int x = xList[i], y = yList[i], d=squareSize/4, k;
2704 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2705 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2706 // now replot every dot that overlapped
2707 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2708 int xx = xList[k], yy = yList[k];
2709 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2710 DrawSeekDot(xx, yy, colorList[k]);
2715 RemoveSeekAd (int nr)
2718 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2720 if(seekAdList[i]) free(seekAdList[i]);
2721 seekAdList[i] = seekAdList[--nrOfSeekAds];
2722 seekNrList[i] = seekNrList[nrOfSeekAds];
2723 ratingList[i] = ratingList[nrOfSeekAds];
2724 colorList[i] = colorList[nrOfSeekAds];
2725 tcList[i] = tcList[nrOfSeekAds];
2726 xList[i] = xList[nrOfSeekAds];
2727 yList[i] = yList[nrOfSeekAds];
2728 zList[i] = zList[nrOfSeekAds];
2729 seekAdList[nrOfSeekAds] = NULL;
2735 MatchSoughtLine (char *line)
2737 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2738 int nr, base, inc, u=0; char dummy;
2740 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2741 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2743 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2744 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2745 // match: compact and save the line
2746 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2756 if(!seekGraphUp) return FALSE;
2757 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2758 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap + 2*border;
2760 DrawSeekBackground(0, 0, w, h);
2761 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2762 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2763 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2764 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2766 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2769 snprintf(buf, MSG_SIZ, "%d", i);
2770 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2773 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2774 for(i=1; i<100; i+=(i<10?1:5)) {
2775 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2776 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2777 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2779 snprintf(buf, MSG_SIZ, "%d", i);
2780 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2783 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2788 SeekGraphClick (ClickType click, int x, int y, int moving)
2790 static int lastDown = 0, displayed = 0, lastSecond;
2791 if(y < 0) return FALSE;
2792 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2793 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2794 if(!seekGraphUp) return FALSE;
2795 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2796 DrawPosition(TRUE, NULL);
2799 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2800 if(click == Release || moving) return FALSE;
2802 soughtPending = TRUE;
2803 SendToICS(ics_prefix);
2804 SendToICS("sought\n"); // should this be "sought all"?
2805 } else { // issue challenge based on clicked ad
2806 int dist = 10000; int i, closest = 0, second = 0;
2807 for(i=0; i<nrOfSeekAds; i++) {
2808 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2809 if(d < dist) { dist = d; closest = i; }
2810 second += (d - zList[i] < 120); // count in-range ads
2811 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2815 second = (second > 1);
2816 if(displayed != closest || second != lastSecond) {
2817 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2818 lastSecond = second; displayed = closest;
2820 if(click == Press) {
2821 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2824 } // on press 'hit', only show info
2825 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2826 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2827 SendToICS(ics_prefix);
2829 return TRUE; // let incoming board of started game pop down the graph
2830 } else if(click == Release) { // release 'miss' is ignored
2831 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2832 if(moving == 2) { // right up-click
2833 nrOfSeekAds = 0; // refresh graph
2834 soughtPending = TRUE;
2835 SendToICS(ics_prefix);
2836 SendToICS("sought\n"); // should this be "sought all"?
2839 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2840 // press miss or release hit 'pop down' seek graph
2841 seekGraphUp = FALSE;
2842 DrawPosition(TRUE, NULL);
2848 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2850 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2851 #define STARTED_NONE 0
2852 #define STARTED_MOVES 1
2853 #define STARTED_BOARD 2
2854 #define STARTED_OBSERVE 3
2855 #define STARTED_HOLDINGS 4
2856 #define STARTED_CHATTER 5
2857 #define STARTED_COMMENT 6
2858 #define STARTED_MOVES_NOHIDE 7
2860 static int started = STARTED_NONE;
2861 static char parse[20000];
2862 static int parse_pos = 0;
2863 static char buf[BUF_SIZE + 1];
2864 static int firstTime = TRUE, intfSet = FALSE;
2865 static ColorClass prevColor = ColorNormal;
2866 static int savingComment = FALSE;
2867 static int cmatch = 0; // continuation sequence match
2874 int backup; /* [DM] For zippy color lines */
2876 char talker[MSG_SIZ]; // [HGM] chat
2877 int channel, collective=0;
2879 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2881 if (appData.debugMode) {
2883 fprintf(debugFP, "<ICS: ");
2884 show_bytes(debugFP, data, count);
2885 fprintf(debugFP, "\n");
2889 if (appData.debugMode) { int f = forwardMostMove;
2890 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2891 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2892 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2895 /* If last read ended with a partial line that we couldn't parse,
2896 prepend it to the new read and try again. */
2897 if (leftover_len > 0) {
2898 for (i=0; i<leftover_len; i++)
2899 buf[i] = buf[leftover_start + i];
2902 /* copy new characters into the buffer */
2903 bp = buf + leftover_len;
2904 buf_len=leftover_len;
2905 for (i=0; i<count; i++)
2908 if (data[i] == '\r')
2911 // join lines split by ICS?
2912 if (!appData.noJoin)
2915 Joining just consists of finding matches against the
2916 continuation sequence, and discarding that sequence
2917 if found instead of copying it. So, until a match
2918 fails, there's nothing to do since it might be the
2919 complete sequence, and thus, something we don't want
2922 if (data[i] == cont_seq[cmatch])
2925 if (cmatch == strlen(cont_seq))
2927 cmatch = 0; // complete match. just reset the counter
2930 it's possible for the ICS to not include the space
2931 at the end of the last word, making our [correct]
2932 join operation fuse two separate words. the server
2933 does this when the space occurs at the width setting.
2935 if (!buf_len || buf[buf_len-1] != ' ')
2946 match failed, so we have to copy what matched before
2947 falling through and copying this character. In reality,
2948 this will only ever be just the newline character, but
2949 it doesn't hurt to be precise.
2951 strncpy(bp, cont_seq, cmatch);
2963 buf[buf_len] = NULLCHAR;
2964 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2969 while (i < buf_len) {
2970 /* Deal with part of the TELNET option negotiation
2971 protocol. We refuse to do anything beyond the
2972 defaults, except that we allow the WILL ECHO option,
2973 which ICS uses to turn off password echoing when we are
2974 directly connected to it. We reject this option
2975 if localLineEditing mode is on (always on in xboard)
2976 and we are talking to port 23, which might be a real
2977 telnet server that will try to keep WILL ECHO on permanently.
2979 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2980 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2981 unsigned char option;
2983 switch ((unsigned char) buf[++i]) {
2985 if (appData.debugMode)
2986 fprintf(debugFP, "\n<WILL ");
2987 switch (option = (unsigned char) buf[++i]) {
2989 if (appData.debugMode)
2990 fprintf(debugFP, "ECHO ");
2991 /* Reply only if this is a change, according
2992 to the protocol rules. */
2993 if (remoteEchoOption) break;
2994 if (appData.localLineEditing &&
2995 atoi(appData.icsPort) == TN_PORT) {
2996 TelnetRequest(TN_DONT, TN_ECHO);
2999 TelnetRequest(TN_DO, TN_ECHO);
3000 remoteEchoOption = TRUE;
3004 if (appData.debugMode)
3005 fprintf(debugFP, "%d ", option);
3006 /* Whatever this is, we don't want it. */
3007 TelnetRequest(TN_DONT, option);
3012 if (appData.debugMode)
3013 fprintf(debugFP, "\n<WONT ");
3014 switch (option = (unsigned char) buf[++i]) {
3016 if (appData.debugMode)
3017 fprintf(debugFP, "ECHO ");
3018 /* Reply only if this is a change, according
3019 to the protocol rules. */
3020 if (!remoteEchoOption) break;
3022 TelnetRequest(TN_DONT, TN_ECHO);
3023 remoteEchoOption = FALSE;
3026 if (appData.debugMode)
3027 fprintf(debugFP, "%d ", (unsigned char) option);
3028 /* Whatever this is, it must already be turned
3029 off, because we never agree to turn on
3030 anything non-default, so according to the
3031 protocol rules, we don't reply. */
3036 if (appData.debugMode)
3037 fprintf(debugFP, "\n<DO ");
3038 switch (option = (unsigned char) buf[++i]) {
3040 /* Whatever this is, we refuse to do it. */
3041 if (appData.debugMode)
3042 fprintf(debugFP, "%d ", option);
3043 TelnetRequest(TN_WONT, option);
3048 if (appData.debugMode)
3049 fprintf(debugFP, "\n<DONT ");
3050 switch (option = (unsigned char) buf[++i]) {
3052 if (appData.debugMode)
3053 fprintf(debugFP, "%d ", option);
3054 /* Whatever this is, we are already not doing
3055 it, because we never agree to do anything
3056 non-default, so according to the protocol
3057 rules, we don't reply. */
3062 if (appData.debugMode)
3063 fprintf(debugFP, "\n<IAC ");
3064 /* Doubled IAC; pass it through */
3068 if (appData.debugMode)
3069 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3070 /* Drop all other telnet commands on the floor */
3073 if (oldi > next_out)
3074 SendToPlayer(&buf[next_out], oldi - next_out);
3080 /* OK, this at least will *usually* work */
3081 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3085 if (loggedOn && !intfSet) {
3086 if (ics_type == ICS_ICC) {
3087 snprintf(str, MSG_SIZ,
3088 "/set-quietly interface %s\n/set-quietly style 12\n",
3090 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3091 strcat(str, "/set-2 51 1\n/set seek 1\n");
3092 } else if (ics_type == ICS_CHESSNET) {
3093 snprintf(str, MSG_SIZ, "/style 12\n");
3095 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3096 strcat(str, programVersion);
3097 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3098 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3099 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3101 strcat(str, "$iset nohighlight 1\n");
3103 strcat(str, "$iset lock 1\n$style 12\n");
3106 NotifyFrontendLogin();
3110 if (started == STARTED_COMMENT) {
3111 /* Accumulate characters in comment */
3112 parse[parse_pos++] = buf[i];
3113 if (buf[i] == '\n') {
3114 parse[parse_pos] = NULLCHAR;
3115 if(chattingPartner>=0) {
3117 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3118 OutputChatMessage(chattingPartner, mess);
3119 if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3121 talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3122 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3123 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3124 OutputChatMessage(p, mess);
3128 chattingPartner = -1;
3129 if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3132 if(!suppressKibitz) // [HGM] kibitz
3133 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3134 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3135 int nrDigit = 0, nrAlph = 0, j;
3136 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3137 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3138 parse[parse_pos] = NULLCHAR;
3139 // try to be smart: if it does not look like search info, it should go to
3140 // ICS interaction window after all, not to engine-output window.
3141 for(j=0; j<parse_pos; j++) { // count letters and digits
3142 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3143 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3144 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3146 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3147 int depth=0; float score;
3148 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3149 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3150 pvInfoList[forwardMostMove-1].depth = depth;
3151 pvInfoList[forwardMostMove-1].score = 100*score;
3153 OutputKibitz(suppressKibitz, parse);
3156 if(gameMode == IcsObserving) // restore original ICS messages
3157 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3158 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3160 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3161 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3162 SendToPlayer(tmp, strlen(tmp));
3164 next_out = i+1; // [HGM] suppress printing in ICS window
3166 started = STARTED_NONE;
3168 /* Don't match patterns against characters in comment */
3173 if (started == STARTED_CHATTER) {
3174 if (buf[i] != '\n') {
3175 /* Don't match patterns against characters in chatter */
3179 started = STARTED_NONE;
3180 if(suppressKibitz) next_out = i+1;
3183 /* Kludge to deal with rcmd protocol */
3184 if (firstTime && looking_at(buf, &i, "\001*")) {
3185 DisplayFatalError(&buf[1], 0, 1);
3191 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3194 if (appData.debugMode)
3195 fprintf(debugFP, "ics_type %d\n", ics_type);
3198 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3199 ics_type = ICS_FICS;
3201 if (appData.debugMode)
3202 fprintf(debugFP, "ics_type %d\n", ics_type);
3205 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3206 ics_type = ICS_CHESSNET;
3208 if (appData.debugMode)
3209 fprintf(debugFP, "ics_type %d\n", ics_type);
3214 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3215 looking_at(buf, &i, "Logging you in as \"*\"") ||
3216 looking_at(buf, &i, "will be \"*\""))) {
3217 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3221 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3223 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3224 DisplayIcsInteractionTitle(buf);
3225 have_set_title = TRUE;
3228 /* skip finger notes */
3229 if (started == STARTED_NONE &&
3230 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3231 (buf[i] == '1' && buf[i+1] == '0')) &&
3232 buf[i+2] == ':' && buf[i+3] == ' ') {
3233 started = STARTED_CHATTER;
3239 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3240 if(appData.seekGraph) {
3241 if(soughtPending && MatchSoughtLine(buf+i)) {
3242 i = strstr(buf+i, "rated") - buf;
3243 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3244 next_out = leftover_start = i;
3245 started = STARTED_CHATTER;
3246 suppressKibitz = TRUE;
3249 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3250 && looking_at(buf, &i, "* ads displayed")) {
3251 soughtPending = FALSE;
3256 if(appData.autoRefresh) {
3257 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3258 int s = (ics_type == ICS_ICC); // ICC format differs
3260 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3261 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3262 looking_at(buf, &i, "*% "); // eat prompt
3263 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3264 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3265 next_out = i; // suppress
3268 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3269 char *p = star_match[0];
3271 if(seekGraphUp) RemoveSeekAd(atoi(p));
3272 while(*p && *p++ != ' '); // next
3274 looking_at(buf, &i, "*% "); // eat prompt
3275 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3282 /* skip formula vars */
3283 if (started == STARTED_NONE &&
3284 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3285 started = STARTED_CHATTER;
3290 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3291 if (appData.autoKibitz && started == STARTED_NONE &&
3292 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3293 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3294 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3295 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3296 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3297 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3298 suppressKibitz = TRUE;
3299 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3301 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3302 && (gameMode == IcsPlayingWhite)) ||
3303 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3304 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3305 started = STARTED_CHATTER; // own kibitz we simply discard
3307 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3308 parse_pos = 0; parse[0] = NULLCHAR;
3309 savingComment = TRUE;
3310 suppressKibitz = gameMode != IcsObserving ? 2 :
3311 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3315 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3316 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3317 && atoi(star_match[0])) {
3318 // suppress the acknowledgements of our own autoKibitz
3320 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3321 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3322 SendToPlayer(star_match[0], strlen(star_match[0]));
3323 if(looking_at(buf, &i, "*% ")) // eat prompt
3324 suppressKibitz = FALSE;
3328 } // [HGM] kibitz: end of patch
3330 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3332 // [HGM] chat: intercept tells by users for which we have an open chat window
3334 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3335 looking_at(buf, &i, "* whispers:") ||
3336 looking_at(buf, &i, "* kibitzes:") ||
3337 looking_at(buf, &i, "* shouts:") ||
3338 looking_at(buf, &i, "* c-shouts:") ||
3339 looking_at(buf, &i, "--> * ") ||
3340 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3341 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3342 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3343 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3345 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3346 chattingPartner = -1; collective = 0;
3348 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3349 for(p=0; p<MAX_CHAT; p++) {
3351 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3352 talker[0] = '['; strcat(talker, "] ");
3353 Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3354 chattingPartner = p; break;
3357 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3358 for(p=0; p<MAX_CHAT; p++) {
3360 if(!strcmp("kibitzes", chatPartner[p])) {
3361 talker[0] = '['; strcat(talker, "] ");
3362 chattingPartner = p; break;
3365 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3366 for(p=0; p<MAX_CHAT; p++) {
3368 if(!strcmp("whispers", chatPartner[p])) {
3369 talker[0] = '['; strcat(talker, "] ");
3370 chattingPartner = p; break;
3373 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3374 if(buf[i-8] == '-' && buf[i-3] == 't')
3375 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3377 if(!strcmp("c-shouts", chatPartner[p])) {
3378 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3379 chattingPartner = p; break;
3382 if(chattingPartner < 0)
3383 for(p=0; p<MAX_CHAT; p++) {
3385 if(!strcmp("shouts", chatPartner[p])) {
3386 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3387 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3388 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3389 chattingPartner = p; break;
3393 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3394 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3396 Colorize(ColorTell, FALSE);
3397 if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3399 chattingPartner = p; break;
3401 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3402 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3403 started = STARTED_COMMENT;
3404 parse_pos = 0; parse[0] = NULLCHAR;
3405 savingComment = 3 + chattingPartner; // counts as TRUE
3406 if(collective == 3) i = oldi; else {
3407 suppressKibitz = TRUE;
3408 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3409 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3413 } // [HGM] chat: end of patch
3416 if (appData.zippyTalk || appData.zippyPlay) {
3417 /* [DM] Backup address for color zippy lines */
3419 if (loggedOn == TRUE)
3420 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3421 (appData.zippyPlay && ZippyMatch(buf, &backup)))
3424 } // [DM] 'else { ' deleted
3426 /* Regular tells and says */
3427 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3428 looking_at(buf, &i, "* (your partner) tells you: ") ||
3429 looking_at(buf, &i, "* says: ") ||
3430 /* Don't color "message" or "messages" output */
3431 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3432 looking_at(buf, &i, "*. * at *:*: ") ||
3433 looking_at(buf, &i, "--* (*:*): ") ||
3434 /* Message notifications (same color as tells) */
3435 looking_at(buf, &i, "* has left a message ") ||
3436 looking_at(buf, &i, "* just sent you a message:\n") ||
3437 /* Whispers and kibitzes */
3438 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3439 looking_at(buf, &i, "* kibitzes: ") ||
3441 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3443 if (tkind == 1 && strchr(star_match[0], ':')) {
3444 /* Avoid "tells you:" spoofs in channels */
3447 if (star_match[0][0] == NULLCHAR ||
3448 strchr(star_match[0], ' ') ||
3449 (tkind == 3 && strchr(star_match[1], ' '))) {
3450 /* Reject bogus matches */
3453 if (appData.colorize) {
3454 if (oldi > next_out) {
3455 SendToPlayer(&buf[next_out], oldi - next_out);
3460 Colorize(ColorTell, FALSE);
3461 curColor = ColorTell;
3464 Colorize(ColorKibitz, FALSE);
3465 curColor = ColorKibitz;
3468 p = strrchr(star_match[1], '(');
3475 Colorize(ColorChannel1, FALSE);
3476 curColor = ColorChannel1;
3478 Colorize(ColorChannel, FALSE);
3479 curColor = ColorChannel;
3483 curColor = ColorNormal;
3487 if (started == STARTED_NONE && appData.autoComment &&
3488 (gameMode == IcsObserving ||
3489 gameMode == IcsPlayingWhite ||
3490 gameMode == IcsPlayingBlack)) {
3491 parse_pos = i - oldi;
3492 memcpy(parse, &buf[oldi], parse_pos);
3493 parse[parse_pos] = NULLCHAR;
3494 started = STARTED_COMMENT;
3495 savingComment = TRUE;
3496 } else if(collective != 3) {
3497 started = STARTED_CHATTER;
3498 savingComment = FALSE;
3505 if (looking_at(buf, &i, "* s-shouts: ") ||
3506 looking_at(buf, &i, "* c-shouts: ")) {
3507 if (appData.colorize) {
3508 if (oldi > next_out) {
3509 SendToPlayer(&buf[next_out], oldi - next_out);
3512 Colorize(ColorSShout, FALSE);
3513 curColor = ColorSShout;
3516 started = STARTED_CHATTER;
3520 if (looking_at(buf, &i, "--->")) {
3525 if (looking_at(buf, &i, "* shouts: ") ||
3526 looking_at(buf, &i, "--> ")) {
3527 if (appData.colorize) {
3528 if (oldi > next_out) {
3529 SendToPlayer(&buf[next_out], oldi - next_out);
3532 Colorize(ColorShout, FALSE);
3533 curColor = ColorShout;
3536 started = STARTED_CHATTER;
3540 if (looking_at( buf, &i, "Challenge:")) {
3541 if (appData.colorize) {
3542 if (oldi > next_out) {
3543 SendToPlayer(&buf[next_out], oldi - next_out);
3546 Colorize(ColorChallenge, FALSE);
3547 curColor = ColorChallenge;
3553 if (looking_at(buf, &i, "* offers you") ||
3554 looking_at(buf, &i, "* offers to be") ||
3555 looking_at(buf, &i, "* would like to") ||
3556 looking_at(buf, &i, "* requests to") ||
3557 looking_at(buf, &i, "Your opponent offers") ||
3558 looking_at(buf, &i, "Your opponent requests")) {
3560 if (appData.colorize) {
3561 if (oldi > next_out) {
3562 SendToPlayer(&buf[next_out], oldi - next_out);
3565 Colorize(ColorRequest, FALSE);
3566 curColor = ColorRequest;
3571 if (looking_at(buf, &i, "* (*) seeking")) {
3572 if (appData.colorize) {
3573 if (oldi > next_out) {
3574 SendToPlayer(&buf[next_out], oldi - next_out);
3577 Colorize(ColorSeek, FALSE);
3578 curColor = ColorSeek;
3583 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3585 if (looking_at(buf, &i, "\\ ")) {
3586 if (prevColor != ColorNormal) {
3587 if (oldi > next_out) {
3588 SendToPlayer(&buf[next_out], oldi - next_out);
3591 Colorize(prevColor, TRUE);
3592 curColor = prevColor;
3594 if (savingComment) {
3595 parse_pos = i - oldi;
3596 memcpy(parse, &buf[oldi], parse_pos);
3597 parse[parse_pos] = NULLCHAR;
3598 started = STARTED_COMMENT;
3599 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3600 chattingPartner = savingComment - 3; // kludge to remember the box
3602 started = STARTED_CHATTER;
3607 if (looking_at(buf, &i, "Black Strength :") ||
3608 looking_at(buf, &i, "<<< style 10 board >>>") ||
3609 looking_at(buf, &i, "<10>") ||
3610 looking_at(buf, &i, "#@#")) {
3611 /* Wrong board style */
3613 SendToICS(ics_prefix);
3614 SendToICS("set style 12\n");
3615 SendToICS(ics_prefix);
3616 SendToICS("refresh\n");
3620 if (looking_at(buf, &i, "login:")) {
3621 if (!have_sent_ICS_logon) {
3623 have_sent_ICS_logon = 1;
3624 else // no init script was found
3625 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3626 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3627 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3632 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3633 (looking_at(buf, &i, "\n<12> ") ||
3634 looking_at(buf, &i, "<12> "))) {
3636 if (oldi > next_out) {
3637 SendToPlayer(&buf[next_out], oldi - next_out);
3640 started = STARTED_BOARD;
3645 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3646 looking_at(buf, &i, "<b1> ")) {
3647 if (oldi > next_out) {
3648 SendToPlayer(&buf[next_out], oldi - next_out);
3651 started = STARTED_HOLDINGS;
3656 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3658 /* Header for a move list -- first line */
3660 switch (ics_getting_history) {
3664 case BeginningOfGame:
3665 /* User typed "moves" or "oldmoves" while we
3666 were idle. Pretend we asked for these
3667 moves and soak them up so user can step
3668 through them and/or save them.
3671 gameMode = IcsObserving;
3674 ics_getting_history = H_GOT_UNREQ_HEADER;
3676 case EditGame: /*?*/
3677 case EditPosition: /*?*/
3678 /* Should above feature work in these modes too? */
3679 /* For now it doesn't */
3680 ics_getting_history = H_GOT_UNWANTED_HEADER;
3683 ics_getting_history = H_GOT_UNWANTED_HEADER;
3688 /* Is this the right one? */
3689 if (gameInfo.white && gameInfo.black &&
3690 strcmp(gameInfo.white, star_match[0]) == 0 &&
3691 strcmp(gameInfo.black, star_match[2]) == 0) {
3693 ics_getting_history = H_GOT_REQ_HEADER;
3696 case H_GOT_REQ_HEADER:
3697 case H_GOT_UNREQ_HEADER:
3698 case H_GOT_UNWANTED_HEADER:
3699 case H_GETTING_MOVES:
3700 /* Should not happen */
3701 DisplayError(_("Error gathering move list: two headers"), 0);
3702 ics_getting_history = H_FALSE;
3706 /* Save player ratings into gameInfo if needed */
3707 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3708 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3709 (gameInfo.whiteRating == -1 ||
3710 gameInfo.blackRating == -1)) {
3712 gameInfo.whiteRating = string_to_rating(star_match[1]);
3713 gameInfo.blackRating = string_to_rating(star_match[3]);
3714 if (appData.debugMode)
3715 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3716 gameInfo.whiteRating, gameInfo.blackRating);
3721 if (looking_at(buf, &i,
3722 "* * match, initial time: * minute*, increment: * second")) {
3723 /* Header for a move list -- second line */
3724 /* Initial board will follow if this is a wild game */
3725 if (gameInfo.event != NULL) free(gameInfo.event);
3726 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3727 gameInfo.event = StrSave(str);
3728 /* [HGM] we switched variant. Translate boards if needed. */
3729 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3733 if (looking_at(buf, &i, "Move ")) {
3734 /* Beginning of a move list */
3735 switch (ics_getting_history) {
3737 /* Normally should not happen */
3738 /* Maybe user hit reset while we were parsing */
3741 /* Happens if we are ignoring a move list that is not
3742 * the one we just requested. Common if the user
3743 * tries to observe two games without turning off
3746 case H_GETTING_MOVES:
3747 /* Should not happen */
3748 DisplayError(_("Error gathering move list: nested"), 0);
3749 ics_getting_history = H_FALSE;
3751 case H_GOT_REQ_HEADER:
3752 ics_getting_history = H_GETTING_MOVES;
3753 started = STARTED_MOVES;
3755 if (oldi > next_out) {
3756 SendToPlayer(&buf[next_out], oldi - next_out);
3759 case H_GOT_UNREQ_HEADER:
3760 ics_getting_history = H_GETTING_MOVES;
3761 started = STARTED_MOVES_NOHIDE;
3764 case H_GOT_UNWANTED_HEADER:
3765 ics_getting_history = H_FALSE;
3771 if (looking_at(buf, &i, "% ") ||
3772 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3773 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3774 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3775 soughtPending = FALSE;
3779 if(suppressKibitz) next_out = i;
3780 savingComment = FALSE;
3784 case STARTED_MOVES_NOHIDE:
3785 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3786 parse[parse_pos + i - oldi] = NULLCHAR;
3787 ParseGameHistory(parse);
3789 if (appData.zippyPlay && first.initDone) {
3790 FeedMovesToProgram(&first, forwardMostMove);
3791 if (gameMode == IcsPlayingWhite) {
3792 if (WhiteOnMove(forwardMostMove)) {
3793 if (first.sendTime) {
3794 if (first.useColors) {
3795 SendToProgram("black\n", &first);
3797 SendTimeRemaining(&first, TRUE);
3799 if (first.useColors) {
3800 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3802 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3803 first.maybeThinking = TRUE;
3805 if (first.usePlayother) {
3806 if (first.sendTime) {
3807 SendTimeRemaining(&first, TRUE);
3809 SendToProgram("playother\n", &first);
3815 } else if (gameMode == IcsPlayingBlack) {
3816 if (!WhiteOnMove(forwardMostMove)) {
3817 if (first.sendTime) {
3818 if (first.useColors) {
3819 SendToProgram("white\n", &first);
3821 SendTimeRemaining(&first, FALSE);
3823 if (first.useColors) {
3824 SendToProgram("black\n", &first);
3826 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3827 first.maybeThinking = TRUE;
3829 if (first.usePlayother) {
3830 if (first.sendTime) {
3831 SendTimeRemaining(&first, FALSE);
3833 SendToProgram("playother\n", &first);
3842 if (gameMode == IcsObserving && ics_gamenum == -1) {
3843 /* Moves came from oldmoves or moves command
3844 while we weren't doing anything else.
3846 currentMove = forwardMostMove;
3847 ClearHighlights();/*!!could figure this out*/
3848 flipView = appData.flipView;
3849 DrawPosition(TRUE, boards[currentMove]);
3850 DisplayBothClocks();
3851 snprintf(str, MSG_SIZ, "%s %s %s",
3852 gameInfo.white, _("vs."), gameInfo.black);
3856 /* Moves were history of an active game */
3857 if (gameInfo.resultDetails != NULL) {
3858 free(gameInfo.resultDetails);
3859 gameInfo.resultDetails = NULL;
3862 HistorySet(parseList, backwardMostMove,
3863 forwardMostMove, currentMove-1);
3864 DisplayMove(currentMove - 1);
3865 if (started == STARTED_MOVES) next_out = i;
3866 started = STARTED_NONE;
3867 ics_getting_history = H_FALSE;
3870 case STARTED_OBSERVE:
3871 started = STARTED_NONE;
3872 SendToICS(ics_prefix);
3873 SendToICS("refresh\n");
3879 if(bookHit) { // [HGM] book: simulate book reply
3880 static char bookMove[MSG_SIZ]; // a bit generous?
3882 programStats.nodes = programStats.depth = programStats.time =
3883 programStats.score = programStats.got_only_move = 0;
3884 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3886 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3887 strcat(bookMove, bookHit);
3888 HandleMachineMove(bookMove, &first);
3893 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3894 started == STARTED_HOLDINGS ||
3895 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3896 /* Accumulate characters in move list or board */
3897 parse[parse_pos++] = buf[i];
3900 /* Start of game messages. Mostly we detect start of game
3901 when the first board image arrives. On some versions
3902 of the ICS, though, we need to do a "refresh" after starting
3903 to observe in order to get the current board right away. */
3904 if (looking_at(buf, &i, "Adding game * to observation list")) {
3905 started = STARTED_OBSERVE;
3909 /* Handle auto-observe */
3910 if (appData.autoObserve &&
3911 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3912 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3914 /* Choose the player that was highlighted, if any. */
3915 if (star_match[0][0] == '\033' ||
3916 star_match[1][0] != '\033') {
3917 player = star_match[0];
3919 player = star_match[2];
3921 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3922 ics_prefix, StripHighlightAndTitle(player));
3925 /* Save ratings from notify string */
3926 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3927 player1Rating = string_to_rating(star_match[1]);
3928 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3929 player2Rating = string_to_rating(star_match[3]);
3931 if (appData.debugMode)
3933 "Ratings from 'Game notification:' %s %d, %s %d\n",
3934 player1Name, player1Rating,
3935 player2Name, player2Rating);
3940 /* Deal with automatic examine mode after a game,
3941 and with IcsObserving -> IcsExamining transition */
3942 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3943 looking_at(buf, &i, "has made you an examiner of game *")) {
3945 int gamenum = atoi(star_match[0]);
3946 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3947 gamenum == ics_gamenum) {
3948 /* We were already playing or observing this game;
3949 no need to refetch history */
3950 gameMode = IcsExamining;
3952 pauseExamForwardMostMove = forwardMostMove;
3953 } else if (currentMove < forwardMostMove) {
3954 ForwardInner(forwardMostMove);
3957 /* I don't think this case really can happen */
3958 SendToICS(ics_prefix);
3959 SendToICS("refresh\n");
3964 /* Error messages */
3965 // if (ics_user_moved) {
3966 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3967 if (looking_at(buf, &i, "Illegal move") ||
3968 looking_at(buf, &i, "Not a legal move") ||
3969 looking_at(buf, &i, "Your king is in check") ||
3970 looking_at(buf, &i, "It isn't your turn") ||
3971 looking_at(buf, &i, "It is not your move")) {
3973 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3974 currentMove = forwardMostMove-1;
3975 DisplayMove(currentMove - 1); /* before DMError */
3976 DrawPosition(FALSE, boards[currentMove]);
3977 SwitchClocks(forwardMostMove-1); // [HGM] race
3978 DisplayBothClocks();
3980 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3986 if (looking_at(buf, &i, "still have time") ||
3987 looking_at(buf, &i, "not out of time") ||
3988 looking_at(buf, &i, "either player is out of time") ||
3989 looking_at(buf, &i, "has timeseal; checking")) {
3990 /* We must have called his flag a little too soon */
3991 whiteFlag = blackFlag = FALSE;
3995 if (looking_at(buf, &i, "added * seconds to") ||
3996 looking_at(buf, &i, "seconds were added to")) {
3997 /* Update the clocks */
3998 SendToICS(ics_prefix);
3999 SendToICS("refresh\n");
4003 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
4004 ics_clock_paused = TRUE;
4009 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
4010 ics_clock_paused = FALSE;
4015 /* Grab player ratings from the Creating: message.
4016 Note we have to check for the special case when
4017 the ICS inserts things like [white] or [black]. */
4018 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
4019 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
4021 0 player 1 name (not necessarily white)
4023 2 empty, white, or black (IGNORED)
4024 3 player 2 name (not necessarily black)
4027 The names/ratings are sorted out when the game
4028 actually starts (below).
4030 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4031 player1Rating = string_to_rating(star_match[1]);
4032 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4033 player2Rating = string_to_rating(star_match[4]);
4035 if (appData.debugMode)
4037 "Ratings from 'Creating:' %s %d, %s %d\n",
4038 player1Name, player1Rating,
4039 player2Name, player2Rating);
4044 /* Improved generic start/end-of-game messages */
4045 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4046 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4047 /* If tkind == 0: */
4048 /* star_match[0] is the game number */
4049 /* [1] is the white player's name */
4050 /* [2] is the black player's name */
4051 /* For end-of-game: */
4052 /* [3] is the reason for the game end */
4053 /* [4] is a PGN end game-token, preceded by " " */
4054 /* For start-of-game: */
4055 /* [3] begins with "Creating" or "Continuing" */
4056 /* [4] is " *" or empty (don't care). */
4057 int gamenum = atoi(star_match[0]);
4058 char *whitename, *blackname, *why, *endtoken;
4059 ChessMove endtype = EndOfFile;
4062 whitename = star_match[1];
4063 blackname = star_match[2];
4064 why = star_match[3];
4065 endtoken = star_match[4];
4067 whitename = star_match[1];
4068 blackname = star_match[3];
4069 why = star_match[5];
4070 endtoken = star_match[6];
4073 /* Game start messages */
4074 if (strncmp(why, "Creating ", 9) == 0 ||
4075 strncmp(why, "Continuing ", 11) == 0) {
4076 gs_gamenum = gamenum;
4077 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4078 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4079 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4081 if (appData.zippyPlay) {
4082 ZippyGameStart(whitename, blackname);
4085 partnerBoardValid = FALSE; // [HGM] bughouse
4089 /* Game end messages */
4090 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4091 ics_gamenum != gamenum) {
4094 while (endtoken[0] == ' ') endtoken++;
4095 switch (endtoken[0]) {
4098 endtype = GameUnfinished;
4101 endtype = BlackWins;
4104 if (endtoken[1] == '/')
4105 endtype = GameIsDrawn;
4107 endtype = WhiteWins;
4110 GameEnds(endtype, why, GE_ICS);
4112 if (appData.zippyPlay && first.initDone) {
4113 ZippyGameEnd(endtype, why);
4114 if (first.pr == NoProc) {
4115 /* Start the next process early so that we'll
4116 be ready for the next challenge */
4117 StartChessProgram(&first);
4119 /* Send "new" early, in case this command takes
4120 a long time to finish, so that we'll be ready
4121 for the next challenge. */
4122 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4126 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4130 if (looking_at(buf, &i, "Removing game * from observation") ||
4131 looking_at(buf, &i, "no longer observing game *") ||
4132 looking_at(buf, &i, "Game * (*) has no examiners")) {
4133 if (gameMode == IcsObserving &&
4134 atoi(star_match[0]) == ics_gamenum)
4136 /* icsEngineAnalyze */
4137 if (appData.icsEngineAnalyze) {
4144 ics_user_moved = FALSE;
4149 if (looking_at(buf, &i, "no longer examining game *")) {
4150 if (gameMode == IcsExamining &&
4151 atoi(star_match[0]) == ics_gamenum)
4155 ics_user_moved = FALSE;
4160 /* Advance leftover_start past any newlines we find,
4161 so only partial lines can get reparsed */
4162 if (looking_at(buf, &i, "\n")) {
4163 prevColor = curColor;
4164 if (curColor != ColorNormal) {
4165 if (oldi > next_out) {
4166 SendToPlayer(&buf[next_out], oldi - next_out);
4169 Colorize(ColorNormal, FALSE);
4170 curColor = ColorNormal;
4172 if (started == STARTED_BOARD) {
4173 started = STARTED_NONE;
4174 parse[parse_pos] = NULLCHAR;
4175 ParseBoard12(parse);
4178 /* Send premove here */
4179 if (appData.premove) {
4181 if (currentMove == 0 &&
4182 gameMode == IcsPlayingWhite &&
4183 appData.premoveWhite) {
4184 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4185 if (appData.debugMode)
4186 fprintf(debugFP, "Sending premove:\n");
4188 } else if (currentMove == 1 &&
4189 gameMode == IcsPlayingBlack &&
4190 appData.premoveBlack) {
4191 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4192 if (appData.debugMode)
4193 fprintf(debugFP, "Sending premove:\n");
4195 } else if (gotPremove) {
4196 int oldFMM = forwardMostMove;
4198 ClearPremoveHighlights();
4199 if (appData.debugMode)
4200 fprintf(debugFP, "Sending premove:\n");
4201 UserMoveEvent(premoveFromX, premoveFromY,
4202 premoveToX, premoveToY,
4204 if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4205 if(moveList[oldFMM-1][1] != '@')
4206 SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4207 moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4209 SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4214 /* Usually suppress following prompt */
4215 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4216 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4217 if (looking_at(buf, &i, "*% ")) {
4218 savingComment = FALSE;
4223 } else if (started == STARTED_HOLDINGS) {
4225 char new_piece[MSG_SIZ];
4226 started = STARTED_NONE;
4227 parse[parse_pos] = NULLCHAR;
4228 if (appData.debugMode)
4229 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4230 parse, currentMove);
4231 if (sscanf(parse, " game %d", &gamenum) == 1) {
4232 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4233 new_piece[0] = NULLCHAR;
4234 sscanf(parse, "game %d white [%s black [%s <- %s",
4235 &gamenum, white_holding, black_holding,
4237 white_holding[strlen(white_holding)-1] = NULLCHAR;
4238 black_holding[strlen(black_holding)-1] = NULLCHAR;
4239 if (gameInfo.variant == VariantNormal) {
4240 /* [HGM] We seem to switch variant during a game!
4241 * Presumably no holdings were displayed, so we have
4242 * to move the position two files to the right to
4243 * create room for them!
4245 VariantClass newVariant;
4246 switch(gameInfo.boardWidth) { // base guess on board width
4247 case 9: newVariant = VariantShogi; break;
4248 case 10: newVariant = VariantGreat; break;
4249 default: newVariant = VariantCrazyhouse;
4250 if(strchr(white_holding, 'E') || strchr(black_holding, 'E') ||
4251 strchr(white_holding, 'H') || strchr(black_holding, 'H') )
4252 newVariant = VariantSChess;
4254 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4255 /* Get a move list just to see the header, which
4256 will tell us whether this is really bug or zh */
4257 if (ics_getting_history == H_FALSE) {
4258 ics_getting_history = H_REQUESTED;
4259 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4263 /* [HGM] copy holdings to board holdings area */
4264 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4265 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4266 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4268 if (appData.zippyPlay && first.initDone) {
4269 ZippyHoldings(white_holding, black_holding,
4273 if (tinyLayout || smallLayout) {
4274 char wh[16], bh[16];
4275 PackHolding(wh, white_holding);
4276 PackHolding(bh, black_holding);
4277 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4278 gameInfo.white, gameInfo.black);
4280 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4281 gameInfo.white, white_holding, _("vs."),
4282 gameInfo.black, black_holding);
4284 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4285 DrawPosition(FALSE, boards[currentMove]);
4287 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4288 sscanf(parse, "game %d white [%s black [%s <- %s",
4289 &gamenum, white_holding, black_holding,
4291 white_holding[strlen(white_holding)-1] = NULLCHAR;
4292 black_holding[strlen(black_holding)-1] = NULLCHAR;
4293 /* [HGM] copy holdings to partner-board holdings area */
4294 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4295 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4296 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4297 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4298 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4301 /* Suppress following prompt */
4302 if (looking_at(buf, &i, "*% ")) {
4303 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4304 savingComment = FALSE;
4312 i++; /* skip unparsed character and loop back */
4315 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4316 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4317 // SendToPlayer(&buf[next_out], i - next_out);
4318 started != STARTED_HOLDINGS && leftover_start > next_out) {
4319 SendToPlayer(&buf[next_out], leftover_start - next_out);
4323 leftover_len = buf_len - leftover_start;
4324 /* if buffer ends with something we couldn't parse,
4325 reparse it after appending the next read */
4327 } else if (count == 0) {
4328 RemoveInputSource(isr);
4329 DisplayFatalError(_("Connection closed by ICS"), 0, 6666);
4331 DisplayFatalError(_("Error reading from ICS"), error, 1);
4336 /* Board style 12 looks like this:
4338 <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
4340 * The "<12> " is stripped before it gets to this routine. The two
4341 * trailing 0's (flip state and clock ticking) are later addition, and
4342 * some chess servers may not have them, or may have only the first.
4343 * Additional trailing fields may be added in the future.
4346 #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"
4348 #define RELATION_OBSERVING_PLAYED 0
4349 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4350 #define RELATION_PLAYING_MYMOVE 1
4351 #define RELATION_PLAYING_NOTMYMOVE -1
4352 #define RELATION_EXAMINING 2
4353 #define RELATION_ISOLATED_BOARD -3
4354 #define RELATION_STARTING_POSITION -4 /* FICS only */
4357 ParseBoard12 (char *string)
4361 char *bookHit = NULL; // [HGM] book
4363 GameMode newGameMode;
4364 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4365 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4366 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4367 char to_play, board_chars[200];
4368 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4369 char black[32], white[32];
4371 int prevMove = currentMove;
4374 int fromX, fromY, toX, toY;
4376 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4377 Boolean weird = FALSE, reqFlag = FALSE;
4379 fromX = fromY = toX = toY = -1;
4383 if (appData.debugMode)
4384 fprintf(debugFP, "Parsing board: %s\n", string);
4386 move_str[0] = NULLCHAR;
4387 elapsed_time[0] = NULLCHAR;
4388 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4390 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4391 if(string[i] == ' ') { ranks++; files = 0; }
4393 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4396 for(j = 0; j <i; j++) board_chars[j] = string[j];
4397 board_chars[i] = '\0';
4400 n = sscanf(string, PATTERN, &to_play, &double_push,
4401 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4402 &gamenum, white, black, &relation, &basetime, &increment,
4403 &white_stren, &black_stren, &white_time, &black_time,
4404 &moveNum, str, elapsed_time, move_str, &ics_flip,
4408 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4409 DisplayError(str, 0);
4413 /* Convert the move number to internal form */
4414 moveNum = (moveNum - 1) * 2;
4415 if (to_play == 'B') moveNum++;
4416 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4417 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4423 case RELATION_OBSERVING_PLAYED:
4424 case RELATION_OBSERVING_STATIC:
4425 if (gamenum == -1) {
4426 /* Old ICC buglet */
4427 relation = RELATION_OBSERVING_STATIC;
4429 newGameMode = IcsObserving;
4431 case RELATION_PLAYING_MYMOVE:
4432 case RELATION_PLAYING_NOTMYMOVE:
4434 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4435 IcsPlayingWhite : IcsPlayingBlack;
4436 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4438 case RELATION_EXAMINING:
4439 newGameMode = IcsExamining;
4441 case RELATION_ISOLATED_BOARD:
4443 /* Just display this board. If user was doing something else,
4444 we will forget about it until the next board comes. */
4445 newGameMode = IcsIdle;
4447 case RELATION_STARTING_POSITION:
4448 newGameMode = gameMode;
4452 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4453 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4454 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4455 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4456 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4457 static int lastBgGame = -1;
4459 for (k = 0; k < ranks; k++) {
4460 for (j = 0; j < files; j++)
4461 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4462 if(gameInfo.holdingsWidth > 1) {
4463 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4464 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4467 CopyBoard(partnerBoard, board);
4468 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4469 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4470 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4471 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4472 if(toSqr = strchr(str, '-')) {
4473 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4474 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4475 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4476 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4477 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4478 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4480 DisplayWhiteClock(white_time*fac, to_play == 'W');
4481 DisplayBlackClock(black_time*fac, to_play != 'W');
4482 activePartner = to_play;
4483 if(gamenum != lastBgGame) {
4485 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4488 lastBgGame = gamenum;
4489 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4490 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4491 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4492 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4493 if(!twoBoards) DisplayMessage(partnerStatus, "");
4494 partnerBoardValid = TRUE;
4498 if(appData.dualBoard && appData.bgObserve) {
4499 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4500 SendToICS(ics_prefix), SendToICS("pobserve\n");
4501 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4503 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4508 /* Modify behavior for initial board display on move listing
4511 switch (ics_getting_history) {
4515 case H_GOT_REQ_HEADER:
4516 case H_GOT_UNREQ_HEADER:
4517 /* This is the initial position of the current game */
4518 gamenum = ics_gamenum;
4519 moveNum = 0; /* old ICS bug workaround */
4520 if (to_play == 'B') {
4521 startedFromSetupPosition = TRUE;
4522 blackPlaysFirst = TRUE;
4524 if (forwardMostMove == 0) forwardMostMove = 1;
4525 if (backwardMostMove == 0) backwardMostMove = 1;
4526 if (currentMove == 0) currentMove = 1;
4528 newGameMode = gameMode;
4529 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4531 case H_GOT_UNWANTED_HEADER:
4532 /* This is an initial board that we don't want */
4534 case H_GETTING_MOVES:
4535 /* Should not happen */
4536 DisplayError(_("Error gathering move list: extra board"), 0);
4537 ics_getting_history = H_FALSE;
4541 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4542 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4543 weird && (int)gameInfo.variant < (int)VariantShogi) {
4544 /* [HGM] We seem to have switched variant unexpectedly
4545 * Try to guess new variant from board size
4547 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4548 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4549 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4550 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4551 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4552 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4553 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4554 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4555 /* Get a move list just to see the header, which
4556 will tell us whether this is really bug or zh */
4557 if (ics_getting_history == H_FALSE) {
4558 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4559 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4564 /* Take action if this is the first board of a new game, or of a
4565 different game than is currently being displayed. */
4566 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4567 relation == RELATION_ISOLATED_BOARD) {
4569 /* Forget the old game and get the history (if any) of the new one */
4570 if (gameMode != BeginningOfGame) {
4574 if (appData.autoRaiseBoard) BoardToTop();
4576 if (gamenum == -1) {
4577 newGameMode = IcsIdle;
4578 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4579 appData.getMoveList && !reqFlag) {
4580 /* Need to get game history */
4581 ics_getting_history = H_REQUESTED;
4582 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4586 /* Initially flip the board to have black on the bottom if playing
4587 black or if the ICS flip flag is set, but let the user change
4588 it with the Flip View button. */
4589 flipView = appData.autoFlipView ?
4590 (newGameMode == IcsPlayingBlack) || ics_flip :
4593 /* Done with values from previous mode; copy in new ones */
4594 gameMode = newGameMode;
4596 ics_gamenum = gamenum;
4597 if (gamenum == gs_gamenum) {
4598 int klen = strlen(gs_kind);
4599 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4600 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4601 gameInfo.event = StrSave(str);
4603 gameInfo.event = StrSave("ICS game");
4605 gameInfo.site = StrSave(appData.icsHost);
4606 gameInfo.date = PGNDate();
4607 gameInfo.round = StrSave("-");
4608 gameInfo.white = StrSave(white);
4609 gameInfo.black = StrSave(black);
4610 timeControl = basetime * 60 * 1000;
4612 timeIncrement = increment * 1000;
4613 movesPerSession = 0;
4614 gameInfo.timeControl = TimeControlTagValue();
4615 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4616 if (appData.debugMode) {
4617 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4618 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4619 setbuf(debugFP, NULL);
4622 gameInfo.outOfBook = NULL;
4624 /* Do we have the ratings? */
4625 if (strcmp(player1Name, white) == 0 &&
4626 strcmp(player2Name, black) == 0) {
4627 if (appData.debugMode)
4628 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4629 player1Rating, player2Rating);
4630 gameInfo.whiteRating = player1Rating;
4631 gameInfo.blackRating = player2Rating;
4632 } else if (strcmp(player2Name, white) == 0 &&
4633 strcmp(player1Name, black) == 0) {
4634 if (appData.debugMode)
4635 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4636 player2Rating, player1Rating);
4637 gameInfo.whiteRating = player2Rating;
4638 gameInfo.blackRating = player1Rating;
4640 player1Name[0] = player2Name[0] = NULLCHAR;
4642 /* Silence shouts if requested */
4643 if (appData.quietPlay &&
4644 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4645 SendToICS(ics_prefix);
4646 SendToICS("set shout 0\n");
4650 /* Deal with midgame name changes */
4652 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4653 if (gameInfo.white) free(gameInfo.white);
4654 gameInfo.white = StrSave(white);
4656 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4657 if (gameInfo.black) free(gameInfo.black);
4658 gameInfo.black = StrSave(black);
4662 /* Throw away game result if anything actually changes in examine mode */
4663 if (gameMode == IcsExamining && !newGame) {
4664 gameInfo.result = GameUnfinished;
4665 if (gameInfo.resultDetails != NULL) {
4666 free(gameInfo.resultDetails);
4667 gameInfo.resultDetails = NULL;
4671 /* In pausing && IcsExamining mode, we ignore boards coming
4672 in if they are in a different variation than we are. */
4673 if (pauseExamInvalid) return;
4674 if (pausing && gameMode == IcsExamining) {
4675 if (moveNum <= pauseExamForwardMostMove) {
4676 pauseExamInvalid = TRUE;
4677 forwardMostMove = pauseExamForwardMostMove;
4682 if (appData.debugMode) {
4683 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4685 /* Parse the board */
4686 for (k = 0; k < ranks; k++) {
4687 for (j = 0; j < files; j++)
4688 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4689 if(gameInfo.holdingsWidth > 1) {
4690 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4691 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4694 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4695 board[5][BOARD_RGHT+1] = WhiteAngel;
4696 board[6][BOARD_RGHT+1] = WhiteMarshall;
4697 board[1][0] = BlackMarshall;
4698 board[2][0] = BlackAngel;
4699 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4701 CopyBoard(boards[moveNum], board);
4702 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4704 startedFromSetupPosition =
4705 !CompareBoards(board, initialPosition);
4706 if(startedFromSetupPosition)
4707 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4710 /* [HGM] Set castling rights. Take the outermost Rooks,
4711 to make it also work for FRC opening positions. Note that board12
4712 is really defective for later FRC positions, as it has no way to
4713 indicate which Rook can castle if they are on the same side of King.
4714 For the initial position we grant rights to the outermost Rooks,
4715 and remember thos rights, and we then copy them on positions
4716 later in an FRC game. This means WB might not recognize castlings with
4717 Rooks that have moved back to their original position as illegal,
4718 but in ICS mode that is not its job anyway.
4720 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4721 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4723 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4724 if(board[0][i] == WhiteRook) j = i;
4725 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4726 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4727 if(board[0][i] == WhiteRook) j = i;
4728 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4729 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4730 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4731 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4732 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4733 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4734 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4736 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4737 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4738 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4739 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4740 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4741 if(board[BOARD_HEIGHT-1][k] == bKing)
4742 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4743 if(gameInfo.variant == VariantTwoKings) {
4744 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4745 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4746 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4749 r = boards[moveNum][CASTLING][0] = initialRights[0];
4750 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4751 r = boards[moveNum][CASTLING][1] = initialRights[1];
4752 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4753 r = boards[moveNum][CASTLING][3] = initialRights[3];
4754 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4755 r = boards[moveNum][CASTLING][4] = initialRights[4];
4756 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4757 /* wildcastle kludge: always assume King has rights */
4758 r = boards[moveNum][CASTLING][2] = initialRights[2];
4759 r = boards[moveNum][CASTLING][5] = initialRights[5];
4761 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4762 boards[moveNum][EP_STATUS] = EP_NONE;
4763 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4764 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4765 if(double_push != -1) {
4766 int dir = WhiteOnMove(moveNum) ? 1 : -1, last = BOARD_HEIGHT-1;
4767 boards[moveNum][EP_FILE] = // also set new e.p. variables
4768 boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4769 boards[moveNum][EP_RANK] = (last + 3*dir)/2;
4770 boards[moveNum][LAST_TO] = 128*(last + dir) + boards[moveNum][EP_FILE];
4771 } else boards[moveNum][EP_FILE] = boards[moveNum][EP_RANK] = 100;
4774 if (ics_getting_history == H_GOT_REQ_HEADER ||
4775 ics_getting_history == H_GOT_UNREQ_HEADER) {
4776 /* This was an initial position from a move list, not
4777 the current position */
4781 /* Update currentMove and known move number limits */
4782 newMove = newGame || moveNum > forwardMostMove;
4785 forwardMostMove = backwardMostMove = currentMove = moveNum;
4786 if (gameMode == IcsExamining && moveNum == 0) {
4787 /* Workaround for ICS limitation: we are not told the wild
4788 type when starting to examine a game. But if we ask for
4789 the move list, the move list header will tell us */
4790 ics_getting_history = H_REQUESTED;
4791 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4794 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4795 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4797 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4798 /* [HGM] applied this also to an engine that is silently watching */
4799 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4800 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4801 gameInfo.variant == currentlyInitializedVariant) {
4802 takeback = forwardMostMove - moveNum;
4803 for (i = 0; i < takeback; i++) {
4804 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4805 SendToProgram("undo\n", &first);
4810 forwardMostMove = moveNum;
4811 if (!pausing || currentMove > forwardMostMove)
4812 currentMove = forwardMostMove;
4814 /* New part of history that is not contiguous with old part */
4815 if (pausing && gameMode == IcsExamining) {
4816 pauseExamInvalid = TRUE;
4817 forwardMostMove = pauseExamForwardMostMove;
4820 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4822 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4823 // [HGM] when we will receive the move list we now request, it will be
4824 // fed to the engine from the first move on. So if the engine is not
4825 // in the initial position now, bring it there.
4826 InitChessProgram(&first, 0);
4829 ics_getting_history = H_REQUESTED;
4830 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4833 forwardMostMove = backwardMostMove = currentMove = moveNum;
4836 /* Update the clocks */
4837 if (strchr(elapsed_time, '.')) {
4839 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4840 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4842 /* Time is in seconds */
4843 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4844 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4849 if (appData.zippyPlay && newGame &&
4850 gameMode != IcsObserving && gameMode != IcsIdle &&
4851 gameMode != IcsExamining)
4852 ZippyFirstBoard(moveNum, basetime, increment);
4855 /* Put the move on the move list, first converting
4856 to canonical algebraic form. */
4858 if (appData.debugMode) {
4859 int f = forwardMostMove;
4860 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4861 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4862 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4863 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4864 fprintf(debugFP, "moveNum = %d\n", moveNum);
4865 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4866 setbuf(debugFP, NULL);
4868 if (moveNum <= backwardMostMove) {
4869 /* We don't know what the board looked like before
4871 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4872 strcat(parseList[moveNum - 1], " ");
4873 strcat(parseList[moveNum - 1], elapsed_time);
4874 moveList[moveNum - 1][0] = NULLCHAR;
4875 } else if (strcmp(move_str, "none") == 0) {
4876 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4877 /* Again, we don't know what the board looked like;
4878 this is really the start of the game. */
4879 parseList[moveNum - 1][0] = NULLCHAR;
4880 moveList[moveNum - 1][0] = NULLCHAR;
4881 backwardMostMove = moveNum;
4882 startedFromSetupPosition = TRUE;
4883 fromX = fromY = toX = toY = -1;
4885 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4886 // So we parse the long-algebraic move string in stead of the SAN move
4887 int valid; char buf[MSG_SIZ], *prom;
4889 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4890 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4891 // str looks something like "Q/a1-a2"; kill the slash
4893 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4894 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4895 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4896 strcat(buf, prom); // long move lacks promo specification!
4897 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4898 if(appData.debugMode)
4899 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4900 safeStrCpy(move_str, buf, MSG_SIZ);
4902 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4903 &fromX, &fromY, &toX, &toY, &promoChar)
4904 || ParseOneMove(buf, moveNum - 1, &moveType,
4905 &fromX, &fromY, &toX, &toY, &promoChar);
4906 // end of long SAN patch
4908 (void) CoordsToAlgebraic(boards[moveNum - 1],
4909 PosFlags(moveNum - 1),
4910 fromY, fromX, toY, toX, promoChar,
4911 parseList[moveNum-1]);
4912 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4918 if(!IS_SHOGI(gameInfo.variant))
4919 strcat(parseList[moveNum - 1], "+");
4922 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4923 strcat(parseList[moveNum - 1], "#");
4926 strcat(parseList[moveNum - 1], " ");
4927 strcat(parseList[moveNum - 1], elapsed_time);
4928 /* currentMoveString is set as a side-effect of ParseOneMove */
4929 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4930 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4931 strcat(moveList[moveNum - 1], "\n");
4933 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4934 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4935 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4936 ChessSquare old, new = boards[moveNum][k][j];
4937 if(new == EmptySquare || fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4938 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4939 if(old == new) continue;
4940 if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4941 else if(new == WhiteWazir || new == BlackWazir) {
4942 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4943 boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4944 else boards[moveNum][k][j] = old; // preserve type of Gold
4945 } else if(old == WhitePawn || old == BlackPawn) // Pawn promotions (but not e.p.capture!)
4946 boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4949 /* Move from ICS was illegal!? Punt. */
4950 if (appData.debugMode) {
4951 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4952 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4954 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4955 strcat(parseList[moveNum - 1], " ");
4956 strcat(parseList[moveNum - 1], elapsed_time);
4957 moveList[moveNum - 1][0] = NULLCHAR;
4958 fromX = fromY = toX = toY = -1;
4961 if (appData.debugMode) {
4962 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4963 setbuf(debugFP, NULL);
4967 /* Send move to chess program (BEFORE animating it). */
4968 if (appData.zippyPlay && !newGame && newMove &&
4969 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4971 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4972 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4973 if (moveList[moveNum - 1][0] == NULLCHAR) {
4974 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4976 DisplayError(str, 0);
4978 if (first.sendTime) {
4979 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4981 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4982 if (firstMove && !bookHit) {
4984 if (first.useColors) {
4985 SendToProgram(gameMode == IcsPlayingWhite ?
4987 "black\ngo\n", &first);
4989 SendToProgram("go\n", &first);
4991 first.maybeThinking = TRUE;
4994 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4995 if (moveList[moveNum - 1][0] == NULLCHAR) {
4996 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4997 DisplayError(str, 0);
4999 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
5000 SendMoveToProgram(moveNum - 1, &first);
5007 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
5008 /* If move comes from a remote source, animate it. If it
5009 isn't remote, it will have already been animated. */
5010 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
5011 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
5013 if (!pausing && appData.highlightLastMove) {
5014 SetHighlights(fromX, fromY, toX, toY);
5018 /* Start the clocks */
5019 whiteFlag = blackFlag = FALSE;
5020 appData.clockMode = !(basetime == 0 && increment == 0);
5022 ics_clock_paused = TRUE;
5024 } else if (ticking == 1) {
5025 ics_clock_paused = FALSE;
5027 if (gameMode == IcsIdle ||
5028 relation == RELATION_OBSERVING_STATIC ||
5029 relation == RELATION_EXAMINING ||
5031 DisplayBothClocks();
5035 /* Display opponents and material strengths */
5036 if (gameInfo.variant != VariantBughouse &&
5037 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5038 if (tinyLayout || smallLayout) {
5039 if(gameInfo.variant == VariantNormal)
5040 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5041 gameInfo.white, white_stren, gameInfo.black, black_stren,
5042 basetime, increment);
5044 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5045 gameInfo.white, white_stren, gameInfo.black, black_stren,
5046 basetime, increment, (int) gameInfo.variant);
5048 if(gameInfo.variant == VariantNormal)
5049 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5050 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5051 basetime, increment);
5053 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5054 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5055 basetime, increment, VariantName(gameInfo.variant));
5058 if (appData.debugMode) {
5059 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5064 /* Display the board */
5065 if (!pausing && !appData.noGUI) {
5067 if (appData.premove)
5069 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5070 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5071 ClearPremoveHighlights();
5073 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5074 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5075 DrawPosition(j, boards[currentMove]);
5077 DisplayMove(moveNum - 1);
5078 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5079 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5080 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
5081 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5085 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5087 if(bookHit) { // [HGM] book: simulate book reply
5088 static char bookMove[MSG_SIZ]; // a bit generous?
5090 programStats.nodes = programStats.depth = programStats.time =
5091 programStats.score = programStats.got_only_move = 0;
5092 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5094 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5095 strcat(bookMove, bookHit);
5096 HandleMachineMove(bookMove, &first);
5105 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5106 ics_getting_history = H_REQUESTED;
5107 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5113 SendToBoth (char *msg)
5114 { // to make it easy to keep two engines in step in dual analysis
5115 SendToProgram(msg, &first);
5116 if(second.analyzing) SendToProgram(msg, &second);
5120 AnalysisPeriodicEvent (int force)
5122 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5123 && !force) || !appData.periodicUpdates)
5126 /* Send . command to Crafty to collect stats */
5129 /* Don't send another until we get a response (this makes
5130 us stop sending to old Crafty's which don't understand
5131 the "." command (sending illegal cmds resets node count & time,
5132 which looks bad)) */
5133 programStats.ok_to_send = 0;
5137 ics_update_width (int new_width)
5139 ics_printf("set width %d\n", new_width);
5143 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5147 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5148 if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5149 sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5150 SendToProgram(buf, cps);
5153 // null move in variant where engine does not understand it (for analysis purposes)
5154 SendBoard(cps, moveNum + 1); // send position after move in stead.
5157 if (cps->useUsermove) {
5158 SendToProgram("usermove ", cps);
5162 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5163 int len = space - parseList[moveNum];
5164 memcpy(buf, parseList[moveNum], len);
5166 buf[len] = NULLCHAR;
5168 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5170 SendToProgram(buf, cps);
5172 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5173 AlphaRank(moveList[moveNum], 4);
5174 SendToProgram(moveList[moveNum], cps);
5175 AlphaRank(moveList[moveNum], 4); // and back
5177 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5178 * the engine. It would be nice to have a better way to identify castle
5180 if(appData.fischerCastling && cps->useOOCastle) {
5181 int fromX = moveList[moveNum][0] - AAA;
5182 int fromY = moveList[moveNum][1] - ONE;
5183 int toX = moveList[moveNum][2] - AAA;
5184 int toY = moveList[moveNum][3] - ONE;
5185 if((boards[moveNum][fromY][fromX] == WhiteKing
5186 && boards[moveNum][toY][toX] == WhiteRook)
5187 || (boards[moveNum][fromY][fromX] == BlackKing
5188 && boards[moveNum][toY][toX] == BlackRook)) {
5189 if(toX > fromX) SendToProgram("O-O\n", cps);
5190 else SendToProgram("O-O-O\n", cps);
5192 else SendToProgram(moveList[moveNum], cps);
5194 if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5195 char *m = moveList[moveNum];
5197 *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5198 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
5199 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5202 m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5203 else if(*c && m[8] != '\n') { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5204 *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5205 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
5210 m[2], m[3] - '0', c);
5212 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5215 m[2], m[3] - '0', c);
5216 SendToProgram(buf, cps);
5218 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5219 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5220 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5221 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5222 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5224 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5225 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5226 SendToProgram(buf, cps);
5228 else SendToProgram(moveList[moveNum], cps);
5229 /* End of additions by Tord */
5232 /* [HGM] setting up the opening has brought engine in force mode! */
5233 /* Send 'go' if we are in a mode where machine should play. */
5234 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5235 (gameMode == TwoMachinesPlay ||
5237 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5239 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5240 SendToProgram("go\n", cps);
5241 if (appData.debugMode) {
5242 fprintf(debugFP, "(extra)\n");
5245 setboardSpoiledMachineBlack = 0;
5249 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5251 char user_move[MSG_SIZ];
5254 if(gameInfo.variant == VariantSChess && promoChar) {
5255 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5256 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5257 } else suffix[0] = NULLCHAR;
5261 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5262 (int)moveType, fromX, fromY, toX, toY);
5263 DisplayError(user_move + strlen("say "), 0);
5265 case WhiteKingSideCastle:
5266 case BlackKingSideCastle:
5267 case WhiteQueenSideCastleWild:
5268 case BlackQueenSideCastleWild:
5270 case WhiteHSideCastleFR:
5271 case BlackHSideCastleFR:
5273 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5275 case WhiteQueenSideCastle:
5276 case BlackQueenSideCastle:
5277 case WhiteKingSideCastleWild:
5278 case BlackKingSideCastleWild:
5280 case WhiteASideCastleFR:
5281 case BlackASideCastleFR:
5283 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5285 case WhiteNonPromotion:
5286 case BlackNonPromotion:
5287 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5289 case WhitePromotion:
5290 case BlackPromotion:
5291 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5292 gameInfo.variant == VariantMakruk)
5293 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5294 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5295 PieceToChar(WhiteFerz));
5296 else if(gameInfo.variant == VariantGreat)
5297 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5298 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5299 PieceToChar(WhiteMan));
5301 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5302 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5308 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5309 ToUpper(PieceToChar((ChessSquare) fromX)),
5310 AAA + toX, ONE + toY);
5312 case IllegalMove: /* could be a variant we don't quite understand */
5313 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5315 case WhiteCapturesEnPassant:
5316 case BlackCapturesEnPassant:
5317 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5318 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5321 SendToICS(user_move);
5322 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5323 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5328 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5329 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5330 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5331 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5332 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5335 if(gameMode != IcsExamining) { // is this ever not the case?
5336 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5338 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5339 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5340 } else { // on FICS we must first go to general examine mode
5341 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5343 if(gameInfo.variant != VariantNormal) {
5344 // try figure out wild number, as xboard names are not always valid on ICS
5345 for(i=1; i<=36; i++) {
5346 snprintf(buf, MSG_SIZ, "wild/%d", i);
5347 if(StringToVariant(buf) == gameInfo.variant) break;
5349 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5350 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5351 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5352 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5353 SendToICS(ics_prefix);
5355 if(startedFromSetupPosition || backwardMostMove != 0) {
5356 fen = PositionToFEN(backwardMostMove, NULL, 1);
5357 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5358 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5360 } else { // FICS: everything has to set by separate bsetup commands
5361 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5362 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5364 if(!WhiteOnMove(backwardMostMove)) {
5365 SendToICS("bsetup tomove black\n");
5367 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5368 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5370 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5371 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5373 i = boards[backwardMostMove][EP_STATUS];
5374 if(i >= 0) { // set e.p.
5375 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5381 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5382 SendToICS("bsetup done\n"); // switch to normal examining.
5384 for(i = backwardMostMove; i<last; i++) {
5386 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5387 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5388 int len = strlen(moveList[i]);
5389 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5390 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5394 SendToICS(ics_prefix);
5395 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5398 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5402 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5404 if (rf == DROP_RANK) {
5405 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5406 sprintf(move, "%c@%c%c\n",
5407 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5409 if (promoChar == 'x' || promoChar == NULLCHAR) {
5410 sprintf(move, "%c%c%c%c\n",
5411 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5412 if(killX >= 0 && killY >= 0) {
5413 sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5414 if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5417 sprintf(move, "%c%c%c%c%c\n",
5418 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5419 if(killX >= 0 && killY >= 0) {
5420 sprintf(move+4, ";%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5421 if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5428 ProcessICSInitScript (FILE *f)
5432 while (fgets(buf, MSG_SIZ, f)) {
5433 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5440 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5442 static ClickType lastClickType;
5445 PieceInString (char *s, ChessSquare piece)
5447 char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5448 while((p = strchr(s, ID))) {
5449 if(!suffix || p[1] == suffix) return TRUE;
5456 Partner (ChessSquare *p)
5457 { // change piece into promotion partner if one shogi-promotes to the other
5458 ChessSquare partner = promoPartner[*p];
5459 if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5460 if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5468 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5469 static int toggleFlag;
5470 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5471 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5472 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5473 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5474 if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5475 if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5477 if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5478 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5479 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5480 else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5481 else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5482 if(!step) step = -1;
5483 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5484 !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5485 promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it
5486 promoSweep == pawn ||
5487 appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5488 (promoSweep == WhiteLion || promoSweep == BlackLion)));
5490 int victim = boards[currentMove][toY][toX];
5491 boards[currentMove][toY][toX] = promoSweep;
5492 DrawPosition(FALSE, boards[currentMove]);
5493 boards[currentMove][toY][toX] = victim;
5495 ChangeDragPiece(promoSweep);
5499 PromoScroll (int x, int y)
5503 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5504 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5505 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5506 if(!step) return FALSE;
5507 lastX = x; lastY = y;
5508 if((promoSweep < BlackPawn) == flipView) step = -step;
5509 if(step > 0) selectFlag = 1;
5510 if(!selectFlag) Sweep(step);
5515 NextPiece (int step)
5517 ChessSquare piece = boards[currentMove][toY][toX];
5520 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5521 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5522 if(!step) step = -1;
5523 } while(PieceToChar(pieceSweep) == '.');
5524 boards[currentMove][toY][toX] = pieceSweep;
5525 DrawPosition(FALSE, boards[currentMove]);
5526 boards[currentMove][toY][toX] = piece;
5528 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5530 AlphaRank (char *move, int n)
5532 // char *p = move, c; int x, y;
5534 if (appData.debugMode) {
5535 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5539 move[2]>='0' && move[2]<='9' &&
5540 move[3]>='a' && move[3]<='x' ) {
5542 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5543 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5545 if(move[0]>='0' && move[0]<='9' &&
5546 move[1]>='a' && move[1]<='x' &&
5547 move[2]>='0' && move[2]<='9' &&
5548 move[3]>='a' && move[3]<='x' ) {
5549 /* input move, Shogi -> normal */
5550 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5551 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5552 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5553 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5556 move[3]>='0' && move[3]<='9' &&
5557 move[2]>='a' && move[2]<='x' ) {
5559 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5560 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5563 move[0]>='a' && move[0]<='x' &&
5564 move[3]>='0' && move[3]<='9' &&
5565 move[2]>='a' && move[2]<='x' ) {
5566 /* output move, normal -> Shogi */
5567 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5568 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5569 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5570 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5571 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5573 if (appData.debugMode) {
5574 fprintf(debugFP, " out = '%s'\n", move);
5578 char yy_textstr[8000];
5580 /* Parser for moves from gnuchess, ICS, or user typein box */
5582 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5584 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5586 switch (*moveType) {
5587 case WhitePromotion:
5588 case BlackPromotion:
5589 case WhiteNonPromotion:
5590 case BlackNonPromotion:
5593 case WhiteCapturesEnPassant:
5594 case BlackCapturesEnPassant:
5595 case WhiteKingSideCastle:
5596 case WhiteQueenSideCastle:
5597 case BlackKingSideCastle:
5598 case BlackQueenSideCastle:
5599 case WhiteKingSideCastleWild:
5600 case WhiteQueenSideCastleWild:
5601 case BlackKingSideCastleWild:
5602 case BlackQueenSideCastleWild:
5603 /* Code added by Tord: */
5604 case WhiteHSideCastleFR:
5605 case WhiteASideCastleFR:
5606 case BlackHSideCastleFR:
5607 case BlackASideCastleFR:
5608 /* End of code added by Tord */
5609 case IllegalMove: /* bug or odd chess variant */
5610 if(currentMoveString[1] == '@') { // illegal drop
5611 *fromX = WhiteOnMove(moveNum) ?
5612 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5613 (int) CharToPiece(ToLower(currentMoveString[0]));
5616 *fromX = currentMoveString[0] - AAA;
5617 *fromY = currentMoveString[1] - ONE;
5618 *toX = currentMoveString[2] - AAA;
5619 *toY = currentMoveString[3] - ONE;
5620 *promoChar = currentMoveString[4];
5621 if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5622 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5623 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5624 if (appData.debugMode) {
5625 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5627 *fromX = *fromY = *toX = *toY = 0;
5630 if (appData.testLegality) {
5631 return (*moveType != IllegalMove);
5633 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5634 // [HGM] lion: if this is a double move we are less critical
5635 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5640 *fromX = *moveType == WhiteDrop ?
5641 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5642 (int) CharToPiece(ToLower(currentMoveString[0]));
5645 *toX = currentMoveString[2] - AAA;
5646 *toY = currentMoveString[3] - ONE;
5647 *promoChar = NULLCHAR;
5651 case ImpossibleMove:
5661 if (appData.debugMode) {
5662 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5665 *fromX = *fromY = *toX = *toY = 0;
5666 *promoChar = NULLCHAR;
5671 Boolean pushed = FALSE;
5672 char *lastParseAttempt;
5675 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5676 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5677 int fromX, fromY, toX, toY; char promoChar;
5682 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5683 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5684 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5687 endPV = forwardMostMove;
5689 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5690 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5691 lastParseAttempt = pv;
5692 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5693 if(!valid && nr == 0 &&
5694 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5695 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5696 // Hande case where played move is different from leading PV move
5697 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5698 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5699 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5700 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5701 endPV += 2; // if position different, keep this
5702 moveList[endPV-1][0] = fromX + AAA;
5703 moveList[endPV-1][1] = fromY + ONE;
5704 moveList[endPV-1][2] = toX + AAA;
5705 moveList[endPV-1][3] = toY + ONE;
5706 parseList[endPV-1][0] = NULLCHAR;
5707 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5710 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5711 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5712 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5713 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5714 valid++; // allow comments in PV
5718 if(endPV+1 > framePtr) break; // no space, truncate
5721 CopyBoard(boards[endPV], boards[endPV-1]);
5722 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5723 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5724 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5725 CoordsToAlgebraic(boards[endPV - 1],
5726 PosFlags(endPV - 1),
5727 fromY, fromX, toY, toX, promoChar,
5728 parseList[endPV - 1]);
5730 if(atEnd == 2) return; // used hidden, for PV conversion
5731 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5732 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5733 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5734 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5735 DrawPosition(TRUE, boards[currentMove]);
5739 MultiPV (ChessProgramState *cps, int kind)
5740 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5742 for(i=0; i<cps->nrOptions; i++) {
5743 char *s = cps->option[i].name;
5744 if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5745 if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5746 && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5751 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5752 static int multi, pv_margin;
5753 static ChessProgramState *activeCps;
5756 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5758 int startPV, lineStart, origIndex = index;
5759 char *p, buf2[MSG_SIZ];
5760 ChessProgramState *cps = (pane ? &second : &first);
5762 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5763 lastX = x; lastY = y;
5764 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5765 lineStart = startPV = index;
5766 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5767 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5769 do{ while(buf[index] && buf[index] != '\n') index++;
5770 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5772 if(lineStart == 0 && gameMode == AnalyzeMode) {
5774 if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5775 if(n == 0) { // click not on "fewer" or "more"
5776 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5777 pv_margin = cps->option[multi].value;
5778 activeCps = cps; // non-null signals margin adjustment
5780 } else if((multi = MultiPV(cps, 1)) >= 0) {
5781 n += cps->option[multi].value; if(n < 1) n = 1;
5782 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5783 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5784 cps->option[multi].value = n;
5788 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5789 ExcludeClick(origIndex - lineStart);
5791 } else if(!strncmp(buf+lineStart, "dep\t", 4)) { // column headers clicked
5792 Collapse(origIndex - lineStart);
5795 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5796 *start = startPV; *end = index-1;
5797 extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5804 static char buf[10*MSG_SIZ];
5805 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5807 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5808 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5809 for(i = forwardMostMove; i<endPV; i++){
5810 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5811 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5814 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5815 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5816 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5822 LoadPV (int x, int y)
5823 { // called on right mouse click to load PV
5824 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5825 lastX = x; lastY = y;
5826 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5834 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5836 if(pv_margin != activeCps->option[multi].value) {
5838 snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5839 SendToProgram(buf, activeCps);
5840 activeCps->option[multi].value = pv_margin;
5845 if(endPV < 0) return;
5846 if(appData.autoCopyPV) CopyFENToClipboard();
5848 if(extendGame && currentMove > forwardMostMove) {
5849 Boolean saveAnimate = appData.animate;
5851 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5852 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5853 } else storedGames--; // abandon shelved tail of original game
5856 forwardMostMove = currentMove;
5857 currentMove = oldFMM;
5858 appData.animate = FALSE;
5859 ToNrEvent(forwardMostMove);
5860 appData.animate = saveAnimate;
5862 currentMove = forwardMostMove;
5863 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5864 ClearPremoveHighlights();
5865 DrawPosition(TRUE, boards[currentMove]);
5869 MovePV (int x, int y, int h)
5870 { // step through PV based on mouse coordinates (called on mouse move)
5871 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5873 if(activeCps) { // adjusting engine's multi-pv margin
5874 if(x > lastX) pv_margin++; else
5875 if(x < lastX) pv_margin -= (pv_margin > 0);
5878 snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5879 DisplayMessage(buf, "");
5884 // we must somehow check if right button is still down (might be released off board!)
5885 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5886 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5887 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5889 lastX = x; lastY = y;
5891 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5892 if(endPV < 0) return;
5893 if(y < margin) step = 1; else
5894 if(y > h - margin) step = -1;
5895 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5896 currentMove += step;
5897 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5898 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5899 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5900 DrawPosition(FALSE, boards[currentMove]);
5904 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5905 // All positions will have equal probability, but the current method will not provide a unique
5906 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5912 int piecesLeft[(int)BlackPawn];
5913 int seed, nrOfShuffles;
5916 GetPositionNumber ()
5917 { // sets global variable seed
5920 seed = appData.defaultFrcPosition;
5921 if(seed < 0) { // randomize based on time for negative FRC position numbers
5922 for(i=0; i<50; i++) seed += random();
5923 seed = random() ^ random() >> 8 ^ random() << 8;
5924 if(seed<0) seed = -seed;
5929 put (Board board, int pieceType, int rank, int n, int shade)
5930 // put the piece on the (n-1)-th empty squares of the given shade
5934 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5935 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5936 board[rank][i] = (ChessSquare) pieceType;
5937 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5939 piecesLeft[pieceType]--;
5948 AddOnePiece (Board board, int pieceType, int rank, int shade)
5949 // calculate where the next piece goes, (any empty square), and put it there
5953 i = seed % squaresLeft[shade];
5954 nrOfShuffles *= squaresLeft[shade];
5955 seed /= squaresLeft[shade];
5956 put(board, pieceType, rank, i, shade);
5960 AddTwoPieces (Board board, int pieceType, int rank)
5961 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5963 int i, n=squaresLeft[ANY], j=n-1, k;
5965 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5966 i = seed % k; // pick one
5969 while(i >= j) i -= j--;
5970 j = n - 1 - j; i += j;
5971 put(board, pieceType, rank, j, ANY);
5972 put(board, pieceType, rank, i, ANY);
5976 SetUpShuffle (Board board, int number)
5980 GetPositionNumber(); nrOfShuffles = 1;
5982 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5983 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5984 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5986 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5988 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5989 p = (int) board[0][i];
5990 if(p < (int) BlackPawn) piecesLeft[p] ++;
5991 board[0][i] = EmptySquare;
5994 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5995 // shuffles restricted to allow normal castling put KRR first
5996 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5997 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5998 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5999 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
6000 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
6001 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
6002 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
6003 put(board, WhiteRook, 0, 0, ANY);
6004 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
6007 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
6008 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
6009 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
6010 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
6011 while(piecesLeft[p] >= 2) {
6012 AddOnePiece(board, p, 0, LITE);
6013 AddOnePiece(board, p, 0, DARK);
6015 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
6018 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
6019 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
6020 // but we leave King and Rooks for last, to possibly obey FRC restriction
6021 if(p == (int)WhiteRook) continue;
6022 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
6023 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
6026 // now everything is placed, except perhaps King (Unicorn) and Rooks
6028 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
6029 // Last King gets castling rights
6030 while(piecesLeft[(int)WhiteUnicorn]) {
6031 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6032 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
6035 while(piecesLeft[(int)WhiteKing]) {
6036 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6037 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
6042 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
6043 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6046 // Only Rooks can be left; simply place them all
6047 while(piecesLeft[(int)WhiteRook]) {
6048 i = put(board, WhiteRook, 0, 0, ANY);
6049 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6052 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
6054 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
6057 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6058 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6061 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6065 ptclen (const char *s, char *escapes)
6068 if(!*escapes) return strlen(s);
6069 while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6074 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6075 /* [HGM] moved here from winboard.c because of its general usefulness */
6076 /* Basically a safe strcpy that uses the last character as King */
6078 int result = FALSE; int NrPieces;
6079 unsigned char partner[EmptySquare];
6081 if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6082 && NrPieces >= 12 && !(NrPieces&1)) {
6083 int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6085 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6086 for( i=offs=0; i<NrPieces/2-1; i++ ) {
6088 if(map[j] == '/') offs = WhitePBishop - i, j++;
6089 if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6090 table[i+offs] = map[j++];
6091 if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6092 if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6093 if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6095 table[(int) WhiteKing] = map[j++];
6096 for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6098 if(map[j] == '/') offs = WhitePBishop - ii, j++;
6099 i = WHITE_TO_BLACK ii;
6100 if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6101 table[i+offs] = map[j++];
6102 if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6103 if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6104 if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6106 table[(int) BlackKing] = map[j++];
6109 if(*escapes) { // set up promotion pairing
6110 for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6111 // pieceToChar entirely filled, so we can look up specified partners
6112 for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6114 if(c == '^' || c == '-') { // has specified partner
6116 for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6117 if(c == '^') table[i] = '+';
6118 if(p < EmptySquare) {
6119 if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6120 if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6121 promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6123 } else if(c == '*') {
6124 table[i] = partner[i];
6125 promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6137 SetCharTable (unsigned char *table, const char * map)
6139 return SetCharTableEsc(table, map, "");
6143 Prelude (Board board)
6144 { // [HGM] superchess: random selection of exo-pieces
6145 int i, j, k; ChessSquare p;
6146 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6148 GetPositionNumber(); // use FRC position number
6150 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6151 SetCharTable(pieceToChar, appData.pieceToCharTable);
6152 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6153 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6156 j = seed%4; seed /= 4;
6157 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6158 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6159 board[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-1-k][1]++;
6160 j = seed%3 + (seed%3 >= j); seed /= 3;
6161 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6162 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6163 board[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-1-k][1]++;
6164 j = seed%3; seed /= 3;
6165 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6166 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6167 board[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-1-k][1]++;
6168 j = seed%2 + (seed%2 >= j); seed /= 2;
6169 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6170 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6171 board[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-1-k][1]++;
6172 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
6173 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
6174 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6175 put(board, exoPieces[0], 0, 0, ANY);
6176 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6180 InitPosition (int redraw)
6182 ChessSquare (* pieces)[BOARD_FILES];
6183 int i, j, pawnRow=1, pieceRows=1, overrule,
6184 oldx = gameInfo.boardWidth,
6185 oldy = gameInfo.boardHeight,
6186 oldh = gameInfo.holdingsWidth;
6189 if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6191 /* [AS] Initialize pv info list [HGM] and game status */
6193 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6194 pvInfoList[i].depth = 0;
6195 boards[i][EP_STATUS] = EP_NONE;
6196 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6199 initialRulePlies = 0; /* 50-move counter start */
6201 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6202 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6206 /* [HGM] logic here is completely changed. In stead of full positions */
6207 /* the initialized data only consist of the two backranks. The switch */
6208 /* selects which one we will use, which is than copied to the Board */
6209 /* initialPosition, which for the rest is initialized by Pawns and */
6210 /* empty squares. This initial position is then copied to boards[0], */
6211 /* possibly after shuffling, so that it remains available. */
6213 gameInfo.holdingsWidth = 0; /* default board sizes */
6214 gameInfo.boardWidth = 8;
6215 gameInfo.boardHeight = 8;
6216 gameInfo.holdingsSize = 0;
6217 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6218 for(i=0; i<BOARD_FILES-6; i++)
6219 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6220 initialPosition[EP_STATUS] = EP_NONE;
6221 initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6222 SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6223 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6224 SetCharTable(pieceNickName, appData.pieceNickNames);
6225 else SetCharTable(pieceNickName, "............");
6228 switch (gameInfo.variant) {
6229 case VariantFischeRandom:
6230 shuffleOpenings = TRUE;
6231 appData.fischerCastling = TRUE;
6234 case VariantShatranj:
6235 pieces = ShatranjArray;
6236 nrCastlingRights = 0;
6237 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6240 pieces = makrukArray;
6241 nrCastlingRights = 0;
6242 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6245 pieces = aseanArray;
6246 nrCastlingRights = 0;
6247 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6249 case VariantTwoKings:
6250 pieces = twoKingsArray;
6253 pieces = GrandArray;
6254 nrCastlingRights = 0;
6255 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6256 gameInfo.boardWidth = 10;
6257 gameInfo.boardHeight = 10;
6258 gameInfo.holdingsSize = 7;
6260 case VariantCapaRandom:
6261 shuffleOpenings = TRUE;
6262 appData.fischerCastling = TRUE;
6263 case VariantCapablanca:
6264 pieces = CapablancaArray;
6265 gameInfo.boardWidth = 10;
6266 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6269 pieces = GothicArray;
6270 gameInfo.boardWidth = 10;
6271 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6274 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6275 gameInfo.holdingsSize = 7;
6276 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6279 pieces = JanusArray;
6280 gameInfo.boardWidth = 10;
6281 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6282 nrCastlingRights = 6;
6283 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6284 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6285 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6286 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6287 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6288 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6291 pieces = FalconArray;
6292 gameInfo.boardWidth = 10;
6293 SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6295 case VariantXiangqi:
6296 pieces = XiangqiArray;
6297 gameInfo.boardWidth = 9;
6298 gameInfo.boardHeight = 10;
6299 nrCastlingRights = 0;
6300 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6303 pieces = ShogiArray;
6304 gameInfo.boardWidth = 9;
6305 gameInfo.boardHeight = 9;
6306 gameInfo.holdingsSize = 7;
6307 nrCastlingRights = 0;
6308 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6311 pieces = ChuArray; pieceRows = 3;
6312 gameInfo.boardWidth = 12;
6313 gameInfo.boardHeight = 12;
6314 nrCastlingRights = 0;
6315 // SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6316 // "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6317 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"
6318 "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);
6320 case VariantCourier:
6321 pieces = CourierArray;
6322 gameInfo.boardWidth = 12;
6323 nrCastlingRights = 0;
6324 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6326 case VariantKnightmate:
6327 pieces = KnightmateArray;
6328 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6330 case VariantSpartan:
6331 pieces = SpartanArray;
6332 SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6336 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6338 case VariantChuChess:
6339 pieces = ChuChessArray;
6340 gameInfo.boardWidth = 10;
6341 gameInfo.boardHeight = 10;
6342 SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6345 pieces = fairyArray;
6346 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6349 pieces = GreatArray;
6350 gameInfo.boardWidth = 10;
6351 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6352 gameInfo.holdingsSize = 8;
6356 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6357 gameInfo.holdingsSize = 8;
6358 startedFromSetupPosition = TRUE;
6360 case VariantCrazyhouse:
6361 case VariantBughouse:
6363 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6364 gameInfo.holdingsSize = 5;
6366 case VariantWildCastle:
6368 /* !!?shuffle with kings guaranteed to be on d or e file */
6369 shuffleOpenings = 1;
6371 case VariantNoCastle:
6372 /* !!?unconstrained back-rank shuffle */
6373 shuffleOpenings = 1;
6374 case VariantSuicide:
6376 nrCastlingRights = 0;
6381 if(appData.NrFiles >= 0) {
6382 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6383 gameInfo.boardWidth = appData.NrFiles;
6385 if(appData.NrRanks >= 0) {
6386 gameInfo.boardHeight = appData.NrRanks;
6388 if(appData.holdingsSize >= 0) {
6389 i = appData.holdingsSize;
6390 // if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6391 gameInfo.holdingsSize = i;
6393 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6394 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6395 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6397 if(!handSize) handSize = BOARD_HEIGHT;
6398 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6399 if(pawnRow < 1) pawnRow = 1;
6400 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6401 gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6402 if(gameInfo.variant == VariantChu) pawnRow = 3;
6404 /* User pieceToChar list overrules defaults */
6405 if(appData.pieceToCharTable != NULL)
6406 SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6408 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6410 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6411 s = (ChessSquare) 0; /* account holding counts in guard band */
6412 for( i=0; i<BOARD_HEIGHT; i++ )
6413 initialPosition[i][j] = s;
6415 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6416 initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6417 initialPosition[pawnRow][j] = WhitePawn;
6418 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6419 if(gameInfo.variant == VariantXiangqi) {
6421 initialPosition[pawnRow][j] =
6422 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6423 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6424 initialPosition[2][j] = WhiteCannon;
6425 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6429 if(gameInfo.variant == VariantChu) {
6430 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6431 initialPosition[pawnRow+1][j] = WhiteCobra,
6432 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6433 for(i=1; i<pieceRows; i++) {
6434 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6435 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6438 if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6439 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6440 initialPosition[0][j] = WhiteRook;
6441 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6444 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth];
6446 if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6447 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6450 initialPosition[1][j] = WhiteBishop;
6451 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6453 initialPosition[1][j] = WhiteRook;
6454 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6457 if( nrCastlingRights == -1) {
6458 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6459 /* This sets default castling rights from none to normal corners */
6460 /* Variants with other castling rights must set them themselves above */
6461 nrCastlingRights = 6;
6463 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6464 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6465 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6466 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6467 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6468 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6471 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6472 if(gameInfo.variant == VariantGreat) { // promotion commoners
6473 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6474 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6475 initialPosition[handSize-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6476 initialPosition[handSize-1-PieceToNumber(WhiteMan)][1] = 9;
6478 if( gameInfo.variant == VariantSChess ) {
6479 initialPosition[1][0] = BlackMarshall;
6480 initialPosition[2][0] = BlackAngel;
6481 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6482 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6483 initialPosition[1][1] = initialPosition[2][1] =
6484 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6486 initialPosition[CHECK_COUNT] = (gameInfo.variant == Variant3Check ? 0x303 : 0);
6487 if (appData.debugMode) {
6488 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6490 if(shuffleOpenings) {
6491 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6492 startedFromSetupPosition = TRUE;
6494 if(startedFromPositionFile) {
6495 /* [HGM] loadPos: use PositionFile for every new game */
6496 CopyBoard(initialPosition, filePosition);
6497 for(i=0; i<nrCastlingRights; i++)
6498 initialRights[i] = filePosition[CASTLING][i];
6499 startedFromSetupPosition = TRUE;
6501 if(*appData.men) LoadPieceDesc(appData.men);
6503 CopyBoard(boards[0], initialPosition);
6505 if(oldx != gameInfo.boardWidth ||
6506 oldy != gameInfo.boardHeight ||
6507 oldv != gameInfo.variant ||
6508 oldh != gameInfo.holdingsWidth
6510 InitDrawingSizes(-2 ,0);
6512 oldv = gameInfo.variant;
6514 DrawPosition(TRUE, boards[currentMove]);
6518 SendBoard (ChessProgramState *cps, int moveNum)
6520 char message[MSG_SIZ];
6522 if (cps->useSetboard) {
6523 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6524 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6525 SendToProgram(message, cps);
6530 int i, j, left=0, right=BOARD_WIDTH;
6531 /* Kludge to set black to move, avoiding the troublesome and now
6532 * deprecated "black" command.
6534 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6535 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
6537 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6539 SendToProgram("edit\n", cps);
6540 SendToProgram("#\n", cps);
6541 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6542 bp = &boards[moveNum][i][left];
6543 for (j = left; j < right; j++, bp++) {
6544 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6545 if ((int) *bp < (int) BlackPawn) {
6546 if(j == BOARD_RGHT+1)
6547 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6548 else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6549 if(message[0] == '+' || message[0] == '~') {
6550 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6551 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6552 AAA + j, ONE + i - '0');
6554 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6555 message[1] = BOARD_RGHT - 1 - j + '1';
6556 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6558 SendToProgram(message, cps);
6563 SendToProgram("c\n", cps);
6564 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6565 bp = &boards[moveNum][i][left];
6566 for (j = left; j < right; j++, bp++) {
6567 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6568 if (((int) *bp != (int) EmptySquare)
6569 && ((int) *bp >= (int) BlackPawn)) {
6570 if(j == BOARD_LEFT-2)
6571 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6572 else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6573 AAA + j, ONE + i - '0');
6574 if(message[0] == '+' || message[0] == '~') {
6575 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6576 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6577 AAA + j, ONE + i - '0');
6579 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6580 message[1] = BOARD_RGHT - 1 - j + '1';
6581 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6583 SendToProgram(message, cps);
6588 SendToProgram(".\n", cps);
6590 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6593 char exclusionHeader[MSG_SIZ];
6594 int exCnt, excludePtr;
6595 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6596 static Exclusion excluTab[200];
6597 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6603 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6604 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6610 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6611 excludePtr = 24; exCnt = 0;
6616 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6617 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6618 char buf[2*MOVE_LEN], *p;
6619 Exclusion *e = excluTab;
6621 for(i=0; i<exCnt; i++)
6622 if(e[i].ff == fromX && e[i].fr == fromY &&
6623 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6624 if(i == exCnt) { // was not in exclude list; add it
6625 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6626 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6627 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6630 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6631 excludePtr++; e[i].mark = excludePtr++;
6632 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6635 exclusionHeader[e[i].mark] = state;
6639 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6640 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6644 if((signed char)promoChar == -1) { // kludge to indicate best move
6645 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6646 return 1; // if unparsable, abort
6648 // update exclusion map (resolving toggle by consulting existing state)
6649 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6651 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6652 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6653 excludeMap[k] |= 1<<j;
6654 else excludeMap[k] &= ~(1<<j);
6656 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6658 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6659 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6661 return (state == '+');
6665 ExcludeClick (int index)
6668 Exclusion *e = excluTab;
6669 if(index < 25) { // none, best or tail clicked
6670 if(index < 13) { // none: include all
6671 WriteMap(0); // clear map
6672 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6673 SendToBoth("include all\n"); // and inform engine
6674 } else if(index > 18) { // tail
6675 if(exclusionHeader[19] == '-') { // tail was excluded
6676 SendToBoth("include all\n");
6677 WriteMap(0); // clear map completely
6678 // now re-exclude selected moves
6679 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6680 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6681 } else { // tail was included or in mixed state
6682 SendToBoth("exclude all\n");
6683 WriteMap(0xFF); // fill map completely
6684 // now re-include selected moves
6685 j = 0; // count them
6686 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6687 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6688 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6691 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6694 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6695 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6696 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6703 DefaultPromoChoice (int white)
6706 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6707 gameInfo.variant == VariantMakruk)
6708 result = WhiteFerz; // no choice
6709 else if(gameInfo.variant == VariantASEAN)
6710 result = WhiteRook; // no choice
6711 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6712 result= WhiteKing; // in Suicide Q is the last thing we want
6713 else if(gameInfo.variant == VariantSpartan)
6714 result = white ? WhiteQueen : WhiteAngel;
6715 else result = WhiteQueen;
6716 if(!white) result = WHITE_TO_BLACK result;
6720 static int autoQueen; // [HGM] oneclick
6723 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6725 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6726 /* [HGM] add Shogi promotions */
6727 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6728 ChessSquare piece, partner;
6732 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6733 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6735 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6736 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6739 if(legal[toY][toX] == 4) return FALSE;
6741 piece = boards[currentMove][fromY][fromX];
6742 if(gameInfo.variant == VariantChu) {
6743 promotionZoneSize = (BOARD_HEIGHT - deadRanks)/3;
6744 if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6745 highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6746 } else if(gameInfo.variant == VariantShogi) {
6747 promotionZoneSize = (BOARD_HEIGHT- deadRanks)/3 +(BOARD_HEIGHT == 8);
6748 highestPromotingPiece = (int)WhiteAlfil;
6749 if(PieceToChar(piece) != '+' || PieceToChar(CHUPROMOTED(piece)) == '+') highestPromotingPiece = piece;
6750 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6751 promotionZoneSize = 3;
6754 // Treat Lance as Pawn when it is not representing Amazon or Lance
6755 if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6756 if(piece == WhiteLance) piece = WhitePawn; else
6757 if(piece == BlackLance) piece = BlackPawn;
6760 // next weed out all moves that do not touch the promotion zone at all
6761 if((int)piece >= BlackPawn) {
6762 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6764 if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6765 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6767 if( toY < BOARD_HEIGHT - deadRanks - promotionZoneSize &&
6768 fromY < BOARD_HEIGHT - deadRanks - promotionZoneSize) return FALSE;
6769 if(fromY >= BOARD_HEIGHT - deadRanks - promotionZoneSize && gameInfo.variant == VariantChuChess)
6773 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6775 // weed out mandatory Shogi promotions
6776 if(gameInfo.variant == VariantShogi) {
6777 if(piece >= BlackPawn) {
6778 if(toY == 0 && piece == BlackPawn ||
6779 toY == 0 && piece == BlackQueen ||
6780 toY <= 1 && piece == BlackKnight) {
6785 if(toY == BOARD_HEIGHT-deadRanks-1 && piece == WhitePawn ||
6786 toY == BOARD_HEIGHT-deadRanks-1 && piece == WhiteQueen ||
6787 toY >= BOARD_HEIGHT-deadRanks-2 && piece == WhiteKnight) {
6794 // weed out obviously illegal Pawn moves
6795 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6796 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6797 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6798 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6799 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6800 // note we are not allowed to test for valid (non-)capture, due to premove
6803 // we either have a choice what to promote to, or (in Shogi) whether to promote
6804 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6805 gameInfo.variant == VariantMakruk) {
6806 ChessSquare p=BlackFerz; // no choice
6807 while(p < EmptySquare) { //but make sure we use piece that exists
6808 *promoChoice = PieceToChar(p++);
6809 if(*promoChoice != '.') break;
6811 if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6813 // no sense asking what we must promote to if it is going to explode...
6814 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6815 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6818 // give caller the default choice even if we will not make it
6819 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6820 partner = piece; // pieces can promote if the pieceToCharTable says so
6821 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6822 else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6823 if( sweepSelect && gameInfo.variant != VariantGreat
6824 && gameInfo.variant != VariantGrand
6825 && gameInfo.variant != VariantSuper) return FALSE;
6826 if(autoQueen) return FALSE; // predetermined
6828 // suppress promotion popup on illegal moves that are not premoves
6829 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6830 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6831 if(appData.testLegality && !premove) {
6832 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6833 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6834 if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6835 if(moveType != WhitePromotion && moveType != BlackPromotion)
6843 InPalace (int row, int column)
6844 { /* [HGM] for Xiangqi */
6845 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6846 column < (BOARD_WIDTH + 4)/2 &&
6847 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6852 PieceForSquare (int x, int y)
6854 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) return -1;
6855 if(x == BOARD_RGHT+1 && handOffsets & 1) y += handSize - BOARD_HEIGHT;
6856 if(x == BOARD_LEFT-2 && !(handOffsets & 2)) y += handSize - BOARD_HEIGHT;
6857 return boards[currentMove][y][x];
6861 More (Board board, int col, int start, int end)
6864 for(k=start; k<end; k++) if(board[k][col]) return (col == 1 ? WhiteMonarch : BlackMonarch); // arrow image
6869 DrawPosition (int repaint, Board board)
6871 Board compactedBoard;
6872 if(handSize > BOARD_HEIGHT && board) {
6874 CopyBoard(compactedBoard, board);
6875 if(handOffsets & 1) {
6876 for(k=0; k<BOARD_HEIGHT; k++) {
6877 compactedBoard[k][BOARD_WIDTH-1] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-1];
6878 compactedBoard[k][BOARD_WIDTH-2] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-2];
6880 compactedBoard[0][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, 0, handSize-BOARD_HEIGHT+1);
6881 } else compactedBoard[BOARD_HEIGHT-1][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, BOARD_HEIGHT-1, handSize);
6882 if(!(handOffsets & 2)) {
6883 for(k=0; k<BOARD_HEIGHT; k++) {
6884 compactedBoard[k][0] = board[k+handSize-BOARD_HEIGHT][0];
6885 compactedBoard[k][1] = board[k+handSize-BOARD_HEIGHT][1];
6887 compactedBoard[0][0] = More(board, 1, 0, handSize-BOARD_HEIGHT+1);
6888 } else compactedBoard[BOARD_HEIGHT-1][0] = More(board, 1, BOARD_HEIGHT-1, handSize);
6889 DrawPositionX(TRUE, compactedBoard);
6890 } else DrawPositionX(repaint, board);
6894 OKToStartUserMove (int x, int y)
6896 ChessSquare from_piece;
6899 if (matchMode) return FALSE;
6900 if (gameMode == EditPosition) return TRUE;
6902 if (x >= 0 && y >= 0)
6903 from_piece = boards[currentMove][y][x];
6905 from_piece = EmptySquare;
6907 if (from_piece == EmptySquare) return FALSE;
6909 white_piece = (int)from_piece >= (int)WhitePawn &&
6910 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6914 case TwoMachinesPlay:
6922 case MachinePlaysWhite:
6923 case IcsPlayingBlack:
6924 if (appData.zippyPlay) return FALSE;
6926 DisplayMoveError(_("You are playing Black"));
6931 case MachinePlaysBlack:
6932 case IcsPlayingWhite:
6933 if (appData.zippyPlay) return FALSE;
6935 DisplayMoveError(_("You are playing White"));
6940 case PlayFromGameFile:
6941 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6944 if (!white_piece && WhiteOnMove(currentMove)) {
6945 DisplayMoveError(_("It is White's turn"));
6948 if (white_piece && !WhiteOnMove(currentMove)) {
6949 DisplayMoveError(_("It is Black's turn"));
6952 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6953 /* Editing correspondence game history */
6954 /* Could disallow this or prompt for confirmation */
6959 case BeginningOfGame:
6960 if (appData.icsActive) return FALSE;
6961 if (!appData.noChessProgram) {
6963 DisplayMoveError(_("You are playing White"));
6970 if (!white_piece && WhiteOnMove(currentMove)) {
6971 DisplayMoveError(_("It is White's turn"));
6974 if (white_piece && !WhiteOnMove(currentMove)) {
6975 DisplayMoveError(_("It is Black's turn"));
6984 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6985 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6986 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6987 && gameMode != AnalyzeFile && gameMode != Training) {
6988 DisplayMoveError(_("Displayed position is not current"));
6995 OnlyMove (int *x, int *y, Boolean captures)
6997 DisambiguateClosure cl;
6998 if (appData.zippyPlay || !appData.testLegality) return FALSE;
7000 case MachinePlaysBlack:
7001 case IcsPlayingWhite:
7002 case BeginningOfGame:
7003 if(!WhiteOnMove(currentMove)) return FALSE;
7005 case MachinePlaysWhite:
7006 case IcsPlayingBlack:
7007 if(WhiteOnMove(currentMove)) return FALSE;
7014 cl.pieceIn = EmptySquare;
7019 cl.promoCharIn = NULLCHAR;
7020 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
7021 if( cl.kind == NormalMove ||
7022 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7023 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7024 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7031 if(cl.kind != ImpossibleMove) return FALSE;
7032 cl.pieceIn = EmptySquare;
7037 cl.promoCharIn = NULLCHAR;
7038 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
7039 if( cl.kind == NormalMove ||
7040 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7041 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7042 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7047 autoQueen = TRUE; // act as if autoQueen on when we click to-square
7053 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
7054 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
7055 int lastLoadGameUseList = FALSE;
7056 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
7057 ChessMove lastLoadGameStart = EndOfFile;
7059 Boolean addToBookFlag;
7060 static Board rightsBoard, nullBoard;
7063 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
7067 int ff=fromX, rf=fromY, ft=toX, rt=toY;
7069 /* Check if the user is playing in turn. This is complicated because we
7070 let the user "pick up" a piece before it is his turn. So the piece he
7071 tried to pick up may have been captured by the time he puts it down!
7072 Therefore we use the color the user is supposed to be playing in this
7073 test, not the color of the piece that is currently on the starting
7074 square---except in EditGame mode, where the user is playing both
7075 sides; fortunately there the capture race can't happen. (It can
7076 now happen in IcsExamining mode, but that's just too bad. The user
7077 will get a somewhat confusing message in that case.)
7082 case TwoMachinesPlay:
7086 /* We switched into a game mode where moves are not accepted,
7087 perhaps while the mouse button was down. */
7090 case MachinePlaysWhite:
7091 /* User is moving for Black */
7092 if (WhiteOnMove(currentMove)) {
7093 DisplayMoveError(_("It is White's turn"));
7098 case MachinePlaysBlack:
7099 /* User is moving for White */
7100 if (!WhiteOnMove(currentMove)) {
7101 DisplayMoveError(_("It is Black's turn"));
7106 case PlayFromGameFile:
7107 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7110 case BeginningOfGame:
7113 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7114 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7115 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7116 /* User is moving for Black */
7117 if (WhiteOnMove(currentMove)) {
7118 DisplayMoveError(_("It is White's turn"));
7122 /* User is moving for White */
7123 if (!WhiteOnMove(currentMove)) {
7124 DisplayMoveError(_("It is Black's turn"));
7130 case IcsPlayingBlack:
7131 /* User is moving for Black */
7132 if (WhiteOnMove(currentMove)) {
7133 if (!appData.premove) {
7134 DisplayMoveError(_("It is White's turn"));
7135 } else if (toX >= 0 && toY >= 0) {
7138 premoveFromX = fromX;
7139 premoveFromY = fromY;
7140 premovePromoChar = promoChar;
7142 if (appData.debugMode)
7143 fprintf(debugFP, "Got premove: fromX %d,"
7144 "fromY %d, toX %d, toY %d\n",
7145 fromX, fromY, toX, toY);
7147 DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7152 case IcsPlayingWhite:
7153 /* User is moving for White */
7154 if (!WhiteOnMove(currentMove)) {
7155 if (!appData.premove) {
7156 DisplayMoveError(_("It is Black's turn"));
7157 } else if (toX >= 0 && toY >= 0) {
7160 premoveFromX = fromX;
7161 premoveFromY = fromY;
7162 premovePromoChar = promoChar;
7164 if (appData.debugMode)
7165 fprintf(debugFP, "Got premove: fromX %d,"
7166 "fromY %d, toX %d, toY %d\n",
7167 fromX, fromY, toX, toY);
7169 DrawPosition(TRUE, boards[currentMove]);
7178 /* EditPosition, empty square, or different color piece;
7179 click-click move is possible */
7180 if (toX == -2 || toY == -2) {
7181 boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7182 DrawPosition(FALSE, boards[currentMove]);
7184 } else if (toX >= 0 && toY >= 0) {
7185 if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7186 ChessSquare p = boards[0][rf][ff];
7187 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7188 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7189 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7190 int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7191 DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7194 } else rightsBoard[toY][toX] = 0; // revoke rights on moving
7195 boards[0][toY][toX] = boards[0][fromY][fromX];
7196 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7197 if(boards[0][fromY][0] != EmptySquare) {
7198 if(boards[0][fromY][1]) boards[0][fromY][1]--;
7199 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
7202 if(fromX == BOARD_RGHT+1) {
7203 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7204 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7205 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7208 boards[0][fromY][fromX] = gatingPiece;
7210 DrawPosition(FALSE, boards[currentMove]);
7216 if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7217 pup = boards[currentMove][toY][toX];
7219 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7220 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7221 if( pup != EmptySquare ) return;
7222 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7223 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7224 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7225 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7226 if(fromX == 0) fromY = handSize-1 - fromY; // black holdings upside-down
7227 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7228 while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7232 /* [HGM] always test for legality, to get promotion info */
7233 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7234 fromY, fromX, toY, toX, promoChar);
7236 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7238 if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7240 /* [HGM] but possibly ignore an IllegalMove result */
7241 if (appData.testLegality) {
7242 if (moveType == IllegalMove || moveType == ImpossibleMove) {
7243 DisplayMoveError(_("Illegal move"));
7248 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7249 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7250 ClearPremoveHighlights(); // was included
7251 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
7252 DrawPosition(FALSE, NULL);
7256 if(addToBookFlag) { // adding moves to book
7257 char buf[MSG_SIZ], move[MSG_SIZ];
7258 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7259 if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7260 killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7261 snprintf(buf, MSG_SIZ, " 0.0%% 1 %s\n", move);
7263 addToBookFlag = FALSE;
7268 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7271 /* Common tail of UserMoveEvent and DropMenuEvent */
7273 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7277 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7278 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7279 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7280 if(WhiteOnMove(currentMove)) {
7281 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7283 if(!boards[currentMove][handSize-1-k][1]) return 0;
7287 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7288 move type in caller when we know the move is a legal promotion */
7289 if(moveType == NormalMove && promoChar)
7290 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7292 /* [HGM] <popupFix> The following if has been moved here from
7293 UserMoveEvent(). Because it seemed to belong here (why not allow
7294 piece drops in training games?), and because it can only be
7295 performed after it is known to what we promote. */
7296 if (gameMode == Training) {
7297 /* compare the move played on the board to the next move in the
7298 * game. If they match, display the move and the opponent's response.
7299 * If they don't match, display an error message.
7303 CopyBoard(testBoard, boards[currentMove]);
7304 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7306 if (CompareBoards(testBoard, boards[currentMove+1])) {
7307 ForwardInner(currentMove+1);
7309 /* Autoplay the opponent's response.
7310 * if appData.animate was TRUE when Training mode was entered,
7311 * the response will be animated.
7313 saveAnimate = appData.animate;
7314 appData.animate = animateTraining;
7315 ForwardInner(currentMove+1);
7316 appData.animate = saveAnimate;
7318 /* check for the end of the game */
7319 if (currentMove >= forwardMostMove) {
7320 gameMode = PlayFromGameFile;
7322 SetTrainingModeOff();
7323 DisplayInformation(_("End of game"));
7326 DisplayError(_("Incorrect move"), 0);
7331 /* Ok, now we know that the move is good, so we can kill
7332 the previous line in Analysis Mode */
7333 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7334 && currentMove < forwardMostMove) {
7335 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7336 else forwardMostMove = currentMove;
7341 /* If we need the chess program but it's dead, restart it */
7342 ResurrectChessProgram();
7344 /* A user move restarts a paused game*/
7348 thinkOutput[0] = NULLCHAR;
7350 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7352 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7353 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7357 if (gameMode == BeginningOfGame) {
7358 if (appData.noChessProgram) {
7359 gameMode = EditGame;
7363 gameMode = MachinePlaysBlack;
7366 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7368 if (first.sendName) {
7369 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7370 SendToProgram(buf, &first);
7377 /* Relay move to ICS or chess engine */
7378 if (appData.icsActive) {
7379 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7380 gameMode == IcsExamining) {
7381 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7382 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7384 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7386 // also send plain move, in case ICS does not understand atomic claims
7387 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7391 if (first.sendTime && (gameMode == BeginningOfGame ||
7392 gameMode == MachinePlaysWhite ||
7393 gameMode == MachinePlaysBlack)) {
7394 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7396 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7397 // [HGM] book: if program might be playing, let it use book
7398 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7399 first.maybeThinking = TRUE;
7400 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7401 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7402 SendBoard(&first, currentMove+1);
7403 if(second.analyzing) {
7404 if(!second.useSetboard) SendToProgram("undo\n", &second);
7405 SendBoard(&second, currentMove+1);
7408 SendMoveToProgram(forwardMostMove-1, &first);
7409 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7411 if (currentMove == cmailOldMove + 1) {
7412 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7416 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7420 if(appData.testLegality)
7421 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7427 if (WhiteOnMove(currentMove)) {
7428 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7430 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7434 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7439 case MachinePlaysBlack:
7440 case MachinePlaysWhite:
7441 /* disable certain menu options while machine is thinking */
7442 SetMachineThinkingEnables();
7449 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7450 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7452 if(bookHit) { // [HGM] book: simulate book reply
7453 static char bookMove[MSG_SIZ]; // a bit generous?
7455 programStats.nodes = programStats.depth = programStats.time =
7456 programStats.score = programStats.got_only_move = 0;
7457 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7459 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7460 strcat(bookMove, bookHit);
7461 HandleMachineMove(bookMove, &first);
7467 MarkByFEN(char *fen)
7470 if(!appData.markers || !appData.highlightDragging) return;
7471 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7472 r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7475 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7476 if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7477 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7478 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7479 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7480 if(*fen == 'T') marker[r][f++] = 0; else
7481 if(*fen == 'Y') marker[r][f++] = 1; else
7482 if(*fen == 'G') marker[r][f++] = 3; else
7483 if(*fen == 'B') marker[r][f++] = 4; else
7484 if(*fen == 'C') marker[r][f++] = 5; else
7485 if(*fen == 'M') marker[r][f++] = 6; else
7486 if(*fen == 'W') marker[r][f++] = 7; else
7487 if(*fen == 'D') marker[r][f++] = 8; else
7488 if(*fen == 'R') marker[r][f++] = 2; else {
7489 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7492 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7496 DrawPosition(TRUE, NULL);
7499 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7502 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7504 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7505 Markers *m = (Markers *) closure;
7506 if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7507 kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7508 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7509 || kind == WhiteCapturesEnPassant
7510 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7511 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7514 static int hoverSavedValid;
7517 MarkTargetSquares (int clear)
7520 if(clear) { // no reason to ever suppress clearing
7521 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7522 hoverSavedValid = 0;
7523 if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7526 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7527 !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7528 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7529 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7530 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7532 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = legal[y][x] = 0;
7535 DrawPosition(FALSE, NULL);
7539 Explode (Board board, int fromX, int fromY, int toX, int toY)
7541 if(gameInfo.variant == VariantAtomic &&
7542 (board[toY][toX] != EmptySquare || // capture?
7543 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7544 board[fromY][fromX] == BlackPawn )
7546 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7552 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7555 CanPromote (ChessSquare piece, int y)
7557 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7558 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7559 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7560 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7561 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7562 (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7563 gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7564 return (piece == BlackPawn && y <= zone ||
7565 piece == WhitePawn && y >= BOARD_HEIGHT-1-deadRanks-zone ||
7566 piece == BlackLance && y <= zone ||
7567 piece == WhiteLance && y >= BOARD_HEIGHT-1-deadRanks-zone );
7571 HoverEvent (int xPix, int yPix, int x, int y)
7573 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7575 if(!first.highlight) return;
7576 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7577 if(x == oldX && y == oldY) return; // only do something if we enter new square
7578 oldFromX = fromX; oldFromY = fromY;
7579 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7580 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7581 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7582 hoverSavedValid = 1;
7583 } else if(oldX != x || oldY != y) {
7584 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7585 if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7586 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7587 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7588 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7590 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7591 SendToProgram(buf, &first);
7594 // SetHighlights(fromX, fromY, x, y);
7598 void ReportClick(char *action, int x, int y)
7600 char buf[MSG_SIZ]; // Inform engine of what user does
7602 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7603 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7604 legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7605 if(!first.highlight || gameMode == EditPosition) return;
7606 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7607 SendToProgram(buf, &first);
7610 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7611 Boolean deferChoice;
7612 int createX = -1, createY = -1; // square where we last created a piece in EditPosition mode
7615 LeftClick (ClickType clickType, int xPix, int yPix)
7618 static Boolean saveAnimate;
7619 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7620 char promoChoice = NULLCHAR;
7622 static TimeMark lastClickTime, prevClickTime;
7624 if(flashing) return;
7626 if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7627 x = EventToSquare(xPix, BOARD_WIDTH);
7628 y = EventToSquare(yPix, BOARD_HEIGHT);
7629 if (!flipView && y >= 0) {
7630 y = BOARD_HEIGHT - 1 - y;
7632 if (flipView && x >= 0) {
7633 x = BOARD_WIDTH - 1 - x;
7636 // map clicks in offsetted holdings back to true coords (or switch the offset)
7637 if(x == BOARD_RGHT+1) {
7638 if(handOffsets & 1) {
7639 if(y == 0) { handOffsets &= ~1; DrawPosition(TRUE, boards[currentMove]); return; }
7640 y += handSize - BOARD_HEIGHT;
7641 } else if(y == BOARD_HEIGHT-1) { handOffsets |= 1; DrawPosition(TRUE, boards[currentMove]); return; }
7643 if(x == BOARD_LEFT-2) {
7644 if(!(handOffsets & 2)) {
7645 if(y == 0) { handOffsets |= 2; DrawPosition(TRUE, boards[currentMove]); return; }
7646 y += handSize - BOARD_HEIGHT;
7647 } else if(y == BOARD_HEIGHT-1) { handOffsets &= ~2; DrawPosition(TRUE, boards[currentMove]); return; }
7650 if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press &&
7651 (boards[currentMove][y][x] == EmptySquare || x == createX && y == createY) ) {
7653 RightClick(clickType, xPix, yPix, &dummy, &dummy);
7658 createX = createY = -1;
7660 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7662 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7664 if (clickType == Press) ErrorPopDown();
7665 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7667 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7668 defaultPromoChoice = promoSweep;
7669 promoSweep = EmptySquare; // terminate sweep
7670 promoDefaultAltered = TRUE;
7671 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7674 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7675 if(clickType == Release) return; // ignore upclick of click-click destination
7676 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7677 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7678 if(gameInfo.holdingsWidth &&
7679 (WhiteOnMove(currentMove)
7680 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7681 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7682 // click in right holdings, for determining promotion piece
7683 ChessSquare p = boards[currentMove][y][x];
7684 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7685 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7686 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7687 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7692 DrawPosition(FALSE, boards[currentMove]);
7696 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7697 if(clickType == Press
7698 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7699 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7700 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7703 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7704 // could be static click on premove from-square: abort premove
7706 ClearPremoveHighlights();
7709 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7710 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7712 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7713 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7714 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7715 defaultPromoChoice = DefaultPromoChoice(side);
7718 autoQueen = appData.alwaysPromoteToQueen;
7722 gatingPiece = EmptySquare;
7723 if (clickType != Press) {
7724 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7725 DragPieceEnd(xPix, yPix); dragging = 0;
7726 DrawPosition(FALSE, NULL);
7730 doubleClick = FALSE;
7731 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7732 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7734 fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7735 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7736 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7737 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7739 if (OKToStartUserMove(fromX, fromY)) {
7741 ReportClick("lift", x, y);
7742 MarkTargetSquares(0);
7743 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7744 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7745 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7746 promoSweep = defaultPromoChoice;
7747 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7748 Sweep(0); // Pawn that is going to promote: preview promotion piece
7749 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7751 if (appData.highlightDragging) {
7752 SetHighlights(fromX, fromY, -1, -1);
7756 } else fromX = fromY = -1;
7762 if (clickType == Press && gameMode != EditPosition) {
7767 // ignore off-board to clicks
7768 if(y < 0 || x < 0) return;
7770 /* Check if clicking again on the same color piece */
7771 fromP = boards[currentMove][fromY][fromX];
7772 toP = boards[currentMove][y][x];
7773 frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7774 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7775 marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7776 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7777 WhitePawn <= toP && toP <= WhiteKing &&
7778 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7779 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7780 (BlackPawn <= fromP && fromP <= BlackKing &&
7781 BlackPawn <= toP && toP <= BlackKing &&
7782 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7783 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7784 /* Clicked again on same color piece -- changed his mind */
7785 second = (x == fromX && y == fromY);
7786 killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7787 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7788 second = FALSE; // first double-click rather than scond click
7789 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7791 promoDefaultAltered = FALSE;
7792 if(!second) MarkTargetSquares(1);
7793 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7794 if (appData.highlightDragging) {
7795 SetHighlights(x, y, -1, -1);
7799 if (OKToStartUserMove(x, y)) {
7800 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7801 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7802 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7803 gatingPiece = boards[currentMove][fromY][fromX];
7804 else gatingPiece = doubleClick ? fromP : EmptySquare;
7806 fromY = y; dragging = 1;
7807 if(!second) ReportClick("lift", x, y);
7808 MarkTargetSquares(0);
7809 DragPieceBegin(xPix, yPix, FALSE);
7810 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7811 promoSweep = defaultPromoChoice;
7812 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7813 Sweep(0); // Pawn that is going to promote: preview promotion piece
7817 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7820 // ignore clicks on holdings
7821 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7824 if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7825 gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7826 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7830 if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7831 DragPieceEnd(xPix, yPix); dragging = 0;
7833 // a deferred attempt to click-click move an empty square on top of a piece
7834 boards[currentMove][y][x] = EmptySquare;
7836 DrawPosition(FALSE, boards[currentMove]);
7837 fromX = fromY = -1; clearFlag = 0;
7840 if (appData.animateDragging) {
7841 /* Undo animation damage if any */
7842 DrawPosition(FALSE, NULL);
7845 /* Second up/down in same square; just abort move */
7848 gatingPiece = EmptySquare;
7851 ClearPremoveHighlights();
7852 MarkTargetSquares(-1);
7853 DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7855 /* First upclick in same square; start click-click mode */
7856 SetHighlights(x, y, -1, -1);
7863 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7864 fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7865 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7866 DisplayMessage(_("only marked squares are legal"),"");
7867 DrawPosition(TRUE, NULL);
7868 return; // ignore to-click
7871 /* we now have a different from- and (possibly off-board) to-square */
7872 /* Completed move */
7873 if(!sweepSelecting) {
7878 piece = boards[currentMove][fromY][fromX];
7880 saveAnimate = appData.animate;
7881 if (clickType == Press) {
7882 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7883 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7884 // must be Edit Position mode with empty-square selected
7885 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7886 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7889 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7892 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7893 killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7895 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7896 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7897 if(appData.sweepSelect) {
7898 promoSweep = defaultPromoChoice;
7899 if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7900 selectFlag = 0; lastX = xPix; lastY = yPix;
7901 ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7902 saveFlash = appData.flashCount; appData.flashCount = 0;
7903 Sweep(0); // Pawn that is going to promote: preview promotion piece
7905 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7906 MarkTargetSquares(1);
7908 return; // promo popup appears on up-click
7910 /* Finish clickclick move */
7911 if (appData.animate || appData.highlightLastMove) {
7912 SetHighlights(fromX, fromY, toX, toY);
7916 MarkTargetSquares(1);
7917 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7918 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7919 *promoRestrict = 0; appData.flashCount = saveFlash;
7920 if (appData.animate || appData.highlightLastMove) {
7921 SetHighlights(fromX, fromY, toX, toY);
7925 MarkTargetSquares(1);
7928 // [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
7929 /* Finish drag move */
7930 if (appData.highlightLastMove) {
7931 SetHighlights(fromX, fromY, toX, toY);
7936 if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7937 defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7938 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7939 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7940 dragging *= 2; // flag button-less dragging if we are dragging
7941 MarkTargetSquares(1);
7942 if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7944 kill2X = killX; kill2Y = killY;
7945 killX = x; killY = y; // remember this square as intermediate
7946 ReportClick("put", x, y); // and inform engine
7947 ReportClick("lift", x, y);
7948 MarkTargetSquares(0);
7952 DragPieceEnd(xPix, yPix); dragging = 0;
7953 /* Don't animate move and drag both */
7954 appData.animate = FALSE;
7955 MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7958 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7959 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7960 ChessSquare piece = boards[currentMove][fromY][fromX];
7961 if(gameMode == EditPosition && piece != EmptySquare &&
7962 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7965 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7966 n = PieceToNumber(piece - (int)BlackPawn);
7967 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7968 boards[currentMove][handSize-1 - n][0] = piece;
7969 boards[currentMove][handSize-1 - n][1]++;
7971 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7972 n = PieceToNumber(piece);
7973 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7974 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7975 boards[currentMove][n][BOARD_WIDTH-2]++;
7977 boards[currentMove][fromY][fromX] = EmptySquare;
7981 MarkTargetSquares(1);
7982 DrawPosition(TRUE, boards[currentMove]);
7986 // off-board moves should not be highlighted
7987 if(x < 0 || y < 0) {
7989 DrawPosition(FALSE, NULL);
7990 } else ReportClick("put", x, y);
7992 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7995 if(legal[toY][toX] == 2) { // highlight-induced promotion
7996 if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7997 else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7998 } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7999 if(!*promoRestrict) { // but has not done that yet
8000 deferChoice = TRUE; // set up retry for when it does
8001 return; // and wait for that
8003 promoChoice = ToLower(*promoRestrict); // force engine's choice
8004 deferChoice = FALSE;
8007 if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
8008 SetHighlights(fromX, fromY, toX, toY);
8009 MarkTargetSquares(1);
8010 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
8011 // [HGM] super: promotion to captured piece selected from holdings
8012 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
8013 promotionChoice = TRUE;
8014 // kludge follows to temporarily execute move on display, without promoting yet
8015 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
8016 boards[currentMove][toY][toX] = p;
8017 DrawPosition(FALSE, boards[currentMove]);
8018 boards[currentMove][fromY][fromX] = p; // take back, but display stays
8019 boards[currentMove][toY][toX] = q;
8020 DisplayMessage("Click in holdings to choose piece", "");
8023 DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
8024 PromotionPopUp(promoChoice);
8026 int oldMove = currentMove;
8027 flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
8028 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
8029 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
8030 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
8031 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
8032 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
8033 DrawPosition(TRUE, boards[currentMove]);
8034 else DrawPosition(FALSE, NULL);
8038 appData.animate = saveAnimate;
8039 if (appData.animate || appData.animateDragging) {
8040 /* Undo animation damage if needed */
8041 // DrawPosition(FALSE, NULL);
8046 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
8047 { // front-end-free part taken out of PieceMenuPopup
8048 int whichMenu; int xSqr, ySqr;
8050 if(seekGraphUp) { // [HGM] seekgraph
8051 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
8052 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
8056 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
8057 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
8058 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
8059 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
8060 if(action == Press) {
8061 originalFlip = flipView;
8062 flipView = !flipView; // temporarily flip board to see game from partners perspective
8063 DrawPosition(TRUE, partnerBoard);
8064 DisplayMessage(partnerStatus, "");
8066 } else if(action == Release) {
8067 flipView = originalFlip;
8068 DrawPosition(TRUE, boards[currentMove]);
8074 xSqr = EventToSquare(x, BOARD_WIDTH);
8075 ySqr = EventToSquare(y, BOARD_HEIGHT);
8076 if (action == Release) {
8077 if(pieceSweep != EmptySquare) {
8078 EditPositionMenuEvent(pieceSweep, toX, toY);
8079 pieceSweep = EmptySquare;
8080 } else UnLoadPV(); // [HGM] pv
8082 if (action != Press) return -2; // return code to be ignored
8085 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8087 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8088 if (xSqr < 0 || ySqr < 0) return -1;
8089 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8090 if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8091 if(xSqr == createX && ySqr == createY && xSqr != BOARD_LEFT-2 && xSqr != BOARD_RGHT+1) {
8092 ChessSquare p = boards[currentMove][ySqr][xSqr];
8093 do { if(++p == EmptySquare) p = WhitePawn; } while(PieceToChar(p) == '.');
8094 boards[currentMove][ySqr][xSqr] = p; DrawPosition(FALSE, boards[currentMove]);
8097 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
8098 createX = toX = xSqr; createY = toY = ySqr; lastX = x, lastY = y;
8102 if(!appData.icsEngineAnalyze) return -1;
8103 case IcsPlayingWhite:
8104 case IcsPlayingBlack:
8105 if(!appData.zippyPlay) goto noZip;
8108 case MachinePlaysWhite:
8109 case MachinePlaysBlack:
8110 case TwoMachinesPlay: // [HGM] pv: use for showing PV
8111 if (!appData.dropMenu) {
8113 return 2; // flag front-end to grab mouse events
8115 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8116 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8119 if (xSqr < 0 || ySqr < 0) return -1;
8120 if (!appData.dropMenu || appData.testLegality &&
8121 gameInfo.variant != VariantBughouse &&
8122 gameInfo.variant != VariantCrazyhouse) return -1;
8123 whichMenu = 1; // drop menu
8129 if (((*fromX = xSqr) < 0) ||
8130 ((*fromY = ySqr) < 0)) {
8131 *fromX = *fromY = -1;
8135 *fromX = BOARD_WIDTH - 1 - *fromX;
8137 *fromY = BOARD_HEIGHT - 1 - *fromY;
8143 Wheel (int dir, int x, int y)
8145 if(gameMode == EditPosition) {
8146 int xSqr = EventToSquare(x, BOARD_WIDTH);
8147 int ySqr = EventToSquare(y, BOARD_HEIGHT);
8148 if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8149 if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8151 boards[currentMove][ySqr][xSqr] += dir;
8152 if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8153 if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8154 } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8155 DrawPosition(FALSE, boards[currentMove]);
8156 } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8160 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8162 // char * hint = lastHint;
8163 FrontEndProgramStats stats;
8165 stats.which = cps == &first ? 0 : 1;
8166 stats.depth = cpstats->depth;
8167 stats.nodes = cpstats->nodes;
8168 stats.score = cpstats->score;
8169 stats.time = cpstats->time;
8170 stats.pv = cpstats->movelist;
8171 stats.hint = lastHint;
8172 stats.an_move_index = 0;
8173 stats.an_move_count = 0;
8175 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8176 stats.hint = cpstats->move_name;
8177 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8178 stats.an_move_count = cpstats->nr_moves;
8181 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
8183 if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8184 && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8186 SetProgramStats( &stats );
8190 ClearEngineOutputPane (int which)
8192 static FrontEndProgramStats dummyStats;
8193 dummyStats.which = which;
8194 dummyStats.pv = "#";
8195 SetProgramStats( &dummyStats );
8198 #define MAXPLAYERS 500
8201 TourneyStandings (int display)
8203 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8204 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8205 char result, *p, *names[MAXPLAYERS];
8207 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8208 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8209 names[0] = p = strdup(appData.participants);
8210 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8212 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8214 while(result = appData.results[nr]) {
8215 color = Pairing(nr, nPlayers, &w, &b, &dummy);
8216 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8217 wScore = bScore = 0;
8219 case '+': wScore = 2; break;
8220 case '-': bScore = 2; break;
8221 case '=': wScore = bScore = 1; break;
8223 case '*': return strdup("busy"); // tourney not finished
8231 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8232 for(w=0; w<nPlayers; w++) {
8234 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8235 ranking[w] = b; points[w] = bScore; score[b] = -2;
8237 p = malloc(nPlayers*34+1);
8238 for(w=0; w<nPlayers && w<display; w++)
8239 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8245 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8246 { // count all piece types
8248 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8249 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8250 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8253 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8254 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8255 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8256 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8257 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
8258 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8263 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8265 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8266 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8268 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8269 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8270 if(myPawns == 2 && nMine == 3) // KPP
8271 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8272 if(myPawns == 1 && nMine == 2) // KP
8273 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8274 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8275 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8276 if(myPawns) return FALSE;
8277 if(pCnt[WhiteRook+side])
8278 return pCnt[BlackRook-side] ||
8279 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8280 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8281 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8282 if(pCnt[WhiteCannon+side]) {
8283 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8284 return majorDefense || pCnt[BlackAlfil-side] >= 2;
8286 if(pCnt[WhiteKnight+side])
8287 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8292 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8294 VariantClass v = gameInfo.variant;
8296 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8297 if(v == VariantShatranj) return TRUE; // always winnable through baring
8298 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8299 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8301 if(v == VariantXiangqi) {
8302 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8304 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8305 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8306 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8307 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8308 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8309 if(stale) // we have at least one last-rank P plus perhaps C
8310 return majors // KPKX
8311 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8313 return pCnt[WhiteFerz+side] // KCAK
8314 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8315 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8316 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8318 } else if(v == VariantKnightmate) {
8319 if(nMine == 1) return FALSE;
8320 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8321 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8322 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8324 if(nMine == 1) return FALSE; // bare King
8325 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
8326 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8327 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8328 // by now we have King + 1 piece (or multiple Bishops on the same color)
8329 if(pCnt[WhiteKnight+side])
8330 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8331 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8332 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8334 return (pCnt[BlackKnight-side]); // KBKN, KFKN
8335 if(pCnt[WhiteAlfil+side])
8336 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8337 if(pCnt[WhiteWazir+side])
8338 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8345 CompareWithRights (Board b1, Board b2)
8348 if(!CompareBoards(b1, b2)) return FALSE;
8349 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8350 /* compare castling rights */
8351 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8352 rights++; /* King lost rights, while rook still had them */
8353 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8354 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8355 rights++; /* but at least one rook lost them */
8357 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8359 if( b1[CASTLING][5] != NoRights ) {
8360 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8367 Adjudicate (ChessProgramState *cps)
8368 { // [HGM] some adjudications useful with buggy engines
8369 // [HGM] adjudicate: made into separate routine, which now can be called after every move
8370 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8371 // Actually ending the game is now based on the additional internal condition canAdjudicate.
8372 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8373 int k, drop, count = 0; static int bare = 1;
8374 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8375 Boolean canAdjudicate = !appData.icsActive;
8377 // most tests only when we understand the game, i.e. legality-checking on
8378 if( appData.testLegality )
8379 { /* [HGM] Some more adjudications for obstinate engines */
8380 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8381 static int moveCount = 6;
8383 char *reason = NULL;
8385 /* Count what is on board. */
8386 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8388 /* Some material-based adjudications that have to be made before stalemate test */
8389 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8390 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8391 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8392 if(canAdjudicate && appData.checkMates) {
8394 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8395 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8396 "Xboard adjudication: King destroyed", GE_XBOARD );
8401 /* Bare King in Shatranj (loses) or Losers (wins) */
8402 if( nrW == 1 || nrB == 1) {
8403 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8404 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
8405 if(canAdjudicate && appData.checkMates) {
8407 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8408 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8409 "Xboard adjudication: Bare king", GE_XBOARD );
8413 if( gameInfo.variant == VariantShatranj && --bare < 0)
8415 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8416 if(canAdjudicate && appData.checkMates) {
8417 /* but only adjudicate if adjudication enabled */
8419 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8420 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8421 "Xboard adjudication: Bare king", GE_XBOARD );
8428 // don't wait for engine to announce game end if we can judge ourselves
8429 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8431 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8432 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
8433 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8434 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8437 reason = "Xboard adjudication: 3rd check";
8438 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8449 reason = "Xboard adjudication: Stalemate";
8450 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8451 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8452 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8453 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8454 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8455 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8456 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8457 EP_CHECKMATE : EP_WINS);
8458 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8459 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8463 reason = "Xboard adjudication: Checkmate";
8464 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8465 if(gameInfo.variant == VariantShogi) {
8466 if(forwardMostMove > backwardMostMove
8467 && moveList[forwardMostMove-1][1] == '@'
8468 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8469 reason = "XBoard adjudication: pawn-drop mate";
8470 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8476 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8478 result = GameIsDrawn; break;
8480 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8482 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8486 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8488 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8489 GameEnds( result, reason, GE_XBOARD );
8493 /* Next absolutely insufficient mating material. */
8494 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8495 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8496 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8498 /* always flag draws, for judging claims */
8499 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8501 if(canAdjudicate && appData.materialDraws) {
8502 /* but only adjudicate them if adjudication enabled */
8503 if(engineOpponent) {
8504 SendToProgram("force\n", engineOpponent); // suppress reply
8505 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8507 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8512 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8513 if(gameInfo.variant == VariantXiangqi ?
8514 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8516 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8517 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8518 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8519 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8521 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8522 { /* if the first 3 moves do not show a tactical win, declare draw */
8523 if(engineOpponent) {
8524 SendToProgram("force\n", engineOpponent); // suppress reply
8525 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8527 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8530 } else moveCount = 6;
8532 if(gameInfo.variant == VariantMakruk && // Makruk counting rules
8533 (nrW == 1 || nrB == 1 || nr[WhitePawn] + nr[BlackPawn] == 0)) { // which only kick in when pawnless or bare King
8534 int maxcnt, his, mine, c, wom = WhiteOnMove(forwardMostMove);
8535 count = forwardMostMove;
8536 while(count >= backwardMostMove) {
8537 int np = nr[WhitePawn] + nr[BlackPawn];
8538 if(wom) mine = nrW, his = nrB, c = BlackPawn;
8539 else mine = nrB, his = nrW, c = WhitePawn;
8540 if(mine > 1 && np) { count++; break; }
8541 if(mine > 1) maxcnt = 64; else
8542 maxcnt = (nr[WhiteRook+c] > 1 ? 8 : nr[WhiteRook+c] ? 16 : nr[WhiteMan+c] > 1 ? 22 :
8543 nr[WhiteKnight+c] > 1 ? 32 : nr[WhiteMan+c] ? 44 : 64) - his - 1;
8544 while(boards[count][EP_STATUS] != EP_CAPTURE && count > backwardMostMove) count--; // seek previous character
8545 if(count == backwardMostMove) break;
8546 if(forwardMostMove - count >= 2*maxcnt + 1 - (mine == 1)) break;
8547 Count(boards[--count], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8549 if(forwardMostMove - count >= 2*maxcnt + 1 - (mine == 1)) {
8550 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8551 if(canAdjudicate && appData.ruleMoves >= 0) {
8552 GameEnds( GameIsDrawn, "Xboard adjudication: counting rule", GE_XBOARD );
8559 // Repetition draws and 50-move rule can be applied independently of legality testing
8561 /* Check for rep-draws */
8563 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8564 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8565 for(k = forwardMostMove-2;
8566 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8567 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8568 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8571 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8572 /* compare castling rights */
8573 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8574 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8575 rights++; /* King lost rights, while rook still had them */
8576 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8577 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8578 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8579 rights++; /* but at least one rook lost them */
8581 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8582 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8584 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8585 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8586 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8589 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8590 && appData.drawRepeats > 1) {
8591 /* adjudicate after user-specified nr of repeats */
8592 int result = GameIsDrawn;
8593 char *details = "XBoard adjudication: repetition draw";
8594 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8595 // [HGM] xiangqi: check for forbidden perpetuals
8596 int m, ourPerpetual = 1, hisPerpetual = 1;
8597 for(m=forwardMostMove; m>k; m-=2) {
8598 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8599 ourPerpetual = 0; // the current mover did not always check
8600 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8601 hisPerpetual = 0; // the opponent did not always check
8603 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8604 ourPerpetual, hisPerpetual);
8605 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8606 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8607 details = "Xboard adjudication: perpetual checking";
8609 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8610 break; // (or we would have caught him before). Abort repetition-checking loop.
8612 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8613 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8615 details = "Xboard adjudication: repetition";
8617 } else // it must be XQ
8618 // Now check for perpetual chases
8619 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8620 hisPerpetual = PerpetualChase(k, forwardMostMove);
8621 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8622 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8623 static char resdet[MSG_SIZ];
8624 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8626 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8628 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8629 break; // Abort repetition-checking loop.
8631 // if neither of us is checking or chasing all the time, or both are, it is draw
8633 if(engineOpponent) {
8634 SendToProgram("force\n", engineOpponent); // suppress reply
8635 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8637 GameEnds( result, details, GE_XBOARD );
8640 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8641 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8645 /* Now we test for 50-move draws. Determine ply count */
8646 count = forwardMostMove;
8647 /* look for last irreversble move */
8648 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8650 /* if we hit starting position, add initial plies */
8651 if( count == backwardMostMove )
8652 count -= initialRulePlies;
8653 count = forwardMostMove - count;
8654 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8655 // adjust reversible move counter for checks in Xiangqi
8656 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8657 if(i < backwardMostMove) i = backwardMostMove;
8658 while(i <= forwardMostMove) {
8659 lastCheck = inCheck; // check evasion does not count
8660 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8661 if(inCheck || lastCheck) count--; // check does not count
8665 if( count >= 100 && gameInfo.variant != VariantMakruk) // do not accept 50-move claims in Makruk
8666 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8667 /* this is used to judge if draw claims are legal */
8668 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8669 if(engineOpponent) {
8670 SendToProgram("force\n", engineOpponent); // suppress reply
8671 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8673 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8677 /* if draw offer is pending, treat it as a draw claim
8678 * when draw condition present, to allow engines a way to
8679 * claim draws before making their move to avoid a race
8680 * condition occurring after their move
8682 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8684 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8685 p = "Draw claim: 50-move rule";
8686 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8687 p = "Draw claim: 3-fold repetition";
8688 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8689 p = "Draw claim: insufficient mating material";
8690 if( p != NULL && canAdjudicate) {
8691 if(engineOpponent) {
8692 SendToProgram("force\n", engineOpponent); // suppress reply
8693 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8695 GameEnds( GameIsDrawn, p, GE_XBOARD );
8700 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8701 if(engineOpponent) {
8702 SendToProgram("force\n", engineOpponent); // suppress reply
8703 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8705 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8711 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8712 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8713 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8718 int pieces[10], squares[10], cnt=0, r, f, res;
8720 static PPROBE_EGBB probeBB;
8721 if(!appData.testLegality) return 10;
8722 if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8723 if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8724 if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8725 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8726 ChessSquare piece = boards[forwardMostMove][r][f];
8727 int black = (piece >= BlackPawn);
8728 int type = piece - black*BlackPawn;
8729 if(piece == EmptySquare) continue;
8730 if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8731 if(type == WhiteKing) type = WhiteQueen + 1;
8732 type = egbbCode[type];
8733 squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8734 pieces[cnt] = type + black*6;
8735 if(++cnt > 5) return 11;
8737 pieces[cnt] = squares[cnt] = 0;
8739 if(loaded == 2) return 13; // loading failed before
8741 char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8744 loaded = 2; // prepare for failure
8745 if(!path) return 13; // no egbb installed
8746 strncpy(buf, path + 8, MSG_SIZ);
8747 if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8748 snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8749 lib = LoadLibrary(buf);
8750 if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8751 loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8752 probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8753 if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8754 p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8755 loaded = 1; // success!
8757 res = probeBB(forwardMostMove & 1, pieces, squares);
8758 return res > 0 ? 1 : res < 0 ? -1 : 0;
8762 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8763 { // [HGM] book: this routine intercepts moves to simulate book replies
8764 char *bookHit = NULL;
8766 if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8768 snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8769 SendToProgram(buf, cps);
8771 //first determine if the incoming move brings opponent into his book
8772 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8773 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8774 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8775 if(bookHit != NULL && !cps->bookSuspend) {
8776 // make sure opponent is not going to reply after receiving move to book position
8777 SendToProgram("force\n", cps);
8778 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8780 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8781 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8782 // now arrange restart after book miss
8784 // after a book hit we never send 'go', and the code after the call to this routine
8785 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8786 char buf[MSG_SIZ], *move = bookHit;
8788 int fromX, fromY, toX, toY;
8792 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8793 &fromX, &fromY, &toX, &toY, &promoChar)) {
8794 (void) CoordsToAlgebraic(boards[forwardMostMove],
8795 PosFlags(forwardMostMove),
8796 fromY, fromX, toY, toX, promoChar, move);
8798 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8802 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8803 SendToProgram(buf, cps);
8804 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8805 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8806 SendToProgram("go\n", cps);
8807 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8808 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8809 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8810 SendToProgram("go\n", cps);
8811 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8813 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8817 LoadError (char *errmess, ChessProgramState *cps)
8818 { // unloads engine and switches back to -ncp mode if it was first
8819 if(cps->initDone) return FALSE;
8820 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8821 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8824 appData.noChessProgram = TRUE;
8825 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8826 gameMode = BeginningOfGame; ModeHighlight();
8829 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8830 DisplayMessage("", ""); // erase waiting message
8831 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8836 ChessProgramState *savedState;
8838 DeferredBookMove (void)
8840 if(savedState->lastPing != savedState->lastPong)
8841 ScheduleDelayedEvent(DeferredBookMove, 10);
8843 HandleMachineMove(savedMessage, savedState);
8846 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8847 static ChessProgramState *stalledEngine;
8848 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8851 HandleMachineMove (char *message, ChessProgramState *cps)
8853 static char firstLeg[20], legs;
8854 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8855 char realname[MSG_SIZ];
8856 int fromX, fromY, toX, toY;
8858 char promoChar, roar;
8863 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8864 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8865 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8866 DisplayError(_("Invalid pairing from pairing engine"), 0);
8869 pairingReceived = 1;
8871 return; // Skim the pairing messages here.
8874 oldError = cps->userError; cps->userError = 0;
8876 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8878 * Kludge to ignore BEL characters
8880 while (*message == '\007') message++;
8883 * [HGM] engine debug message: ignore lines starting with '#' character
8885 if(cps->debug && *message == '#') return;
8888 * Look for book output
8890 if (cps == &first && bookRequested) {
8891 if (message[0] == '\t' || message[0] == ' ') {
8892 /* Part of the book output is here; append it */
8893 strcat(bookOutput, message);
8894 strcat(bookOutput, " \n");
8896 } else if (bookOutput[0] != NULLCHAR) {
8897 /* All of book output has arrived; display it */
8898 char *p = bookOutput;
8899 while (*p != NULLCHAR) {
8900 if (*p == '\t') *p = ' ';
8903 DisplayInformation(bookOutput);
8904 bookRequested = FALSE;
8905 /* Fall through to parse the current output */
8910 * Look for machine move.
8912 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8913 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8915 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8916 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8917 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8918 stalledEngine = cps;
8919 if(appData.ponderNextMove) { // bring opponent out of ponder
8920 if(gameMode == TwoMachinesPlay) {
8921 if(cps->other->pause)
8922 PauseEngine(cps->other);
8924 SendToProgram("easy\n", cps->other);
8933 /* This method is only useful on engines that support ping */
8934 if(abortEngineThink) {
8935 if (appData.debugMode) {
8936 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8938 SendToProgram("undo\n", cps);
8942 if (cps->lastPing != cps->lastPong) {
8943 /* Extra move from before last new; ignore */
8944 if (appData.debugMode) {
8945 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8952 int machineWhite = FALSE;
8955 case BeginningOfGame:
8956 /* Extra move from before last reset; ignore */
8957 if (appData.debugMode) {
8958 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8965 /* Extra move after we tried to stop. The mode test is
8966 not a reliable way of detecting this problem, but it's
8967 the best we can do on engines that don't support ping.
8969 if (appData.debugMode) {
8970 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8971 cps->which, gameMode);
8973 SendToProgram("undo\n", cps);
8976 case MachinePlaysWhite:
8977 case IcsPlayingWhite:
8978 machineWhite = TRUE;
8981 case MachinePlaysBlack:
8982 case IcsPlayingBlack:
8983 machineWhite = FALSE;
8986 case TwoMachinesPlay:
8987 machineWhite = (cps->twoMachinesColor[0] == 'w');
8990 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8991 if (appData.debugMode) {
8993 "Ignoring move out of turn by %s, gameMode %d"
8994 ", forwardMost %d\n",
8995 cps->which, gameMode, forwardMostMove);
9001 if(cps->alphaRank) AlphaRank(machineMove, 4);
9003 // [HGM] lion: (some very limited) support for Alien protocol
9004 killX = killY = kill2X = kill2Y = -1;
9005 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
9006 if(legs++) return; // middle leg contains only redundant info, ignore (but count it)
9007 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
9010 if(p = strchr(machineMove, ',')) { // we got both legs in one (happens on book move)
9011 char *q = strchr(p+1, ','); // second comma?
9012 safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
9013 if(q) legs = 2, p = q; else legs = 1; // with 3-leg move we clipof first two legs!
9014 safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
9016 if(firstLeg[0]) { // there was a previous leg;
9017 // only support case where same piece makes two step
9018 char buf[20], *p = machineMove+1, *q = buf+1, f;
9019 safeStrCpy(buf, machineMove, 20);
9020 while(isdigit(*q)) q++; // find start of to-square
9021 safeStrCpy(machineMove, firstLeg, 20);
9022 while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
9023 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
9024 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)
9025 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
9026 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
9027 firstLeg[0] = NULLCHAR; legs = 0;
9030 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
9031 &fromX, &fromY, &toX, &toY, &promoChar)) {
9032 /* Machine move could not be parsed; ignore it. */
9033 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
9034 machineMove, _(cps->which));
9035 DisplayMoveError(buf1);
9036 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
9037 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
9038 if (gameMode == TwoMachinesPlay) {
9039 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9045 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
9046 /* So we have to redo legality test with true e.p. status here, */
9047 /* to make sure an illegal e.p. capture does not slip through, */
9048 /* to cause a forfeit on a justified illegal-move complaint */
9049 /* of the opponent. */
9050 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
9052 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
9053 fromY, fromX, toY, toX, promoChar);
9054 if(moveType == IllegalMove) {
9055 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
9056 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
9057 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9060 } else if(!appData.fischerCastling)
9061 /* [HGM] Kludge to handle engines that send FRC-style castling
9062 when they shouldn't (like TSCP-Gothic) */
9064 case WhiteASideCastleFR:
9065 case BlackASideCastleFR:
9067 currentMoveString[2]++;
9069 case WhiteHSideCastleFR:
9070 case BlackHSideCastleFR:
9072 currentMoveString[2]--;
9074 default: ; // nothing to do, but suppresses warning of pedantic compilers
9077 hintRequested = FALSE;
9078 lastHint[0] = NULLCHAR;
9079 bookRequested = FALSE;
9080 /* Program may be pondering now */
9081 cps->maybeThinking = TRUE;
9082 if (cps->sendTime == 2) cps->sendTime = 1;
9083 if (cps->offeredDraw) cps->offeredDraw--;
9085 /* [AS] Save move info*/
9086 pvInfoList[ forwardMostMove ].score = programStats.score;
9087 pvInfoList[ forwardMostMove ].depth = programStats.depth;
9088 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
9090 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
9092 /* Test suites abort the 'game' after one move */
9093 if(*appData.finger) {
9095 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
9096 if(!f) f = fopen(appData.finger, "w");
9097 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
9098 else { DisplayFatalError("Bad output file", errno, 0); return; }
9100 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9103 if(solvingTime >= 0) {
9104 snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
9105 totalTime += solvingTime; first.matchWins++; solvingTime = -1;
9107 snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
9108 if(solvingTime == -2) second.matchWins++;
9110 OutputKibitz(2, buf1);
9111 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9114 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
9115 if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9118 while( count < adjudicateLossPlies ) {
9119 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9122 score = -score; /* Flip score for winning side */
9125 if( score > appData.adjudicateLossThreshold ) {
9132 if( count >= adjudicateLossPlies ) {
9133 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9135 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9136 "Xboard adjudication",
9143 if(Adjudicate(cps)) {
9144 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9145 return; // [HGM] adjudicate: for all automatic game ends
9149 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9151 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9152 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9154 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9156 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9158 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9159 char buf[3*MSG_SIZ];
9161 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9162 programStats.score / 100.,
9164 programStats.time / 100.,
9165 (unsigned int)programStats.nodes,
9166 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9167 programStats.movelist);
9173 /* [AS] Clear stats for next move */
9174 ClearProgramStats();
9175 thinkOutput[0] = NULLCHAR;
9176 hiddenThinkOutputState = 0;
9179 if (gameMode == TwoMachinesPlay) {
9180 /* [HGM] relaying draw offers moved to after reception of move */
9181 /* and interpreting offer as claim if it brings draw condition */
9182 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9183 SendToProgram("draw\n", cps->other);
9185 if (cps->other->sendTime) {
9186 SendTimeRemaining(cps->other,
9187 cps->other->twoMachinesColor[0] == 'w');
9189 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9190 if (firstMove && !bookHit) {
9192 if (cps->other->useColors) {
9193 SendToProgram(cps->other->twoMachinesColor, cps->other);
9195 SendToProgram("go\n", cps->other);
9197 cps->other->maybeThinking = TRUE;
9200 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9202 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9204 if (!pausing && appData.ringBellAfterMoves) {
9205 if(!roar) RingBell();
9209 * Reenable menu items that were disabled while
9210 * machine was thinking
9212 if (gameMode != TwoMachinesPlay)
9213 SetUserThinkingEnables();
9215 // [HGM] book: after book hit opponent has received move and is now in force mode
9216 // force the book reply into it, and then fake that it outputted this move by jumping
9217 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9219 static char bookMove[MSG_SIZ]; // a bit generous?
9221 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9222 strcat(bookMove, bookHit);
9225 programStats.nodes = programStats.depth = programStats.time =
9226 programStats.score = programStats.got_only_move = 0;
9227 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9229 if(cps->lastPing != cps->lastPong) {
9230 savedMessage = message; // args for deferred call
9232 ScheduleDelayedEvent(DeferredBookMove, 10);
9241 /* Set special modes for chess engines. Later something general
9242 * could be added here; for now there is just one kludge feature,
9243 * needed because Crafty 15.10 and earlier don't ignore SIGINT
9244 * when "xboard" is given as an interactive command.
9246 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9247 cps->useSigint = FALSE;
9248 cps->useSigterm = FALSE;
9250 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9251 ParseFeatures(message+8, cps); if(tryNr && tryNr < 3) tryNr = 3;
9252 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9255 if (!strncmp(message, "setup ", 6) &&
9256 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9257 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9258 ) { // [HGM] allow first engine to define opening position
9259 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9260 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9262 if(sscanf(message, "setup (%s", buf) == 1) {
9263 s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9264 ASSIGN(appData.pieceToCharTable, buf);
9266 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9268 while(message[s] && message[s++] != ' ');
9269 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9270 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9271 // if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9272 if(hand > h) handSize = hand; else handSize = h;
9273 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9274 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
9275 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9276 if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9277 startedFromSetupPosition = FALSE;
9280 if(startedFromSetupPosition) return;
9281 ParseFEN(boards[0], &dummy, message+s, FALSE);
9282 DrawPosition(TRUE, boards[0]);
9283 CopyBoard(initialPosition, boards[0]);
9284 startedFromSetupPosition = TRUE;
9287 if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9288 ChessSquare piece = WhitePawn;
9289 char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9290 if(*p == '+') promoted++, ID = *++p;
9291 if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9292 piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9293 if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9294 /* always accept definition of */ && piece != WhiteFalcon && piece != BlackFalcon
9295 /* wild-card pieces. */ && piece != WhiteCobra && piece != BlackCobra
9296 /* For variants we don't have */ && gameInfo.variant != VariantBerolina
9297 /* correct rules for, we cannot */ && gameInfo.variant != VariantCylinder
9298 /* enforce legality on our own! */ && gameInfo.variant != VariantUnknown
9299 && gameInfo.variant != VariantGreat
9300 && gameInfo.variant != VariantFairy ) return;
9301 if(piece < EmptySquare) {
9303 ASSIGN(pieceDesc[piece], buf1);
9304 if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9308 if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9310 LeftClick(Press, 0, 0); // finish the click that was interrupted
9311 } else if(promoSweep != EmptySquare) {
9312 promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9313 if(strlen(promoRestrict) > 1) Sweep(0);
9317 /* [HGM] Allow engine to set up a position. Don't ask me why one would
9318 * want this, I was asked to put it in, and obliged.
9320 if (!strncmp(message, "setboard ", 9)) {
9321 Board initial_position;
9323 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9325 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9326 DisplayError(_("Bad FEN received from engine"), 0);
9330 CopyBoard(boards[0], initial_position);
9331 initialRulePlies = FENrulePlies;
9332 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9333 else gameMode = MachinePlaysBlack;
9334 DrawPosition(FALSE, boards[currentMove]);
9340 * Look for communication commands
9342 if (!strncmp(message, "telluser ", 9)) {
9343 if(message[9] == '\\' && message[10] == '\\')
9344 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9346 DisplayNote(message + 9);
9349 if (!strncmp(message, "tellusererror ", 14)) {
9351 if(message[14] == '\\' && message[15] == '\\')
9352 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9354 DisplayError(message + 14, 0);
9357 if (!strncmp(message, "tellopponent ", 13)) {
9358 if (appData.icsActive) {
9360 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9364 DisplayNote(message + 13);
9368 if (!strncmp(message, "tellothers ", 11)) {
9369 if (appData.icsActive) {
9371 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9374 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9377 if (!strncmp(message, "tellall ", 8)) {
9378 if (appData.icsActive) {
9380 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9384 DisplayNote(message + 8);
9388 if (strncmp(message, "warning", 7) == 0) {
9389 /* Undocumented feature, use tellusererror in new code */
9390 DisplayError(message, 0);
9393 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9394 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9395 strcat(realname, " query");
9396 AskQuestion(realname, buf2, buf1, cps->pr);
9399 /* Commands from the engine directly to ICS. We don't allow these to be
9400 * sent until we are logged on. Crafty kibitzes have been known to
9401 * interfere with the login process.
9404 if (!strncmp(message, "tellics ", 8)) {
9405 SendToICS(message + 8);
9409 if (!strncmp(message, "tellicsnoalias ", 15)) {
9410 SendToICS(ics_prefix);
9411 SendToICS(message + 15);
9415 /* The following are for backward compatibility only */
9416 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9417 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9418 SendToICS(ics_prefix);
9424 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9425 if(initPing == cps->lastPong) {
9426 if(gameInfo.variant == VariantUnknown) {
9427 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9428 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9429 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9433 if(cps->lastPing == cps->lastPong && abortEngineThink) {
9434 abortEngineThink = FALSE;
9435 DisplayMessage("", "");
9440 if(!strncmp(message, "highlight ", 10)) {
9441 if(appData.testLegality && !*engineVariant && appData.markers) return;
9442 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9445 if(!strncmp(message, "click ", 6)) {
9446 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9447 if(appData.testLegality || !appData.oneClick) return;
9448 sscanf(message+6, "%c%d%c", &f, &y, &c);
9449 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9450 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9451 x = x*squareSize + (x+1)*lineGap + squareSize/2;
9452 y = y*squareSize + (y+1)*lineGap + squareSize/2;
9453 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9454 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9455 LeftClick(Release, lastLeftX, lastLeftY);
9456 controlKey = (c == ',');
9457 LeftClick(Press, x, y);
9458 LeftClick(Release, x, y);
9459 first.highlight = f;
9462 if(strncmp(message, "uciok", 5) == 0) { // response to "uci" probe
9463 int nr = (cps == &second);
9464 appData.isUCI[nr] = isUCI = 1;
9465 ReplaceEngine(cps, nr); // retry install as UCI
9469 * If the move is illegal, cancel it and redraw the board.
9470 * Also deal with other error cases. Matching is rather loose
9471 * here to accommodate engines written before the spec.
9473 if (strncmp(message + 1, "llegal move", 11) == 0 ||
9474 strncmp(message, "Error", 5) == 0) {
9475 if (StrStr(message, "name") ||
9476 StrStr(message, "rating") || StrStr(message, "?") ||
9477 StrStr(message, "result") || StrStr(message, "board") ||
9478 StrStr(message, "bk") || StrStr(message, "computer") ||
9479 StrStr(message, "variant") || StrStr(message, "hint") ||
9480 StrStr(message, "random") || StrStr(message, "depth") ||
9481 StrStr(message, "accepted")) {
9484 if (StrStr(message, "protover")) {
9485 /* Program is responding to input, so it's apparently done
9486 initializing, and this error message indicates it is
9487 protocol version 1. So we don't need to wait any longer
9488 for it to initialize and send feature commands. */
9489 FeatureDone(cps, 1);
9490 cps->protocolVersion = 1;
9493 cps->maybeThinking = FALSE;
9495 if (StrStr(message, "draw")) {
9496 /* Program doesn't have "draw" command */
9497 cps->sendDrawOffers = 0;
9500 if (cps->sendTime != 1 &&
9501 (StrStr(message, "time") || StrStr(message, "otim"))) {
9502 /* Program apparently doesn't have "time" or "otim" command */
9506 if (StrStr(message, "analyze")) {
9507 cps->analysisSupport = FALSE;
9508 cps->analyzing = FALSE;
9509 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9510 EditGameEvent(); // [HGM] try to preserve loaded game
9511 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9512 DisplayError(buf2, 0);
9515 if (StrStr(message, "(no matching move)st")) {
9516 /* Special kludge for GNU Chess 4 only */
9517 cps->stKludge = TRUE;
9518 SendTimeControl(cps, movesPerSession, timeControl,
9519 timeIncrement, appData.searchDepth,
9523 if (StrStr(message, "(no matching move)sd")) {
9524 /* Special kludge for GNU Chess 4 only */
9525 cps->sdKludge = TRUE;
9526 SendTimeControl(cps, movesPerSession, timeControl,
9527 timeIncrement, appData.searchDepth,
9531 if (!StrStr(message, "llegal")) {
9534 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9535 gameMode == IcsIdle) return;
9536 if (forwardMostMove <= backwardMostMove) return;
9537 if (pausing) PauseEvent();
9538 if(appData.forceIllegal) {
9539 // [HGM] illegal: machine refused move; force position after move into it
9540 SendToProgram("force\n", cps);
9541 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9542 // we have a real problem now, as SendBoard will use the a2a3 kludge
9543 // when black is to move, while there might be nothing on a2 or black
9544 // might already have the move. So send the board as if white has the move.
9545 // But first we must change the stm of the engine, as it refused the last move
9546 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9547 if(WhiteOnMove(forwardMostMove)) {
9548 SendToProgram("a7a6\n", cps); // for the engine black still had the move
9549 SendBoard(cps, forwardMostMove); // kludgeless board
9551 SendToProgram("a2a3\n", cps); // for the engine white still had the move
9552 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9553 SendBoard(cps, forwardMostMove+1); // kludgeless board
9555 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9556 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9557 gameMode == TwoMachinesPlay)
9558 SendToProgram("go\n", cps);
9561 if (gameMode == PlayFromGameFile) {
9562 /* Stop reading this game file */
9563 gameMode = EditGame;
9566 /* [HGM] illegal-move claim should forfeit game when Xboard */
9567 /* only passes fully legal moves */
9568 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9569 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9570 "False illegal-move claim", GE_XBOARD );
9571 return; // do not take back move we tested as valid
9573 currentMove = forwardMostMove-1;
9574 DisplayMove(currentMove-1); /* before DisplayMoveError */
9575 SwitchClocks(forwardMostMove-1); // [HGM] race
9576 DisplayBothClocks();
9577 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9578 parseList[currentMove], _(cps->which));
9579 DisplayMoveError(buf1);
9580 DrawPosition(FALSE, boards[currentMove]);
9582 SetUserThinkingEnables();
9585 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9586 /* Program has a broken "time" command that
9587 outputs a string not ending in newline.
9591 if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9592 if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9593 sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1 ) return;
9597 * If chess program startup fails, exit with an error message.
9598 * Attempts to recover here are futile. [HGM] Well, we try anyway
9600 if ((StrStr(message, "unknown host") != NULL)
9601 || (StrStr(message, "No remote directory") != NULL)
9602 || (StrStr(message, "not found") != NULL)
9603 || (StrStr(message, "No such file") != NULL)
9604 || (StrStr(message, "can't alloc") != NULL)
9605 || (StrStr(message, "Permission denied") != NULL)) {
9607 cps->maybeThinking = FALSE;
9608 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9609 _(cps->which), cps->program, cps->host, message);
9610 RemoveInputSource(cps->isr);
9611 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9612 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9613 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9619 * Look for hint output
9621 if (sscanf(message, "Hint: %s", buf1) == 1) {
9622 if (cps == &first && hintRequested) {
9623 hintRequested = FALSE;
9624 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9625 &fromX, &fromY, &toX, &toY, &promoChar)) {
9626 (void) CoordsToAlgebraic(boards[forwardMostMove],
9627 PosFlags(forwardMostMove),
9628 fromY, fromX, toY, toX, promoChar, buf1);
9629 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9630 DisplayInformation(buf2);
9632 /* Hint move could not be parsed!? */
9633 snprintf(buf2, sizeof(buf2),
9634 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9635 buf1, _(cps->which));
9636 DisplayError(buf2, 0);
9639 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9645 * Ignore other messages if game is not in progress
9647 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9648 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9651 * look for win, lose, draw, or draw offer
9653 if (strncmp(message, "1-0", 3) == 0) {
9654 char *p, *q, *r = "";
9655 p = strchr(message, '{');
9663 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9665 } else if (strncmp(message, "0-1", 3) == 0) {
9666 char *p, *q, *r = "";
9667 p = strchr(message, '{');
9675 /* Kludge for Arasan 4.1 bug */
9676 if (strcmp(r, "Black resigns") == 0) {
9677 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9680 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9682 } else if (strncmp(message, "1/2", 3) == 0) {
9683 char *p, *q, *r = "";
9684 p = strchr(message, '{');
9693 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9696 } else if (strncmp(message, "White resign", 12) == 0) {
9697 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9699 } else if (strncmp(message, "Black resign", 12) == 0) {
9700 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9702 } else if (strncmp(message, "White matches", 13) == 0 ||
9703 strncmp(message, "Black matches", 13) == 0 ) {
9704 /* [HGM] ignore GNUShogi noises */
9706 } else if (strncmp(message, "White", 5) == 0 &&
9707 message[5] != '(' &&
9708 StrStr(message, "Black") == NULL) {
9709 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9711 } else if (strncmp(message, "Black", 5) == 0 &&
9712 message[5] != '(') {
9713 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9715 } else if (strcmp(message, "resign") == 0 ||
9716 strcmp(message, "computer resigns") == 0) {
9718 case MachinePlaysBlack:
9719 case IcsPlayingBlack:
9720 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9722 case MachinePlaysWhite:
9723 case IcsPlayingWhite:
9724 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9726 case TwoMachinesPlay:
9727 if (cps->twoMachinesColor[0] == 'w')
9728 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9730 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9737 } else if (strncmp(message, "opponent mates", 14) == 0) {
9739 case MachinePlaysBlack:
9740 case IcsPlayingBlack:
9741 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9743 case MachinePlaysWhite:
9744 case IcsPlayingWhite:
9745 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9747 case TwoMachinesPlay:
9748 if (cps->twoMachinesColor[0] == 'w')
9749 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9751 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9758 } else if (strncmp(message, "computer mates", 14) == 0) {
9760 case MachinePlaysBlack:
9761 case IcsPlayingBlack:
9762 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9764 case MachinePlaysWhite:
9765 case IcsPlayingWhite:
9766 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9768 case TwoMachinesPlay:
9769 if (cps->twoMachinesColor[0] == 'w')
9770 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9772 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9779 } else if (strncmp(message, "checkmate", 9) == 0) {
9780 if (WhiteOnMove(forwardMostMove)) {
9781 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9783 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9786 } else if (strstr(message, "Draw") != NULL ||
9787 strstr(message, "game is a draw") != NULL) {
9788 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9790 } else if (strstr(message, "offer") != NULL &&
9791 strstr(message, "draw") != NULL) {
9793 if (appData.zippyPlay && first.initDone) {
9794 /* Relay offer to ICS */
9795 SendToICS(ics_prefix);
9796 SendToICS("draw\n");
9799 cps->offeredDraw = 2; /* valid until this engine moves twice */
9800 if (gameMode == TwoMachinesPlay) {
9801 if (cps->other->offeredDraw) {
9802 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9803 /* [HGM] in two-machine mode we delay relaying draw offer */
9804 /* until after we also have move, to see if it is really claim */
9806 } else if (gameMode == MachinePlaysWhite ||
9807 gameMode == MachinePlaysBlack) {
9808 if (userOfferedDraw) {
9809 DisplayInformation(_("Machine accepts your draw offer"));
9810 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9812 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9819 * Look for thinking output
9821 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9822 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9824 int plylev, mvleft, mvtot, curscore, time;
9825 char mvname[MOVE_LEN];
9829 int prefixHint = FALSE;
9830 mvname[0] = NULLCHAR;
9833 case MachinePlaysBlack:
9834 case IcsPlayingBlack:
9835 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9837 case MachinePlaysWhite:
9838 case IcsPlayingWhite:
9839 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9844 case IcsObserving: /* [DM] icsEngineAnalyze */
9845 if (!appData.icsEngineAnalyze) ignore = TRUE;
9847 case TwoMachinesPlay:
9848 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9858 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9861 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9862 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9863 char score_buf[MSG_SIZ];
9865 if(nodes>>32 == u64Const(0xFFFFFFFF)) // [HGM] negative node count read
9866 nodes += u64Const(0x100000000);
9868 if (plyext != ' ' && plyext != '\t') {
9872 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9873 if( cps->scoreIsAbsolute &&
9874 ( gameMode == MachinePlaysBlack ||
9875 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9876 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9877 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9878 !WhiteOnMove(currentMove)
9881 curscore = -curscore;
9884 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9886 if(*bestMove) { // rememer time best EPD move was first found
9887 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9888 ChessMove mt; char *p = bestMove;
9889 int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9891 while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9892 if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9893 solvingTime = (solvingTime < 0 ? time : solvingTime);
9897 while(*p && *p != ' ') p++;
9898 while(*p == ' ') p++;
9900 if(!solved) solvingTime = -1;
9902 if(*avoidMove && !solved) {
9903 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9904 ChessMove mt; char *p = avoidMove, solved = 1;
9905 int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9906 while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9907 if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9908 solved = 0; solvingTime = -2;
9911 while(*p && *p != ' ') p++;
9912 while(*p == ' ') p++;
9914 if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9917 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9920 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9921 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9922 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9923 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9924 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9925 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9929 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9930 DisplayError(_("failed writing PV"), 0);
9933 tempStats.depth = plylev;
9934 tempStats.nodes = nodes;
9935 tempStats.time = time;
9936 tempStats.score = curscore;
9937 tempStats.got_only_move = 0;
9939 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9942 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9943 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9944 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9945 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9946 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9947 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9948 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9949 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9952 /* Buffer overflow protection */
9953 if (pv[0] != NULLCHAR) {
9954 if (strlen(pv) >= sizeof(tempStats.movelist)
9955 && appData.debugMode) {
9957 "PV is too long; using the first %u bytes.\n",
9958 (unsigned) sizeof(tempStats.movelist) - 1);
9961 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9963 sprintf(tempStats.movelist, " no PV\n");
9966 if (tempStats.seen_stat) {
9967 tempStats.ok_to_send = 1;
9970 if (strchr(tempStats.movelist, '(') != NULL) {
9971 tempStats.line_is_book = 1;
9972 tempStats.nr_moves = 0;
9973 tempStats.moves_left = 0;
9975 tempStats.line_is_book = 0;
9978 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9979 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9981 SendProgramStatsToFrontend( cps, &tempStats );
9984 [AS] Protect the thinkOutput buffer from overflow... this
9985 is only useful if buf1 hasn't overflowed first!
9987 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9988 if(curscore >= MATE_SCORE)
9989 snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9990 else if(curscore <= -MATE_SCORE)
9991 snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9993 snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9994 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9996 (gameMode == TwoMachinesPlay ?
9997 ToUpper(cps->twoMachinesColor[0]) : ' '),
9999 prefixHint ? lastHint : "",
10000 prefixHint ? " " : "" );
10002 if( buf1[0] != NULLCHAR ) {
10003 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
10005 if( strlen(pv) > max_len ) {
10006 if( appData.debugMode) {
10007 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
10009 pv[max_len+1] = '\0';
10012 strcat( thinkOutput, pv);
10015 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
10016 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10017 DisplayMove(currentMove - 1);
10021 } else if ((p=StrStr(message, "(only move)")) != NULL) {
10022 /* crafty (9.25+) says "(only move) <move>"
10023 * if there is only 1 legal move
10025 sscanf(p, "(only move) %s", buf1);
10026 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
10027 sprintf(programStats.movelist, "%s (only move)", buf1);
10028 programStats.depth = 1;
10029 programStats.nr_moves = 1;
10030 programStats.moves_left = 1;
10031 programStats.nodes = 1;
10032 programStats.time = 1;
10033 programStats.got_only_move = 1;
10035 /* Not really, but we also use this member to
10036 mean "line isn't going to change" (Crafty
10037 isn't searching, so stats won't change) */
10038 programStats.line_is_book = 1;
10040 SendProgramStatsToFrontend( cps, &programStats );
10042 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10043 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10044 DisplayMove(currentMove - 1);
10047 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
10048 &time, &nodes, &plylev, &mvleft,
10049 &mvtot, mvname) >= 5) {
10050 /* The stat01: line is from Crafty (9.29+) in response
10051 to the "." command */
10052 programStats.seen_stat = 1;
10053 cps->maybeThinking = TRUE;
10055 if (programStats.got_only_move || !appData.periodicUpdates)
10058 programStats.depth = plylev;
10059 programStats.time = time;
10060 programStats.nodes = nodes;
10061 programStats.moves_left = mvleft;
10062 programStats.nr_moves = mvtot;
10063 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
10064 programStats.ok_to_send = 1;
10065 programStats.movelist[0] = '\0';
10067 SendProgramStatsToFrontend( cps, &programStats );
10071 } else if (strncmp(message,"++",2) == 0) {
10072 /* Crafty 9.29+ outputs this */
10073 programStats.got_fail = 2;
10076 } else if (strncmp(message,"--",2) == 0) {
10077 /* Crafty 9.29+ outputs this */
10078 programStats.got_fail = 1;
10081 } else if (thinkOutput[0] != NULLCHAR &&
10082 strncmp(message, " ", 4) == 0) {
10083 unsigned message_len;
10086 while (*p && *p == ' ') p++;
10088 message_len = strlen( p );
10090 /* [AS] Avoid buffer overflow */
10091 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
10092 strcat(thinkOutput, " ");
10093 strcat(thinkOutput, p);
10096 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
10097 strcat(programStats.movelist, " ");
10098 strcat(programStats.movelist, p);
10101 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10102 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10103 DisplayMove(currentMove - 1);
10109 buf1[0] = NULLCHAR;
10111 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
10112 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
10114 ChessProgramStats cpstats;
10116 if (plyext != ' ' && plyext != '\t') {
10120 /* [AS] Negate score if machine is playing black and reporting absolute scores */
10121 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
10122 curscore = -curscore;
10125 cpstats.depth = plylev;
10126 cpstats.nodes = nodes;
10127 cpstats.time = time;
10128 cpstats.score = curscore;
10129 cpstats.got_only_move = 0;
10130 cpstats.movelist[0] = '\0';
10132 if (buf1[0] != NULLCHAR) {
10133 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10136 cpstats.ok_to_send = 0;
10137 cpstats.line_is_book = 0;
10138 cpstats.nr_moves = 0;
10139 cpstats.moves_left = 0;
10141 SendProgramStatsToFrontend( cps, &cpstats );
10148 /* Parse a game score from the character string "game", and
10149 record it as the history of the current game. The game
10150 score is NOT assumed to start from the standard position.
10151 The display is not updated in any way.
10154 ParseGameHistory (char *game)
10156 ChessMove moveType;
10157 int fromX, fromY, toX, toY, boardIndex, mask;
10162 if (appData.debugMode)
10163 fprintf(debugFP, "Parsing game history: %s\n", game);
10165 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10166 gameInfo.site = StrSave(appData.icsHost);
10167 gameInfo.date = PGNDate();
10168 gameInfo.round = StrSave("-");
10170 /* Parse out names of players */
10171 while (*game == ' ') game++;
10173 while (*game != ' ') *p++ = *game++;
10175 gameInfo.white = StrSave(buf);
10176 while (*game == ' ') game++;
10178 while (*game != ' ' && *game != '\n') *p++ = *game++;
10180 gameInfo.black = StrSave(buf);
10183 boardIndex = blackPlaysFirst ? 1 : 0;
10186 yyboardindex = boardIndex;
10187 moveType = (ChessMove) Myylex();
10188 switch (moveType) {
10189 case IllegalMove: /* maybe suicide chess, etc. */
10190 if (appData.debugMode) {
10191 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10192 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10193 setbuf(debugFP, NULL);
10195 case WhitePromotion:
10196 case BlackPromotion:
10197 case WhiteNonPromotion:
10198 case BlackNonPromotion:
10201 case WhiteCapturesEnPassant:
10202 case BlackCapturesEnPassant:
10203 case WhiteKingSideCastle:
10204 case WhiteQueenSideCastle:
10205 case BlackKingSideCastle:
10206 case BlackQueenSideCastle:
10207 case WhiteKingSideCastleWild:
10208 case WhiteQueenSideCastleWild:
10209 case BlackKingSideCastleWild:
10210 case BlackQueenSideCastleWild:
10212 case WhiteHSideCastleFR:
10213 case WhiteASideCastleFR:
10214 case BlackHSideCastleFR:
10215 case BlackASideCastleFR:
10217 fromX = currentMoveString[0] - AAA;
10218 fromY = currentMoveString[1] - ONE;
10219 toX = currentMoveString[2] - AAA;
10220 toY = currentMoveString[3] - ONE;
10221 promoChar = currentMoveString[4];
10225 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10226 fromX = moveType == WhiteDrop ?
10227 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10228 (int) CharToPiece(ToLower(currentMoveString[0]));
10230 toX = currentMoveString[2] - AAA;
10231 toY = currentMoveString[3] - ONE;
10232 promoChar = NULLCHAR;
10234 case AmbiguousMove:
10236 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10237 if (appData.debugMode) {
10238 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10239 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10240 setbuf(debugFP, NULL);
10242 DisplayError(buf, 0);
10244 case ImpossibleMove:
10246 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10247 if (appData.debugMode) {
10248 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10249 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10250 setbuf(debugFP, NULL);
10252 DisplayError(buf, 0);
10255 if (boardIndex < backwardMostMove) {
10256 /* Oops, gap. How did that happen? */
10257 DisplayError(_("Gap in move list"), 0);
10260 backwardMostMove = blackPlaysFirst ? 1 : 0;
10261 if (boardIndex > forwardMostMove) {
10262 forwardMostMove = boardIndex;
10266 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10267 strcat(parseList[boardIndex-1], " ");
10268 strcat(parseList[boardIndex-1], yy_text);
10280 case GameUnfinished:
10281 if (gameMode == IcsExamining) {
10282 if (boardIndex < backwardMostMove) {
10283 /* Oops, gap. How did that happen? */
10286 backwardMostMove = blackPlaysFirst ? 1 : 0;
10289 gameInfo.result = moveType;
10290 p = strchr(yy_text, '{');
10291 if (p == NULL) p = strchr(yy_text, '(');
10294 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10296 q = strchr(p, *p == '{' ? '}' : ')');
10297 if (q != NULL) *q = NULLCHAR;
10300 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10301 gameInfo.resultDetails = StrSave(p);
10304 if (boardIndex >= forwardMostMove &&
10305 !(gameMode == IcsObserving && ics_gamenum == -1)) {
10306 backwardMostMove = blackPlaysFirst ? 1 : 0;
10309 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10310 fromY, fromX, toY, toX, promoChar,
10311 parseList[boardIndex]);
10312 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10313 /* currentMoveString is set as a side-effect of yylex */
10314 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10315 strcat(moveList[boardIndex], "\n");
10317 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10318 mask = (WhiteOnMove(boardIndex) ? 0xFF : 0xFF00);
10319 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10325 if(boards[boardIndex][CHECK_COUNT]) boards[boardIndex][CHECK_COUNT] -= mask & 0x101;
10326 if(!boards[boardIndex][CHECK_COUNT] || boards[boardIndex][CHECK_COUNT] & mask) {
10327 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+");
10332 strcat(parseList[boardIndex - 1], "#");
10339 /* Apply a move to the given board */
10341 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10343 ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10344 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10346 /* [HGM] compute & store e.p. status and castling rights for new position */
10347 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10349 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10350 oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8;
10351 board[EP_STATUS] = EP_NONE;
10352 board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
10354 if (fromY == DROP_RANK) {
10355 /* must be first */
10356 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10357 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10360 piece = board[toY][toX] = (ChessSquare) fromX;
10362 // ChessSquare victim;
10365 if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10366 // victim = board[killY][killX],
10367 killed = board[killY][killX],
10368 board[killY][killX] = EmptySquare,
10369 board[EP_STATUS] = EP_CAPTURE;
10370 if( kill2X >= 0 && kill2Y >= 0)
10371 killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10374 if( board[toY][toX] != EmptySquare ) {
10375 board[EP_STATUS] = EP_CAPTURE;
10376 if( (fromX != toX || fromY != toY) && // not igui!
10377 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10378 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
10379 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10383 pawn = board[fromY][fromX];
10384 if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10385 if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10386 captured = board[lastRank][lastFile]; // remove victim
10387 board[lastRank][lastFile] = EmptySquare;
10388 pawn = EmptySquare; // kludge to suppress old e.p. code
10391 if( pawn == WhiteLance || pawn == BlackLance ) {
10392 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10393 if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10394 else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10397 if( pawn == WhitePawn ) {
10398 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10399 board[EP_STATUS] = EP_PAWN_MOVE;
10400 if( toY-fromY>=2) {
10401 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10402 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
10403 gameInfo.variant != VariantBerolina || toX < fromX)
10404 board[EP_STATUS] = toX | berolina;
10405 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10406 gameInfo.variant != VariantBerolina || toX > fromX)
10407 board[EP_STATUS] = toX;
10408 board[LAST_TO] = toX + 256*toY;
10411 if( pawn == BlackPawn ) {
10412 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10413 board[EP_STATUS] = EP_PAWN_MOVE;
10414 if( toY-fromY<= -2) {
10415 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10416 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
10417 gameInfo.variant != VariantBerolina || toX < fromX)
10418 board[EP_STATUS] = toX | berolina;
10419 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10420 gameInfo.variant != VariantBerolina || toX > fromX)
10421 board[EP_STATUS] = toX;
10422 board[LAST_TO] = toX + 256*toY;
10426 if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10427 if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10428 if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10429 if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10431 for(i=0; i<nrCastlingRights; i++) {
10432 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10433 board[CASTLING][i] == toX && castlingRank[i] == toY
10434 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10437 if(gameInfo.variant == VariantSChess) { // update virginity
10438 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10439 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10440 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
10441 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
10444 if (fromX == toX && fromY == toY && killX < 0) return;
10446 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10447 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10448 if(gameInfo.variant == VariantKnightmate)
10449 king += (int) WhiteUnicorn - (int) WhiteKing;
10451 if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10452 && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) { // and tramples own
10453 board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10454 board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10455 board[EP_STATUS] = EP_NONE; // capture was fake!
10457 if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10458 board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10459 board[toY][toX] = piece;
10460 board[EP_STATUS] = EP_NONE; // capture was fake!
10462 /* Code added by Tord: */
10463 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10464 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10465 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10466 board[EP_STATUS] = EP_NONE; // capture was fake!
10467 board[fromY][fromX] = EmptySquare;
10468 board[toY][toX] = EmptySquare;
10469 if((toX > fromX) != (piece == WhiteRook)) {
10470 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10472 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10474 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10475 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10476 board[EP_STATUS] = EP_NONE;
10477 board[fromY][fromX] = EmptySquare;
10478 board[toY][toX] = EmptySquare;
10479 if((toX > fromX) != (piece == BlackRook)) {
10480 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10482 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10484 /* End of code added by Tord */
10486 } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10487 board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10488 board[toY][toX] = piece;
10489 } else if (board[fromY][fromX] == king
10490 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10491 && toY == fromY && toX > fromX+1) {
10492 for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10493 ; // castle with nearest piece
10494 board[fromY][toX-1] = board[fromY][rookX];
10495 board[fromY][rookX] = EmptySquare;
10496 board[fromY][fromX] = EmptySquare;
10497 board[toY][toX] = king;
10498 } else if (board[fromY][fromX] == king
10499 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10500 && toY == fromY && toX < fromX-1) {
10501 for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10502 ; // castle with nearest piece
10503 board[fromY][toX+1] = board[fromY][rookX];
10504 board[fromY][rookX] = EmptySquare;
10505 board[fromY][fromX] = EmptySquare;
10506 board[toY][toX] = king;
10507 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10508 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10509 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10511 /* white pawn promotion */
10512 board[toY][toX] = CharToPiece(ToUpper(promoChar));
10513 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10514 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10515 board[fromY][fromX] = EmptySquare;
10516 } else if ((fromY >= BOARD_HEIGHT>>1)
10517 && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10519 && gameInfo.variant != VariantXiangqi
10520 && gameInfo.variant != VariantBerolina
10521 && (pawn == WhitePawn)
10522 && (board[toY][toX] == EmptySquare)) {
10523 if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10524 board[fromY][fromX] = EmptySquare;
10525 board[toY][toX] = piece;
10526 captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10527 } else if ((fromY == BOARD_HEIGHT-4)
10529 && gameInfo.variant == VariantBerolina
10530 && (board[fromY][fromX] == WhitePawn)
10531 && (board[toY][toX] == EmptySquare)) {
10532 board[fromY][fromX] = EmptySquare;
10533 board[toY][toX] = WhitePawn;
10534 if(oldEP & EP_BEROLIN_A) {
10535 captured = board[fromY][fromX-1];
10536 board[fromY][fromX-1] = EmptySquare;
10537 }else{ captured = board[fromY][fromX+1];
10538 board[fromY][fromX+1] = EmptySquare;
10540 } else if (board[fromY][fromX] == king
10541 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10542 && toY == fromY && toX > fromX+1) {
10543 for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10545 board[fromY][toX-1] = board[fromY][rookX];
10546 board[fromY][rookX] = EmptySquare;
10547 board[fromY][fromX] = EmptySquare;
10548 board[toY][toX] = king;
10549 } else if (board[fromY][fromX] == king
10550 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10551 && toY == fromY && toX < fromX-1) {
10552 for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10554 board[fromY][toX+1] = board[fromY][rookX];
10555 board[fromY][rookX] = EmptySquare;
10556 board[fromY][fromX] = EmptySquare;
10557 board[toY][toX] = king;
10558 } else if (fromY == 7 && fromX == 3
10559 && board[fromY][fromX] == BlackKing
10560 && toY == 7 && toX == 5) {
10561 board[fromY][fromX] = EmptySquare;
10562 board[toY][toX] = BlackKing;
10563 board[fromY][7] = EmptySquare;
10564 board[toY][4] = BlackRook;
10565 } else if (fromY == 7 && fromX == 3
10566 && board[fromY][fromX] == BlackKing
10567 && toY == 7 && toX == 1) {
10568 board[fromY][fromX] = EmptySquare;
10569 board[toY][toX] = BlackKing;
10570 board[fromY][0] = EmptySquare;
10571 board[toY][2] = BlackRook;
10572 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10573 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10574 && toY < promoRank && promoChar
10576 /* black pawn promotion */
10577 board[toY][toX] = CharToPiece(ToLower(promoChar));
10578 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10579 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10580 board[fromY][fromX] = EmptySquare;
10581 } else if ((fromY < BOARD_HEIGHT>>1)
10582 && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10584 && gameInfo.variant != VariantXiangqi
10585 && gameInfo.variant != VariantBerolina
10586 && (pawn == BlackPawn)
10587 && (board[toY][toX] == EmptySquare)) {
10588 if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10589 board[fromY][fromX] = EmptySquare;
10590 board[toY][toX] = piece;
10591 captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10592 } else if ((fromY == 3)
10594 && gameInfo.variant == VariantBerolina
10595 && (board[fromY][fromX] == BlackPawn)
10596 && (board[toY][toX] == EmptySquare)) {
10597 board[fromY][fromX] = EmptySquare;
10598 board[toY][toX] = BlackPawn;
10599 if(oldEP & EP_BEROLIN_A) {
10600 captured = board[fromY][fromX-1];
10601 board[fromY][fromX-1] = EmptySquare;
10602 }else{ captured = board[fromY][fromX+1];
10603 board[fromY][fromX+1] = EmptySquare;
10606 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10607 board[fromY][fromX] = EmptySquare;
10608 board[toY][toX] = piece;
10612 if (gameInfo.holdingsWidth != 0) {
10614 /* !!A lot more code needs to be written to support holdings */
10615 /* [HGM] OK, so I have written it. Holdings are stored in the */
10616 /* penultimate board files, so they are automaticlly stored */
10617 /* in the game history. */
10618 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10619 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10620 /* Delete from holdings, by decreasing count */
10621 /* and erasing image if necessary */
10622 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10623 if(p < (int) BlackPawn) { /* white drop */
10624 p -= (int)WhitePawn;
10625 p = PieceToNumber((ChessSquare)p);
10626 if(p >= gameInfo.holdingsSize) p = 0;
10627 if(--board[p][BOARD_WIDTH-2] <= 0)
10628 board[p][BOARD_WIDTH-1] = EmptySquare;
10629 if((int)board[p][BOARD_WIDTH-2] < 0)
10630 board[p][BOARD_WIDTH-2] = 0;
10631 } else { /* black drop */
10632 p -= (int)BlackPawn;
10633 p = PieceToNumber((ChessSquare)p);
10634 if(p >= gameInfo.holdingsSize) p = 0;
10635 if(--board[handSize-1-p][1] <= 0)
10636 board[handSize-1-p][0] = EmptySquare;
10637 if((int)board[handSize-1-p][1] < 0)
10638 board[handSize-1-p][1] = 0;
10641 if (captured != EmptySquare && gameInfo.holdingsSize > 0
10642 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
10643 /* [HGM] holdings: Add to holdings, if holdings exist */
10644 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10645 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10646 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10648 p = (int) captured;
10649 if (p >= (int) BlackPawn) {
10650 p -= (int)BlackPawn;
10651 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10652 /* Restore shogi-promoted piece to its original first */
10653 captured = (ChessSquare) (DEMOTED(captured));
10656 p = PieceToNumber((ChessSquare)p);
10657 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10658 board[p][BOARD_WIDTH-2]++;
10659 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10661 p -= (int)WhitePawn;
10662 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10663 captured = (ChessSquare) (DEMOTED(captured));
10666 p = PieceToNumber((ChessSquare)p);
10667 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10668 board[handSize-1-p][1]++;
10669 board[handSize-1-p][0] = WHITE_TO_BLACK captured;
10672 } else if (gameInfo.variant == VariantAtomic) {
10673 if (captured != EmptySquare) {
10675 for (y = toY-1; y <= toY+1; y++) {
10676 for (x = toX-1; x <= toX+1; x++) {
10677 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10678 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10679 board[y][x] = EmptySquare;
10683 board[toY][toX] = EmptySquare;
10687 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10688 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10690 if(promoChar == '+') {
10691 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10692 board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10693 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10694 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10695 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10696 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10697 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10698 && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10699 board[toY][toX] = newPiece;
10701 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10702 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10703 // [HGM] superchess: take promotion piece out of holdings
10704 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10705 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10706 if(!--board[k][BOARD_WIDTH-2])
10707 board[k][BOARD_WIDTH-1] = EmptySquare;
10709 if(!--board[handSize-1-k][1])
10710 board[handSize-1-k][0] = EmptySquare;
10715 /* Updates forwardMostMove */
10717 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10719 int x = toX, y = toY, mask;
10720 char *s = parseList[forwardMostMove];
10721 ChessSquare p = boards[forwardMostMove][toY][toX];
10722 // forwardMostMove++; // [HGM] bare: moved downstream
10724 if(kill2X >= 0) x = kill2X, y = kill2Y; else
10725 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10726 (void) CoordsToAlgebraic(boards[forwardMostMove],
10727 PosFlags(forwardMostMove),
10728 fromY, fromX, y, x, (killX < 0)*promoChar,
10730 if(kill2X >= 0 && kill2Y >= 0)
10731 sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10732 if(killX >= 0 && killY >= 0)
10733 sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10734 toX + AAA, toY + ONE - '0', promoChar);
10736 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10737 int timeLeft; static int lastLoadFlag=0; int king, piece;
10738 piece = boards[forwardMostMove][fromY][fromX];
10739 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10740 if(gameInfo.variant == VariantKnightmate)
10741 king += (int) WhiteUnicorn - (int) WhiteKing;
10742 if(forwardMostMove == 0) {
10743 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10744 fprintf(serverMoves, "%s;", UserName());
10745 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10746 fprintf(serverMoves, "%s;", second.tidy);
10747 fprintf(serverMoves, "%s;", first.tidy);
10748 if(gameMode == MachinePlaysWhite)
10749 fprintf(serverMoves, "%s;", UserName());
10750 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10751 fprintf(serverMoves, "%s;", second.tidy);
10752 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10753 lastLoadFlag = loadFlag;
10755 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10756 // print castling suffix
10757 if( toY == fromY && piece == king ) {
10759 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10761 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10764 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10765 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10766 boards[forwardMostMove][toY][toX] == EmptySquare
10767 && fromX != toX && fromY != toY)
10768 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10769 // promotion suffix
10770 if(promoChar != NULLCHAR) {
10771 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10772 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10773 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10774 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10777 char buf[MOVE_LEN*2], *p; int len;
10778 fprintf(serverMoves, "/%d/%d",
10779 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10780 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10781 else timeLeft = blackTimeRemaining/1000;
10782 fprintf(serverMoves, "/%d", timeLeft);
10783 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10784 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10785 if(p = strchr(buf, '=')) *p = NULLCHAR;
10786 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10787 fprintf(serverMoves, "/%s", buf);
10789 fflush(serverMoves);
10792 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10793 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10796 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10797 if (commentList[forwardMostMove+1] != NULL) {
10798 free(commentList[forwardMostMove+1]);
10799 commentList[forwardMostMove+1] = NULL;
10801 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10802 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10803 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10804 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10805 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10806 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10807 adjustedClock = FALSE;
10808 gameInfo.result = GameUnfinished;
10809 if (gameInfo.resultDetails != NULL) {
10810 free(gameInfo.resultDetails);
10811 gameInfo.resultDetails = NULL;
10813 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10814 moveList[forwardMostMove - 1]);
10815 mask = (WhiteOnMove(forwardMostMove) ? 0xFF : 0xFF00);
10816 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10822 if(boards[forwardMostMove][CHECK_COUNT]) boards[forwardMostMove][CHECK_COUNT] -= mask & 0x101;
10823 if(!boards[forwardMostMove][CHECK_COUNT] || boards[forwardMostMove][CHECK_COUNT] & mask) {
10824 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+");
10829 strcat(parseList[forwardMostMove - 1], "#");
10834 /* Updates currentMove if not pausing */
10836 ShowMove (int fromX, int fromY, int toX, int toY)
10838 int instant = (gameMode == PlayFromGameFile) ?
10839 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10840 if(appData.noGUI) return;
10841 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10843 if (forwardMostMove == currentMove + 1) {
10844 AnimateMove(boards[forwardMostMove - 1],
10845 fromX, fromY, toX, toY);
10848 currentMove = forwardMostMove;
10851 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10853 if (instant) return;
10855 DisplayMove(currentMove - 1);
10856 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10857 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10858 SetHighlights(fromX, fromY, toX, toY);
10861 DrawPosition(FALSE, boards[currentMove]);
10862 DisplayBothClocks();
10863 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10867 SendEgtPath (ChessProgramState *cps)
10868 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10869 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10871 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10874 char c, *q = name+1, *r, *s;
10876 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10877 while(*p && *p != ',') *q++ = *p++;
10878 *q++ = ':'; *q = 0;
10879 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10880 strcmp(name, ",nalimov:") == 0 ) {
10881 // take nalimov path from the menu-changeable option first, if it is defined
10882 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10883 SendToProgram(buf,cps); // send egtbpath command for nalimov
10885 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10886 (s = StrStr(appData.egtFormats, name)) != NULL) {
10887 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10888 s = r = StrStr(s, ":") + 1; // beginning of path info
10889 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10890 c = *r; *r = 0; // temporarily null-terminate path info
10891 *--q = 0; // strip of trailig ':' from name
10892 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10894 SendToProgram(buf,cps); // send egtbpath command for this format
10896 if(*p == ',') p++; // read away comma to position for next format name
10901 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10903 int width = 8, height = 8, holdings = 0; // most common sizes
10904 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10905 // correct the deviations default for each variant
10906 if( v == VariantXiangqi ) width = 9, height = 10;
10907 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10908 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10909 if( v == VariantCapablanca || v == VariantCapaRandom ||
10910 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10912 if( v == VariantCourier ) width = 12;
10913 if( v == VariantSuper ) holdings = 8;
10914 if( v == VariantGreat ) width = 10, holdings = 8;
10915 if( v == VariantSChess ) holdings = 7;
10916 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10917 if( v == VariantChuChess) width = 10, height = 10;
10918 if( v == VariantChu ) width = 12, height = 12;
10919 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10920 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10921 holdingsSize >= 0 && holdingsSize != holdings;
10924 char variantError[MSG_SIZ];
10927 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10928 { // returns error message (recognizable by upper-case) if engine does not support the variant
10929 char *p, *variant = VariantName(v);
10930 static char b[MSG_SIZ];
10931 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10932 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10933 holdingsSize, variant); // cook up sized variant name
10934 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10935 if(StrStr(list, b) == NULL) {
10936 // specific sized variant not known, check if general sizing allowed
10937 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10938 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10939 boardWidth, boardHeight, holdingsSize, engine);
10942 /* [HGM] here we really should compare with the maximum supported board size */
10944 } else snprintf(b, MSG_SIZ,"%s", variant);
10945 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10946 p = StrStr(list, b);
10947 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10949 // occurs not at all in list, or only as sub-string
10950 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10951 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10952 int l = strlen(variantError);
10954 while(p != list && p[-1] != ',') p--;
10955 q = strchr(p, ',');
10956 if(q) *q = NULLCHAR;
10957 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10966 InitChessProgram (ChessProgramState *cps, int setup)
10967 /* setup needed to setup FRC opening position */
10969 char buf[MSG_SIZ], *b;
10970 if (appData.noChessProgram) return;
10971 hintRequested = FALSE;
10972 bookRequested = FALSE;
10974 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10975 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10976 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10977 if(cps->memSize) { /* [HGM] memory */
10978 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10979 SendToProgram(buf, cps);
10981 SendEgtPath(cps); /* [HGM] EGT */
10982 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10983 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10984 SendToProgram(buf, cps);
10987 setboardSpoiledMachineBlack = FALSE;
10988 SendToProgram(cps->initString, cps);
10989 if (gameInfo.variant != VariantNormal &&
10990 gameInfo.variant != VariantLoadable
10991 /* [HGM] also send variant if board size non-standard */
10992 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10994 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10995 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10999 char c, *q = cps->variants, *p = strchr(q, ',');
11000 if(p) *p = NULLCHAR;
11001 v = StringToVariant(q);
11002 DisplayError(variantError, 0);
11003 if(v != VariantUnknown && cps == &first) {
11005 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
11006 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
11007 ASSIGN(appData.variant, q);
11008 Reset(TRUE, FALSE);
11014 snprintf(buf, MSG_SIZ, "variant %s\n", b);
11015 SendToProgram(buf, cps);
11017 currentlyInitializedVariant = gameInfo.variant;
11019 /* [HGM] send opening position in FRC to first engine */
11021 SendToProgram("force\n", cps);
11023 /* engine is now in force mode! Set flag to wake it up after first move. */
11024 setboardSpoiledMachineBlack = 1;
11027 if (cps->sendICS) {
11028 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
11029 SendToProgram(buf, cps);
11031 cps->maybeThinking = FALSE;
11032 cps->offeredDraw = 0;
11033 if (!appData.icsActive) {
11034 SendTimeControl(cps, movesPerSession, timeControl,
11035 timeIncrement, appData.searchDepth,
11038 if (appData.showThinking
11039 // [HGM] thinking: four options require thinking output to be sent
11040 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
11042 SendToProgram("post\n", cps);
11044 SendToProgram("hard\n", cps);
11045 if (!appData.ponderNextMove) {
11046 /* Warning: "easy" is a toggle in GNU Chess, so don't send
11047 it without being sure what state we are in first. "hard"
11048 is not a toggle, so that one is OK.
11050 SendToProgram("easy\n", cps);
11052 if (cps->usePing) {
11053 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
11054 SendToProgram(buf, cps);
11056 cps->initDone = TRUE;
11057 ClearEngineOutputPane(cps == &second);
11062 ResendOptions (ChessProgramState *cps, int toEngine)
11063 { // send the stored value of the options
11065 static char buf2[MSG_SIZ*10];
11066 char buf[MSG_SIZ], *p = buf2;
11067 Option *opt = cps->option;
11069 for(i=0; i<cps->nrOptions; i++, opt++) {
11071 switch(opt->type) {
11075 if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11076 snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
11079 if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11080 snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
11083 if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
11084 snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
11092 snprintf(buf2, MSG_SIZ, "option %s\n", buf);
11093 SendToProgram(buf2, cps);
11095 if(p != buf2) *p++ = ',';
11096 strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
11105 StartChessProgram (ChessProgramState *cps)
11110 if (appData.noChessProgram) return;
11111 cps->initDone = FALSE;
11113 if (strcmp(cps->host, "localhost") == 0) {
11114 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
11115 } else if (*appData.remoteShell == NULLCHAR) {
11116 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
11118 if (*appData.remoteUser == NULLCHAR) {
11119 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
11122 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
11123 cps->host, appData.remoteUser, cps->program);
11125 err = StartChildProcess(buf, "", &cps->pr);
11129 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11130 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11131 if(cps != &first) return;
11132 appData.noChessProgram = TRUE;
11135 // DisplayFatalError(buf, err, 1);
11136 // cps->pr = NoProc;
11137 // cps->isr = NULL;
11141 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11142 if (cps->protocolVersion > 1) {
11143 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11144 if(!cps->reload) { // do not clear options when reloading because of -xreuse
11145 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11146 cps->comboCnt = 0; // and values of combo boxes
11148 SendToProgram(buf, cps);
11149 if(cps->reload) ResendOptions(cps, TRUE);
11151 SendToProgram("xboard\n", cps);
11156 TwoMachinesEventIfReady P((void))
11158 static int curMess = 0;
11159 if (first.lastPing != first.lastPong) {
11160 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11161 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11164 if (second.lastPing != second.lastPong) {
11165 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11166 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11169 DisplayMessage("", ""); curMess = 0;
11170 TwoMachinesEvent();
11174 MakeName (char *template)
11178 static char buf[MSG_SIZ];
11182 clock = time((time_t *)NULL);
11183 tm = localtime(&clock);
11185 while(*p++ = *template++) if(p[-1] == '%') {
11186 switch(*template++) {
11187 case 0: *p = 0; return buf;
11188 case 'Y': i = tm->tm_year+1900; break;
11189 case 'y': i = tm->tm_year-100; break;
11190 case 'M': i = tm->tm_mon+1; break;
11191 case 'd': i = tm->tm_mday; break;
11192 case 'h': i = tm->tm_hour; break;
11193 case 'm': i = tm->tm_min; break;
11194 case 's': i = tm->tm_sec; break;
11197 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11203 CountPlayers (char *p)
11206 while(p = strchr(p, '\n')) p++, n++; // count participants
11211 WriteTourneyFile (char *results, FILE *f)
11212 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11213 if(f == NULL) f = fopen(appData.tourneyFile, "w");
11214 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11215 // create a file with tournament description
11216 fprintf(f, "-participants {%s}\n", appData.participants);
11217 fprintf(f, "-seedBase %d\n", appData.seedBase);
11218 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11219 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11220 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11221 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11222 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11223 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11224 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11225 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11226 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11227 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11228 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11229 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11230 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11231 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11232 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11233 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11234 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11235 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11236 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11237 fprintf(f, "-smpCores %d\n", appData.smpCores);
11239 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11241 fprintf(f, "-mps %d\n", appData.movesPerSession);
11242 fprintf(f, "-tc %s\n", appData.timeControl);
11243 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11245 fprintf(f, "-results \"%s\"\n", results);
11250 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11253 Substitute (char *participants, int expunge)
11255 int i, changed, changes=0, nPlayers=0;
11256 char *p, *q, *r, buf[MSG_SIZ];
11257 if(participants == NULL) return;
11258 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11259 r = p = participants; q = appData.participants;
11260 while(*p && *p == *q) {
11261 if(*p == '\n') r = p+1, nPlayers++;
11264 if(*p) { // difference
11265 while(*p && *p++ != '\n')
11267 while(*q && *q++ != '\n')
11269 changed = nPlayers;
11270 changes = 1 + (strcmp(p, q) != 0);
11272 if(changes == 1) { // a single engine mnemonic was changed
11273 q = r; while(*q) nPlayers += (*q++ == '\n');
11274 p = buf; while(*r && (*p = *r++) != '\n') p++;
11276 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11277 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11278 if(mnemonic[i]) { // The substitute is valid
11280 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11281 flock(fileno(f), LOCK_EX);
11282 ParseArgsFromFile(f);
11283 fseek(f, 0, SEEK_SET);
11284 FREE(appData.participants); appData.participants = participants;
11285 if(expunge) { // erase results of replaced engine
11286 int len = strlen(appData.results), w, b, dummy;
11287 for(i=0; i<len; i++) {
11288 Pairing(i, nPlayers, &w, &b, &dummy);
11289 if((w == changed || b == changed) && appData.results[i] == '*') {
11290 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11295 for(i=0; i<len; i++) {
11296 Pairing(i, nPlayers, &w, &b, &dummy);
11297 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11300 WriteTourneyFile(appData.results, f);
11301 fclose(f); // release lock
11304 } else DisplayError(_("No engine with the name you gave is installed"), 0);
11306 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11307 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
11308 free(participants);
11313 CheckPlayers (char *participants)
11316 char buf[MSG_SIZ], *p;
11317 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11318 while(p = strchr(participants, '\n')) {
11320 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11322 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11324 DisplayError(buf, 0);
11328 participants = p + 1;
11334 CreateTourney (char *name)
11337 if(matchMode && strcmp(name, appData.tourneyFile)) {
11338 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11340 if(name[0] == NULLCHAR) {
11341 if(appData.participants[0])
11342 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11345 f = fopen(name, "r");
11346 if(f) { // file exists
11347 ASSIGN(appData.tourneyFile, name);
11348 ParseArgsFromFile(f); // parse it
11350 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11351 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11352 DisplayError(_("Not enough participants"), 0);
11355 if(CheckPlayers(appData.participants)) return 0;
11356 ASSIGN(appData.tourneyFile, name);
11357 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11358 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11361 appData.noChessProgram = FALSE;
11362 appData.clockMode = TRUE;
11368 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11370 char buf[2*MSG_SIZ], *p, *q;
11371 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11372 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11373 skip = !all && group[0]; // if group requested, we start in skip mode
11374 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11375 p = names; q = buf; header = 0;
11376 while(*p && *p != '\n') *q++ = *p++;
11378 if(*p == '\n') p++;
11379 if(buf[0] == '#') {
11380 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11381 depth++; // we must be entering a new group
11382 if(all) continue; // suppress printing group headers when complete list requested
11384 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11386 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11387 if(engineList[i]) free(engineList[i]);
11388 engineList[i] = strdup(buf);
11389 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11390 if(engineMnemonic[i]) free(engineMnemonic[i]);
11391 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11393 sscanf(q + 8, "%s", buf + strlen(buf));
11396 engineMnemonic[i] = strdup(buf);
11399 engineList[i] = engineMnemonic[i] = NULL;
11404 SaveEngineSettings (int n)
11406 int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11407 if(!currentEngine[n] || !currentEngine[n][0]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
11408 if(*engineListFile) ParseSettingsFile(engineListFile, &engineListFile); // update engine list
11409 p = strstr(firstChessProgramNames, currentEngine[n]);
11410 if(!p) { DisplayMessage("saving failed: engine not found in list", ""); return; } // sanity check; engine could be deleted from list after loading
11411 optionSettings = ResendOptions(n ? &second : &first, FALSE);
11412 len = strlen(currentEngine[n]);
11413 q = p + len; *p = 0; // cut list into head and tail piece
11414 s = strstr(currentEngine[n], "firstOptions");
11415 if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11417 while(*r && *r != s[13]) r++;
11418 s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11419 snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11420 } else if(*optionSettings) {
11421 snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11423 ASSIGN(currentEngine[n], buf); // updated engine line
11424 len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11426 snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11427 FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11428 if(*engineListFile) SaveEngineList();
11431 // following implemented as macro to avoid type limitations
11432 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11435 SwapEngines (int n)
11436 { // swap settings for first engine and other engine (so far only some selected options)
11441 SWAP(chessProgram, p)
11443 SWAP(hasOwnBookUCI, h)
11444 SWAP(protocolVersion, h)
11446 SWAP(scoreIsAbsolute, h)
11451 SWAP(engOptions, p)
11452 SWAP(engInitString, p)
11453 SWAP(computerString, p)
11455 SWAP(fenOverride, p)
11457 SWAP(accumulateTC, h)
11464 GetEngineLine (char *s, int n)
11468 extern char *icsNames;
11469 if(!s || !*s) return 0;
11470 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11471 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11472 if(!mnemonic[i]) return 0;
11473 if(n == 11) return 1; // just testing if there was a match
11474 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11475 if(n == 1) SwapEngines(n);
11476 ParseArgsFromString(buf);
11477 if(n == 1) SwapEngines(n);
11478 if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
11479 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11480 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11481 ParseArgsFromString(buf);
11487 SetPlayer (int player, char *p)
11488 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
11490 char buf[MSG_SIZ], *engineName;
11491 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11492 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11493 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11495 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11496 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11497 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11498 ParseArgsFromString(buf);
11499 } else { // no engine with this nickname is installed!
11500 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11501 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11502 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11504 DisplayError(buf, 0);
11511 char *recentEngines;
11514 RecentEngineEvent (int nr)
11517 // SwapEngines(1); // bump first to second
11518 // ReplaceEngine(&second, 1); // and load it there
11519 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11520 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11521 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11522 ReplaceEngine(&first, 0);
11523 FloatToFront(&appData.recentEngineList, command[n]);
11524 ASSIGN(currentEngine[0], command[n]);
11529 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11530 { // determine players from game number
11531 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11533 if(appData.tourneyType == 0) {
11534 roundsPerCycle = (nPlayers - 1) | 1;
11535 pairingsPerRound = nPlayers / 2;
11536 } else if(appData.tourneyType > 0) {
11537 roundsPerCycle = nPlayers - appData.tourneyType;
11538 pairingsPerRound = appData.tourneyType;
11540 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11541 gamesPerCycle = gamesPerRound * roundsPerCycle;
11542 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11543 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11544 curRound = nr / gamesPerRound; nr %= gamesPerRound;
11545 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11546 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11547 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11549 if(appData.cycleSync) *syncInterval = gamesPerCycle;
11550 if(appData.roundSync) *syncInterval = gamesPerRound;
11552 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11554 if(appData.tourneyType == 0) {
11555 if(curPairing == (nPlayers-1)/2 ) {
11556 *whitePlayer = curRound;
11557 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11559 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11560 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11561 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11562 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11564 } else if(appData.tourneyType > 1) {
11565 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11566 *whitePlayer = curRound + appData.tourneyType;
11567 } else if(appData.tourneyType > 0) {
11568 *whitePlayer = curPairing;
11569 *blackPlayer = curRound + appData.tourneyType;
11572 // take care of white/black alternation per round.
11573 // For cycles and games this is already taken care of by default, derived from matchGame!
11574 return curRound & 1;
11578 NextTourneyGame (int nr, int *swapColors)
11579 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11581 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11583 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11584 tf = fopen(appData.tourneyFile, "r");
11585 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11586 ParseArgsFromFile(tf); fclose(tf);
11587 InitTimeControls(); // TC might be altered from tourney file
11589 nPlayers = CountPlayers(appData.participants); // count participants
11590 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11591 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11594 p = q = appData.results;
11595 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11596 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11597 DisplayMessage(_("Waiting for other game(s)"),"");
11598 waitingForGame = TRUE;
11599 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11602 waitingForGame = FALSE;
11605 if(appData.tourneyType < 0) {
11606 if(nr>=0 && !pairingReceived) {
11608 if(pairing.pr == NoProc) {
11609 if(!appData.pairingEngine[0]) {
11610 DisplayFatalError(_("No pairing engine specified"), 0, 1);
11613 StartChessProgram(&pairing); // starts the pairing engine
11615 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11616 SendToProgram(buf, &pairing);
11617 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11618 SendToProgram(buf, &pairing);
11619 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11621 pairingReceived = 0; // ... so we continue here
11623 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11624 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11625 matchGame = 1; roundNr = nr / syncInterval + 1;
11628 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11630 // redefine engines, engine dir, etc.
11631 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11632 if(first.pr == NoProc) {
11633 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11634 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
11636 if(second.pr == NoProc) {
11638 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11639 SwapEngines(1); // and make that valid for second engine by swapping
11640 InitEngine(&second, 1);
11642 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
11643 UpdateLogos(FALSE); // leave display to ModeHiglight()
11649 { // performs game initialization that does not invoke engines, and then tries to start the game
11650 int res, firstWhite, swapColors = 0;
11651 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11652 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
11654 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11655 if(strcmp(buf, currentDebugFile)) { // name has changed
11656 FILE *f = fopen(buf, "w");
11657 if(f) { // if opening the new file failed, just keep using the old one
11658 ASSIGN(currentDebugFile, buf);
11662 if(appData.serverFileName) {
11663 if(serverFP) fclose(serverFP);
11664 serverFP = fopen(appData.serverFileName, "w");
11665 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11666 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11670 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11671 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11672 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11673 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11674 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11675 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11676 Reset(FALSE, first.pr != NoProc);
11677 res = LoadGameOrPosition(matchGame); // setup game
11678 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11679 if(!res) return; // abort when bad game/pos file
11680 if(appData.epd) {// in EPD mode we make sure first engine is to move
11681 firstWhite = !(forwardMostMove & 1);
11682 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11683 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11685 TwoMachinesEvent();
11689 UserAdjudicationEvent (int result)
11691 ChessMove gameResult = GameIsDrawn;
11694 gameResult = WhiteWins;
11696 else if( result < 0 ) {
11697 gameResult = BlackWins;
11700 if( gameMode == TwoMachinesPlay ) {
11701 GameEnds( gameResult, "User adjudication", GE_XBOARD );
11706 // [HGM] save: calculate checksum of game to make games easily identifiable
11708 StringCheckSum (char *s)
11711 if(s==NULL) return 0;
11712 while(*s) i = i*259 + *s++;
11720 for(i=backwardMostMove; i<forwardMostMove; i++) {
11721 sum += pvInfoList[i].depth;
11722 sum += StringCheckSum(parseList[i]);
11723 sum += StringCheckSum(commentList[i]);
11726 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11727 return sum + StringCheckSum(commentList[i]);
11728 } // end of save patch
11731 GameEnds (ChessMove result, char *resultDetails, int whosays)
11733 GameMode nextGameMode;
11735 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11737 if(endingGame) return; /* [HGM] crash: forbid recursion */
11739 if(twoBoards) { // [HGM] dual: switch back to one board
11740 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11741 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11743 if (appData.debugMode) {
11744 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11745 result, resultDetails ? resultDetails : "(null)", whosays);
11748 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11750 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11752 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11753 /* If we are playing on ICS, the server decides when the
11754 game is over, but the engine can offer to draw, claim
11758 if (appData.zippyPlay && first.initDone) {
11759 if (result == GameIsDrawn) {
11760 /* In case draw still needs to be claimed */
11761 SendToICS(ics_prefix);
11762 SendToICS("draw\n");
11763 } else if (StrCaseStr(resultDetails, "resign")) {
11764 SendToICS(ics_prefix);
11765 SendToICS("resign\n");
11769 endingGame = 0; /* [HGM] crash */
11773 /* If we're loading the game from a file, stop */
11774 if (whosays == GE_FILE) {
11775 (void) StopLoadGameTimer();
11779 /* Cancel draw offers */
11780 first.offeredDraw = second.offeredDraw = 0;
11782 /* If this is an ICS game, only ICS can really say it's done;
11783 if not, anyone can. */
11784 isIcsGame = (gameMode == IcsPlayingWhite ||
11785 gameMode == IcsPlayingBlack ||
11786 gameMode == IcsObserving ||
11787 gameMode == IcsExamining);
11789 if (!isIcsGame || whosays == GE_ICS) {
11790 /* OK -- not an ICS game, or ICS said it was done */
11792 if (!isIcsGame && !appData.noChessProgram)
11793 SetUserThinkingEnables();
11795 /* [HGM] if a machine claims the game end we verify this claim */
11796 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11797 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11799 ChessMove trueResult = (ChessMove) -1;
11801 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11802 first.twoMachinesColor[0] :
11803 second.twoMachinesColor[0] ;
11805 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11806 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11807 /* [HGM] verify: engine mate claims accepted if they were flagged */
11808 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11810 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11811 /* [HGM] verify: engine mate claims accepted if they were flagged */
11812 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11814 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11815 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11818 // now verify win claims, but not in drop games, as we don't understand those yet
11819 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11820 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11821 (result == WhiteWins && claimer == 'w' ||
11822 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11823 if (appData.debugMode) {
11824 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11825 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11827 if(result != trueResult) {
11828 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11829 result = claimer == 'w' ? BlackWins : WhiteWins;
11830 resultDetails = buf;
11833 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11834 && (forwardMostMove <= backwardMostMove ||
11835 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11836 (claimer=='b')==(forwardMostMove&1))
11838 /* [HGM] verify: draws that were not flagged are false claims */
11839 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11840 result = claimer == 'w' ? BlackWins : WhiteWins;
11841 resultDetails = buf;
11843 /* (Claiming a loss is accepted no questions asked!) */
11844 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11845 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11846 result = GameUnfinished;
11847 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11849 /* [HGM] bare: don't allow bare King to win */
11850 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11851 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11852 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11853 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11854 && result != GameIsDrawn)
11855 { int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11856 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11857 int p = (int)boards[forwardMostMove][i][j] - color;
11858 if(p >= 0 && p <= (int)WhiteKing) k++;
11859 oppoKings += (p + color == WhiteKing + BlackPawn - color);
11861 if (appData.debugMode) {
11862 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11863 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11865 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11866 result = GameIsDrawn;
11867 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11868 resultDetails = buf;
11874 if(serverMoves != NULL && !loadFlag) { char c = '=';
11875 if(result==WhiteWins) c = '+';
11876 if(result==BlackWins) c = '-';
11877 if(resultDetails != NULL)
11878 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11880 if (resultDetails != NULL) {
11881 gameInfo.result = result;
11882 gameInfo.resultDetails = StrSave(resultDetails);
11884 /* display last move only if game was not loaded from file */
11885 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11886 DisplayMove(currentMove - 1);
11888 if (forwardMostMove != 0) {
11889 if (gameMode != PlayFromGameFile && gameMode != EditGame
11890 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11892 if (*appData.saveGameFile != NULLCHAR) {
11893 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11894 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11896 SaveGameToFile(appData.saveGameFile, TRUE);
11897 } else if (appData.autoSaveGames) {
11898 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11900 if (*appData.savePositionFile != NULLCHAR) {
11901 SavePositionToFile(appData.savePositionFile);
11903 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11907 /* Tell program how game ended in case it is learning */
11908 /* [HGM] Moved this to after saving the PGN, just in case */
11909 /* engine died and we got here through time loss. In that */
11910 /* case we will get a fatal error writing the pipe, which */
11911 /* would otherwise lose us the PGN. */
11912 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11913 /* output during GameEnds should never be fatal anymore */
11914 if (gameMode == MachinePlaysWhite ||
11915 gameMode == MachinePlaysBlack ||
11916 gameMode == TwoMachinesPlay ||
11917 gameMode == IcsPlayingWhite ||
11918 gameMode == IcsPlayingBlack ||
11919 gameMode == BeginningOfGame) {
11921 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11923 if (first.pr != NoProc) {
11924 SendToProgram(buf, &first);
11926 if (second.pr != NoProc &&
11927 gameMode == TwoMachinesPlay) {
11928 SendToProgram(buf, &second);
11933 if (appData.icsActive) {
11934 if (appData.quietPlay &&
11935 (gameMode == IcsPlayingWhite ||
11936 gameMode == IcsPlayingBlack)) {
11937 SendToICS(ics_prefix);
11938 SendToICS("set shout 1\n");
11940 nextGameMode = IcsIdle;
11941 ics_user_moved = FALSE;
11942 /* clean up premove. It's ugly when the game has ended and the
11943 * premove highlights are still on the board.
11946 gotPremove = FALSE;
11947 ClearPremoveHighlights();
11948 DrawPosition(FALSE, boards[currentMove]);
11950 if (whosays == GE_ICS) {
11953 if (gameMode == IcsPlayingWhite)
11955 else if(gameMode == IcsPlayingBlack)
11956 PlayIcsLossSound();
11959 if (gameMode == IcsPlayingBlack)
11961 else if(gameMode == IcsPlayingWhite)
11962 PlayIcsLossSound();
11965 PlayIcsDrawSound();
11968 PlayIcsUnfinishedSound();
11971 if(appData.quitNext) { ExitEvent(0); return; }
11972 } else if (gameMode == EditGame ||
11973 gameMode == PlayFromGameFile ||
11974 gameMode == AnalyzeMode ||
11975 gameMode == AnalyzeFile) {
11976 nextGameMode = gameMode;
11978 nextGameMode = EndOfGame;
11983 nextGameMode = gameMode;
11986 if (appData.noChessProgram) {
11987 gameMode = nextGameMode;
11989 endingGame = 0; /* [HGM] crash */
11994 /* Put first chess program into idle state */
11995 if (first.pr != NoProc &&
11996 (gameMode == MachinePlaysWhite ||
11997 gameMode == MachinePlaysBlack ||
11998 gameMode == TwoMachinesPlay ||
11999 gameMode == IcsPlayingWhite ||
12000 gameMode == IcsPlayingBlack ||
12001 gameMode == BeginningOfGame)) {
12002 SendToProgram("force\n", &first);
12003 if (first.usePing) {
12005 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
12006 SendToProgram(buf, &first);
12009 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
12010 /* Kill off first chess program */
12011 if (first.isr != NULL)
12012 RemoveInputSource(first.isr);
12015 if (first.pr != NoProc) {
12017 DoSleep( appData.delayBeforeQuit );
12018 SendToProgram("quit\n", &first);
12019 DestroyChildProcess(first.pr, 4 + first.useSigterm);
12020 first.reload = TRUE;
12024 if (second.reuse) {
12025 /* Put second chess program into idle state */
12026 if (second.pr != NoProc &&
12027 gameMode == TwoMachinesPlay) {
12028 SendToProgram("force\n", &second);
12029 if (second.usePing) {
12031 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
12032 SendToProgram(buf, &second);
12035 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
12036 /* Kill off second chess program */
12037 if (second.isr != NULL)
12038 RemoveInputSource(second.isr);
12041 if (second.pr != NoProc) {
12042 DoSleep( appData.delayBeforeQuit );
12043 SendToProgram("quit\n", &second);
12044 DestroyChildProcess(second.pr, 4 + second.useSigterm);
12045 second.reload = TRUE;
12047 second.pr = NoProc;
12050 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
12051 char resChar = '=';
12055 if (first.twoMachinesColor[0] == 'w') {
12058 second.matchWins++;
12063 if (first.twoMachinesColor[0] == 'b') {
12066 second.matchWins++;
12069 case GameUnfinished:
12075 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
12076 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
12077 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
12078 ReserveGame(nextGame, resChar); // sets nextGame
12079 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
12080 else ranking = strdup("busy"); //suppress popup when aborted but not finished
12081 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
12083 if (nextGame <= appData.matchGames && !abortMatch) {
12084 gameMode = nextGameMode;
12085 matchGame = nextGame; // this will be overruled in tourney mode!
12086 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
12087 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
12088 endingGame = 0; /* [HGM] crash */
12091 gameMode = nextGameMode;
12093 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
12094 OutputKibitz(2, buf);
12095 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
12096 OutputKibitz(2, buf);
12097 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
12098 if(second.matchWins) OutputKibitz(2, buf);
12099 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
12100 OutputKibitz(2, buf);
12102 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
12103 first.tidy, second.tidy,
12104 first.matchWins, second.matchWins,
12105 appData.matchGames - (first.matchWins + second.matchWins));
12106 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
12107 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
12108 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
12109 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
12110 first.twoMachinesColor = "black\n";
12111 second.twoMachinesColor = "white\n";
12113 first.twoMachinesColor = "white\n";
12114 second.twoMachinesColor = "black\n";
12118 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
12119 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
12121 gameMode = nextGameMode;
12123 endingGame = 0; /* [HGM] crash */
12124 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
12125 if(matchMode == TRUE) { // match through command line: exit with or without popup
12127 ToNrEvent(forwardMostMove);
12128 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
12130 } else DisplayFatalError(buf, 0, 0);
12131 } else { // match through menu; just stop, with or without popup
12132 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12135 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12136 } else DisplayNote(buf);
12138 if(ranking) free(ranking);
12142 /* Assumes program was just initialized (initString sent).
12143 Leaves program in force mode. */
12145 FeedMovesToProgram (ChessProgramState *cps, int upto)
12149 if (appData.debugMode)
12150 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12151 startedFromSetupPosition ? "position and " : "",
12152 backwardMostMove, upto, cps->which);
12153 if(currentlyInitializedVariant != gameInfo.variant) {
12155 // [HGM] variantswitch: make engine aware of new variant
12156 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12157 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12158 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12159 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12160 SendToProgram(buf, cps);
12161 currentlyInitializedVariant = gameInfo.variant;
12163 SendToProgram("force\n", cps);
12164 if (startedFromSetupPosition) {
12165 SendBoard(cps, backwardMostMove);
12166 if (appData.debugMode) {
12167 fprintf(debugFP, "feedMoves\n");
12170 for (i = backwardMostMove; i < upto; i++) {
12171 SendMoveToProgram(i, cps);
12177 ResurrectChessProgram ()
12179 /* The chess program may have exited.
12180 If so, restart it and feed it all the moves made so far. */
12181 static int doInit = 0;
12183 if (appData.noChessProgram) return 1;
12185 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12186 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12187 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12188 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12190 if (first.pr != NoProc) return 1;
12191 StartChessProgram(&first);
12193 InitChessProgram(&first, FALSE);
12194 FeedMovesToProgram(&first, currentMove);
12196 if (!first.sendTime) {
12197 /* can't tell gnuchess what its clock should read,
12198 so we bow to its notion. */
12200 timeRemaining[0][currentMove] = whiteTimeRemaining;
12201 timeRemaining[1][currentMove] = blackTimeRemaining;
12204 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12205 appData.icsEngineAnalyze) && first.analysisSupport) {
12206 SendToProgram("analyze\n", &first);
12207 first.analyzing = TRUE;
12213 * Button procedures
12216 Reset (int redraw, int init)
12220 if (appData.debugMode) {
12221 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12222 redraw, init, gameMode);
12224 pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12225 deadRanks = 0; // assume entire board is used
12227 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12228 CleanupTail(); // [HGM] vari: delete any stored variations
12229 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12230 pausing = pauseExamInvalid = FALSE;
12231 startedFromSetupPosition = blackPlaysFirst = FALSE;
12233 whiteFlag = blackFlag = FALSE;
12234 userOfferedDraw = FALSE;
12235 hintRequested = bookRequested = FALSE;
12236 first.maybeThinking = FALSE;
12237 second.maybeThinking = FALSE;
12238 first.bookSuspend = FALSE; // [HGM] book
12239 second.bookSuspend = FALSE;
12240 thinkOutput[0] = NULLCHAR;
12241 lastHint[0] = NULLCHAR;
12242 ClearGameInfo(&gameInfo);
12243 gameInfo.variant = StringToVariant(appData.variant);
12244 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12245 gameInfo.variant = VariantUnknown;
12246 strncpy(engineVariant, appData.variant, MSG_SIZ);
12248 ics_user_moved = ics_clock_paused = FALSE;
12249 ics_getting_history = H_FALSE;
12251 white_holding[0] = black_holding[0] = NULLCHAR;
12252 ClearProgramStats();
12253 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12257 flipView = appData.flipView;
12258 ClearPremoveHighlights();
12259 gotPremove = FALSE;
12260 alarmSounded = FALSE;
12261 killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12263 GameEnds(EndOfFile, NULL, GE_PLAYER);
12264 if(appData.serverMovesName != NULL) {
12265 /* [HGM] prepare to make moves file for broadcasting */
12266 clock_t t = clock();
12267 if(serverMoves != NULL) fclose(serverMoves);
12268 serverMoves = fopen(appData.serverMovesName, "r");
12269 if(serverMoves != NULL) {
12270 fclose(serverMoves);
12271 /* delay 15 sec before overwriting, so all clients can see end */
12272 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12274 serverMoves = fopen(appData.serverMovesName, "w");
12278 gameMode = BeginningOfGame;
12280 if(appData.icsActive) gameInfo.variant = VariantNormal;
12281 currentMove = forwardMostMove = backwardMostMove = 0;
12282 MarkTargetSquares(1);
12283 InitPosition(redraw);
12284 for (i = 0; i < MAX_MOVES; i++) {
12285 if (commentList[i] != NULL) {
12286 free(commentList[i]);
12287 commentList[i] = NULL;
12291 timeRemaining[0][0] = whiteTimeRemaining;
12292 timeRemaining[1][0] = blackTimeRemaining;
12294 if (first.pr == NoProc) {
12295 StartChessProgram(&first);
12298 InitChessProgram(&first, startedFromSetupPosition);
12301 DisplayMessage("", "");
12302 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12303 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12304 ClearMap(); // [HGM] exclude: invalidate map
12308 AutoPlayGameLoop ()
12311 if (!AutoPlayOneMove())
12313 if (matchMode || appData.timeDelay == 0)
12315 if (appData.timeDelay < 0)
12317 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12325 ReloadGame(1); // next game
12331 int fromX, fromY, toX, toY;
12333 if (appData.debugMode) {
12334 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12337 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12340 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12341 pvInfoList[currentMove].depth = programStats.depth;
12342 pvInfoList[currentMove].score = programStats.score;
12343 pvInfoList[currentMove].time = 0;
12344 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12345 else { // append analysis of final position as comment
12347 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12348 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12350 programStats.depth = 0;
12353 if (currentMove >= forwardMostMove) {
12354 if(gameMode == AnalyzeFile) {
12355 if(appData.loadGameIndex == -1) {
12356 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12357 ScheduleDelayedEvent(AnalyzeNextGame, 10);
12359 ExitAnalyzeMode(); SendToProgram("force\n", &first);
12362 // gameMode = EndOfGame;
12363 // ModeHighlight();
12365 /* [AS] Clear current move marker at the end of a game */
12366 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12371 toX = moveList[currentMove][2] - AAA;
12372 toY = moveList[currentMove][3] - ONE;
12374 if (moveList[currentMove][1] == '@') {
12375 if (appData.highlightLastMove) {
12376 SetHighlights(-1, -1, toX, toY);
12379 fromX = moveList[currentMove][0] - AAA;
12380 fromY = moveList[currentMove][1] - ONE;
12382 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12384 if(moveList[currentMove][4] == ';') { // multi-leg
12385 killX = moveList[currentMove][5] - AAA;
12386 killY = moveList[currentMove][6] - ONE;
12388 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12389 killX = killY = -1;
12391 if (appData.highlightLastMove) {
12392 SetHighlights(fromX, fromY, toX, toY);
12395 DisplayMove(currentMove);
12396 SendMoveToProgram(currentMove++, &first);
12397 DisplayBothClocks();
12398 DrawPosition(FALSE, boards[currentMove]);
12399 // [HGM] PV info: always display, routine tests if empty
12400 DisplayComment(currentMove - 1, commentList[currentMove]);
12406 LoadGameOneMove (ChessMove readAhead)
12408 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12409 char promoChar = NULLCHAR;
12410 ChessMove moveType;
12411 char move[MSG_SIZ];
12414 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12415 gameMode != AnalyzeMode && gameMode != Training) {
12420 yyboardindex = forwardMostMove;
12421 if (readAhead != EndOfFile) {
12422 moveType = readAhead;
12424 if (gameFileFP == NULL)
12426 moveType = (ChessMove) Myylex();
12430 switch (moveType) {
12432 if (appData.debugMode)
12433 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12436 /* append the comment but don't display it */
12437 AppendComment(currentMove, p, FALSE);
12440 case WhiteCapturesEnPassant:
12441 case BlackCapturesEnPassant:
12442 case WhitePromotion:
12443 case BlackPromotion:
12444 case WhiteNonPromotion:
12445 case BlackNonPromotion:
12448 case WhiteKingSideCastle:
12449 case WhiteQueenSideCastle:
12450 case BlackKingSideCastle:
12451 case BlackQueenSideCastle:
12452 case WhiteKingSideCastleWild:
12453 case WhiteQueenSideCastleWild:
12454 case BlackKingSideCastleWild:
12455 case BlackQueenSideCastleWild:
12457 case WhiteHSideCastleFR:
12458 case WhiteASideCastleFR:
12459 case BlackHSideCastleFR:
12460 case BlackASideCastleFR:
12462 if (appData.debugMode)
12463 fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12464 fromX = currentMoveString[0] - AAA;
12465 fromY = currentMoveString[1] - ONE;
12466 toX = currentMoveString[2] - AAA;
12467 toY = currentMoveString[3] - ONE;
12468 promoChar = currentMoveString[4];
12469 if(promoChar == ';') promoChar = currentMoveString[7];
12474 if (appData.debugMode)
12475 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12476 fromX = moveType == WhiteDrop ?
12477 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12478 (int) CharToPiece(ToLower(currentMoveString[0]));
12480 toX = currentMoveString[2] - AAA;
12481 toY = currentMoveString[3] - ONE;
12487 case GameUnfinished:
12488 if (appData.debugMode)
12489 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12490 p = strchr(yy_text, '{');
12491 if (p == NULL) p = strchr(yy_text, '(');
12494 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12496 q = strchr(p, *p == '{' ? '}' : ')');
12497 if (q != NULL) *q = NULLCHAR;
12500 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12501 GameEnds(moveType, p, GE_FILE);
12503 if (cmailMsgLoaded) {
12505 flipView = WhiteOnMove(currentMove);
12506 if (moveType == GameUnfinished) flipView = !flipView;
12507 if (appData.debugMode)
12508 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12513 if (appData.debugMode)
12514 fprintf(debugFP, "Parser hit end of file\n");
12515 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12521 if (WhiteOnMove(currentMove)) {
12522 GameEnds(BlackWins, "Black mates", GE_FILE);
12524 GameEnds(WhiteWins, "White mates", GE_FILE);
12528 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12534 case MoveNumberOne:
12535 if (lastLoadGameStart == GNUChessGame) {
12536 /* GNUChessGames have numbers, but they aren't move numbers */
12537 if (appData.debugMode)
12538 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12539 yy_text, (int) moveType);
12540 return LoadGameOneMove(EndOfFile); /* tail recursion */
12542 /* else fall thru */
12547 /* Reached start of next game in file */
12548 if (appData.debugMode)
12549 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12550 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12556 if (WhiteOnMove(currentMove)) {
12557 GameEnds(BlackWins, "Black mates", GE_FILE);
12559 GameEnds(WhiteWins, "White mates", GE_FILE);
12563 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12569 case PositionDiagram: /* should not happen; ignore */
12570 case ElapsedTime: /* ignore */
12571 case NAG: /* ignore */
12572 if (appData.debugMode)
12573 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12574 yy_text, (int) moveType);
12575 return LoadGameOneMove(EndOfFile); /* tail recursion */
12578 if (appData.testLegality) {
12579 if (appData.debugMode)
12580 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12581 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12582 (forwardMostMove / 2) + 1,
12583 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12584 DisplayError(move, 0);
12587 if (appData.debugMode)
12588 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12589 yy_text, currentMoveString);
12590 if(currentMoveString[1] == '@') {
12591 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12594 fromX = currentMoveString[0] - AAA;
12595 fromY = currentMoveString[1] - ONE;
12597 toX = currentMoveString[2] - AAA;
12598 toY = currentMoveString[3] - ONE;
12599 promoChar = currentMoveString[4];
12603 case AmbiguousMove:
12604 if (appData.debugMode)
12605 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12606 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12607 (forwardMostMove / 2) + 1,
12608 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12609 DisplayError(move, 0);
12614 case ImpossibleMove:
12615 if (appData.debugMode)
12616 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12617 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12618 (forwardMostMove / 2) + 1,
12619 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12620 DisplayError(move, 0);
12626 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12627 DrawPosition(FALSE, boards[currentMove]);
12628 DisplayBothClocks();
12629 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12630 DisplayComment(currentMove - 1, commentList[currentMove]);
12632 (void) StopLoadGameTimer();
12634 cmailOldMove = forwardMostMove;
12637 /* currentMoveString is set as a side-effect of yylex */
12639 thinkOutput[0] = NULLCHAR;
12640 MakeMove(fromX, fromY, toX, toY, promoChar);
12641 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12642 currentMove = forwardMostMove;
12647 /* Load the nth game from the given file */
12649 LoadGameFromFile (char *filename, int n, char *title, int useList)
12654 if (strcmp(filename, "-") == 0) {
12658 f = fopen(filename, "rb");
12660 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12661 DisplayError(buf, errno);
12665 if (fseek(f, 0, 0) == -1) {
12666 /* f is not seekable; probably a pipe */
12669 if (useList && n == 0) {
12670 int error = GameListBuild(f);
12672 DisplayError(_("Cannot build game list"), error);
12673 } else if (!ListEmpty(&gameList) &&
12674 ((ListGame *) gameList.tailPred)->number > 1) {
12675 GameListPopUp(f, title);
12682 return LoadGame(f, n, title, FALSE);
12687 MakeRegisteredMove ()
12689 int fromX, fromY, toX, toY;
12691 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12692 switch (cmailMoveType[lastLoadGameNumber - 1]) {
12695 if (appData.debugMode)
12696 fprintf(debugFP, "Restoring %s for game %d\n",
12697 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12699 thinkOutput[0] = NULLCHAR;
12700 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12701 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12702 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12703 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12704 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12705 promoChar = cmailMove[lastLoadGameNumber - 1][4];
12706 MakeMove(fromX, fromY, toX, toY, promoChar);
12707 ShowMove(fromX, fromY, toX, toY);
12709 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12716 if (WhiteOnMove(currentMove)) {
12717 GameEnds(BlackWins, "Black mates", GE_PLAYER);
12719 GameEnds(WhiteWins, "White mates", GE_PLAYER);
12724 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12731 if (WhiteOnMove(currentMove)) {
12732 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12734 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12739 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12750 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12752 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12756 if (gameNumber > nCmailGames) {
12757 DisplayError(_("No more games in this message"), 0);
12760 if (f == lastLoadGameFP) {
12761 int offset = gameNumber - lastLoadGameNumber;
12763 cmailMsg[0] = NULLCHAR;
12764 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12765 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12766 nCmailMovesRegistered--;
12768 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12769 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12770 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12773 if (! RegisterMove()) return FALSE;
12777 retVal = LoadGame(f, gameNumber, title, useList);
12779 /* Make move registered during previous look at this game, if any */
12780 MakeRegisteredMove();
12782 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12783 commentList[currentMove]
12784 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12785 DisplayComment(currentMove - 1, commentList[currentMove]);
12791 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12793 ReloadGame (int offset)
12795 int gameNumber = lastLoadGameNumber + offset;
12796 if (lastLoadGameFP == NULL) {
12797 DisplayError(_("No game has been loaded yet"), 0);
12800 if (gameNumber <= 0) {
12801 DisplayError(_("Can't back up any further"), 0);
12804 if (cmailMsgLoaded) {
12805 return CmailLoadGame(lastLoadGameFP, gameNumber,
12806 lastLoadGameTitle, lastLoadGameUseList);
12808 return LoadGame(lastLoadGameFP, gameNumber,
12809 lastLoadGameTitle, lastLoadGameUseList);
12813 int keys[EmptySquare+1];
12816 PositionMatches (Board b1, Board b2)
12819 switch(appData.searchMode) {
12820 case 1: return CompareWithRights(b1, b2);
12822 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12823 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12827 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12828 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12829 sum += keys[b1[r][f]] - keys[b2[r][f]];
12833 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12834 sum += keys[b1[r][f]] - keys[b2[r][f]];
12846 int pieceList[256], quickBoard[256];
12847 ChessSquare pieceType[256] = { EmptySquare };
12848 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12849 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12850 int soughtTotal, turn;
12851 Boolean epOK, flipSearch;
12854 unsigned char piece, to;
12857 #define DSIZE (250000)
12859 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12860 Move *moveDatabase = initialSpace;
12861 unsigned int movePtr, dataSize = DSIZE;
12864 MakePieceList (Board board, int *counts)
12866 int r, f, n=Q_PROMO, total=0;
12867 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12868 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12869 int sq = f + (r<<4);
12870 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12871 quickBoard[sq] = ++n;
12873 pieceType[n] = board[r][f];
12874 counts[board[r][f]]++;
12875 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12876 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12880 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12885 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12887 int sq = fromX + (fromY<<4);
12888 int piece = quickBoard[sq], rook;
12889 quickBoard[sq] = 0;
12890 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12891 if(piece == pieceList[1] && fromY == toY) {
12892 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12893 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12894 moveDatabase[movePtr++].piece = Q_WCASTL;
12895 quickBoard[sq] = piece;
12896 piece = quickBoard[from]; quickBoard[from] = 0;
12897 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12898 } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12899 quickBoard[sq] = 0; // remove Rook
12900 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12901 moveDatabase[movePtr++].piece = Q_WCASTL;
12902 quickBoard[sq] = pieceList[1]; // put King
12904 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12907 if(piece == pieceList[2] && fromY == toY) {
12908 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12909 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12910 moveDatabase[movePtr++].piece = Q_BCASTL;
12911 quickBoard[sq] = piece;
12912 piece = quickBoard[from]; quickBoard[from] = 0;
12913 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12914 } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12915 quickBoard[sq] = 0; // remove Rook
12916 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12917 moveDatabase[movePtr++].piece = Q_BCASTL;
12918 quickBoard[sq] = pieceList[2]; // put King
12920 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12923 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12924 quickBoard[(fromY<<4)+toX] = 0;
12925 moveDatabase[movePtr].piece = Q_EP;
12926 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12927 moveDatabase[movePtr].to = sq;
12929 if(promoPiece != pieceType[piece]) {
12930 moveDatabase[movePtr++].piece = Q_PROMO;
12931 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12933 moveDatabase[movePtr].piece = piece;
12934 quickBoard[sq] = piece;
12939 PackGame (Board board)
12941 Move *newSpace = NULL;
12942 moveDatabase[movePtr].piece = 0; // terminate previous game
12943 if(movePtr > dataSize) {
12944 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12945 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12946 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12949 Move *p = moveDatabase, *q = newSpace;
12950 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12951 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12952 moveDatabase = newSpace;
12953 } else { // calloc failed, we must be out of memory. Too bad...
12954 dataSize = 0; // prevent calloc events for all subsequent games
12955 return 0; // and signal this one isn't cached
12959 MakePieceList(board, counts);
12964 QuickCompare (Board board, int *minCounts, int *maxCounts)
12965 { // compare according to search mode
12967 switch(appData.searchMode)
12969 case 1: // exact position match
12970 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12971 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12972 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12975 case 2: // can have extra material on empty squares
12976 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12977 if(board[r][f] == EmptySquare) continue;
12978 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12981 case 3: // material with exact Pawn structure
12982 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12983 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12984 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12985 } // fall through to material comparison
12986 case 4: // exact material
12987 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12989 case 6: // material range with given imbalance
12990 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12991 // fall through to range comparison
12992 case 5: // material range
12993 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12999 QuickScan (Board board, Move *move)
13000 { // reconstruct game,and compare all positions in it
13001 int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
13003 int piece = move->piece;
13004 int to = move->to, from = pieceList[piece];
13005 if(found < 0) { // if already found just scan to game end for final piece count
13006 if(QuickCompare(soughtBoard, minSought, maxSought) ||
13007 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
13008 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
13009 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
13011 static int lastCounts[EmptySquare+1];
13013 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
13014 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
13015 } else stretch = 0;
13016 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
13017 if(found >= 0 && !appData.minPieces) return found;
13019 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
13020 if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
13021 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
13022 piece = (++move)->piece;
13023 from = pieceList[piece];
13024 counts[pieceType[piece]]--;
13025 pieceType[piece] = (ChessSquare) move->to;
13026 counts[move->to]++;
13027 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
13028 counts[pieceType[quickBoard[to]]]--;
13029 quickBoard[to] = 0; total--;
13032 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
13033 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
13034 from = pieceList[piece]; // so this must be King
13035 quickBoard[from] = 0;
13036 pieceList[piece] = to;
13037 from = pieceList[(++move)->piece]; // for FRC this has to be done here
13038 quickBoard[from] = 0; // rook
13039 quickBoard[to] = piece;
13040 to = move->to; piece = move->piece;
13044 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
13045 if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
13046 quickBoard[from] = 0;
13048 quickBoard[to] = piece;
13049 pieceList[piece] = to;
13059 flipSearch = FALSE;
13060 CopyBoard(soughtBoard, boards[currentMove]);
13061 soughtTotal = MakePieceList(soughtBoard, maxSought);
13062 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
13063 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
13064 CopyBoard(reverseBoard, boards[currentMove]);
13065 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13066 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
13067 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
13068 reverseBoard[r][f] = piece;
13070 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
13071 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
13072 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
13073 || (boards[currentMove][CASTLING][2] == NoRights ||
13074 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
13075 && (boards[currentMove][CASTLING][5] == NoRights ||
13076 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
13079 CopyBoard(flipBoard, soughtBoard);
13080 CopyBoard(rotateBoard, reverseBoard);
13081 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13082 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
13083 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
13086 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
13087 if(appData.searchMode >= 5) {
13088 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
13089 MakePieceList(soughtBoard, minSought);
13090 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
13092 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
13093 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
13096 GameInfo dummyInfo;
13097 static int creatingBook;
13100 GameContainsPosition (FILE *f, ListGame *lg)
13102 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
13103 int fromX, fromY, toX, toY;
13105 static int initDone=FALSE;
13107 // weed out games based on numerical tag comparison
13108 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
13109 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
13110 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
13111 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
13113 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
13116 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
13117 else CopyBoard(boards[scratch], initialPosition); // default start position
13120 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
13121 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
13124 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13125 fseek(f, lg->offset, 0);
13128 yyboardindex = scratch;
13129 quickFlag = plyNr+1;
13134 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13140 if(plyNr) return -1; // after we have seen moves, this is for new game
13143 case AmbiguousMove: // we cannot reconstruct the game beyond these two
13144 case ImpossibleMove:
13145 case WhiteWins: // game ends here with these four
13148 case GameUnfinished:
13152 if(appData.testLegality) return -1;
13153 case WhiteCapturesEnPassant:
13154 case BlackCapturesEnPassant:
13155 case WhitePromotion:
13156 case BlackPromotion:
13157 case WhiteNonPromotion:
13158 case BlackNonPromotion:
13161 case WhiteKingSideCastle:
13162 case WhiteQueenSideCastle:
13163 case BlackKingSideCastle:
13164 case BlackQueenSideCastle:
13165 case WhiteKingSideCastleWild:
13166 case WhiteQueenSideCastleWild:
13167 case BlackKingSideCastleWild:
13168 case BlackQueenSideCastleWild:
13169 case WhiteHSideCastleFR:
13170 case WhiteASideCastleFR:
13171 case BlackHSideCastleFR:
13172 case BlackASideCastleFR:
13173 fromX = currentMoveString[0] - AAA;
13174 fromY = currentMoveString[1] - ONE;
13175 toX = currentMoveString[2] - AAA;
13176 toY = currentMoveString[3] - ONE;
13177 promoChar = currentMoveString[4];
13181 fromX = next == WhiteDrop ?
13182 (int) CharToPiece(ToUpper(currentMoveString[0])) :
13183 (int) CharToPiece(ToLower(currentMoveString[0]));
13185 toX = currentMoveString[2] - AAA;
13186 toY = currentMoveString[3] - ONE;
13190 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13192 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13193 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13194 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13195 if(appData.findMirror) {
13196 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13197 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13202 /* Load the nth game from open file f */
13204 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13208 int gn = gameNumber;
13209 ListGame *lg = NULL;
13210 int numPGNTags = 0, i;
13212 GameMode oldGameMode;
13213 VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13214 char oldName[MSG_SIZ];
13216 safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13218 if (appData.debugMode)
13219 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13221 if (gameMode == Training )
13222 SetTrainingModeOff();
13224 oldGameMode = gameMode;
13225 if (gameMode != BeginningOfGame) {
13226 Reset(FALSE, TRUE);
13228 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13231 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13232 fclose(lastLoadGameFP);
13236 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13239 fseek(f, lg->offset, 0);
13240 GameListHighlight(gameNumber);
13241 pos = lg->position;
13245 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13246 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13248 DisplayError(_("Game number out of range"), 0);
13253 if (fseek(f, 0, 0) == -1) {
13254 if (f == lastLoadGameFP ?
13255 gameNumber == lastLoadGameNumber + 1 :
13259 DisplayError(_("Can't seek on game file"), 0);
13264 lastLoadGameFP = f;
13265 lastLoadGameNumber = gameNumber;
13266 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13267 lastLoadGameUseList = useList;
13271 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13272 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13273 lg->gameInfo.black);
13275 } else if (*title != NULLCHAR) {
13276 if (gameNumber > 1) {
13277 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13280 DisplayTitle(title);
13284 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13285 gameMode = PlayFromGameFile;
13289 currentMove = forwardMostMove = backwardMostMove = 0;
13290 CopyBoard(boards[0], initialPosition);
13294 * Skip the first gn-1 games in the file.
13295 * Also skip over anything that precedes an identifiable
13296 * start of game marker, to avoid being confused by
13297 * garbage at the start of the file. Currently
13298 * recognized start of game markers are the move number "1",
13299 * the pattern "gnuchess .* game", the pattern
13300 * "^[#;%] [^ ]* game file", and a PGN tag block.
13301 * A game that starts with one of the latter two patterns
13302 * will also have a move number 1, possibly
13303 * following a position diagram.
13304 * 5-4-02: Let's try being more lenient and allowing a game to
13305 * start with an unnumbered move. Does that break anything?
13307 cm = lastLoadGameStart = EndOfFile;
13309 yyboardindex = forwardMostMove;
13310 cm = (ChessMove) Myylex();
13313 if (cmailMsgLoaded) {
13314 nCmailGames = CMAIL_MAX_GAMES - gn;
13317 DisplayError(_("Game not found in file"), 0);
13324 lastLoadGameStart = cm;
13327 case MoveNumberOne:
13328 switch (lastLoadGameStart) {
13333 case MoveNumberOne:
13335 gn--; /* count this game */
13336 lastLoadGameStart = cm;
13345 switch (lastLoadGameStart) {
13348 case MoveNumberOne:
13350 gn--; /* count this game */
13351 lastLoadGameStart = cm;
13354 lastLoadGameStart = cm; /* game counted already */
13362 yyboardindex = forwardMostMove;
13363 cm = (ChessMove) Myylex();
13364 } while (cm == PGNTag || cm == Comment);
13371 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13372 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
13373 != CMAIL_OLD_RESULT) {
13375 cmailResult[ CMAIL_MAX_GAMES
13376 - gn - 1] = CMAIL_OLD_RESULT;
13383 /* Only a NormalMove can be at the start of a game
13384 * without a position diagram. */
13385 if (lastLoadGameStart == EndOfFile ) {
13387 lastLoadGameStart = MoveNumberOne;
13396 if (appData.debugMode)
13397 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13399 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13401 if (cm == XBoardGame) {
13402 /* Skip any header junk before position diagram and/or move 1 */
13404 yyboardindex = forwardMostMove;
13405 cm = (ChessMove) Myylex();
13407 if (cm == EndOfFile ||
13408 cm == GNUChessGame || cm == XBoardGame) {
13409 /* Empty game; pretend end-of-file and handle later */
13414 if (cm == MoveNumberOne || cm == PositionDiagram ||
13415 cm == PGNTag || cm == Comment)
13418 } else if (cm == GNUChessGame) {
13419 if (gameInfo.event != NULL) {
13420 free(gameInfo.event);
13422 gameInfo.event = StrSave(yy_text);
13425 startedFromSetupPosition = startedFromPositionFile; // [HGM]
13426 while (cm == PGNTag) {
13427 if (appData.debugMode)
13428 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13429 err = ParsePGNTag(yy_text, &gameInfo);
13430 if (!err) numPGNTags++;
13432 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13433 if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13434 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13435 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13436 InitPosition(TRUE);
13437 oldVariant = gameInfo.variant;
13438 if (appData.debugMode)
13439 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13443 if (gameInfo.fen != NULL) {
13444 Board initial_position;
13445 startedFromSetupPosition = TRUE;
13446 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13448 DisplayError(_("Bad FEN position in file"), 0);
13451 CopyBoard(boards[0], initial_position);
13452 if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13453 CopyBoard(initialPosition, initial_position);
13454 if (blackPlaysFirst) {
13455 currentMove = forwardMostMove = backwardMostMove = 1;
13456 CopyBoard(boards[1], initial_position);
13457 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13458 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13459 timeRemaining[0][1] = whiteTimeRemaining;
13460 timeRemaining[1][1] = blackTimeRemaining;
13461 if (commentList[0] != NULL) {
13462 commentList[1] = commentList[0];
13463 commentList[0] = NULL;
13466 currentMove = forwardMostMove = backwardMostMove = 0;
13468 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13470 initialRulePlies = FENrulePlies;
13471 for( i=0; i< nrCastlingRights; i++ )
13472 initialRights[i] = initial_position[CASTLING][i];
13474 yyboardindex = forwardMostMove;
13475 free(gameInfo.fen);
13476 gameInfo.fen = NULL;
13479 yyboardindex = forwardMostMove;
13480 cm = (ChessMove) Myylex();
13482 /* Handle comments interspersed among the tags */
13483 while (cm == Comment) {
13485 if (appData.debugMode)
13486 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13488 AppendComment(currentMove, p, FALSE);
13489 yyboardindex = forwardMostMove;
13490 cm = (ChessMove) Myylex();
13494 /* don't rely on existence of Event tag since if game was
13495 * pasted from clipboard the Event tag may not exist
13497 if (numPGNTags > 0){
13499 if (gameInfo.variant == VariantNormal) {
13500 VariantClass v = StringToVariant(gameInfo.event);
13501 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13502 if(v < VariantShogi) gameInfo.variant = v;
13505 if( appData.autoDisplayTags ) {
13506 tags = PGNTags(&gameInfo);
13507 TagsPopUp(tags, CmailMsg());
13512 /* Make something up, but don't display it now */
13517 if (cm == PositionDiagram) {
13520 Board initial_position;
13522 if (appData.debugMode)
13523 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13525 if (!startedFromSetupPosition) {
13527 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13528 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13539 initial_position[i][j++] = CharToPiece(*p);
13542 while (*p == ' ' || *p == '\t' ||
13543 *p == '\n' || *p == '\r') p++;
13545 if (strncmp(p, "black", strlen("black"))==0)
13546 blackPlaysFirst = TRUE;
13548 blackPlaysFirst = FALSE;
13549 startedFromSetupPosition = TRUE;
13551 CopyBoard(boards[0], initial_position);
13552 if (blackPlaysFirst) {
13553 currentMove = forwardMostMove = backwardMostMove = 1;
13554 CopyBoard(boards[1], initial_position);
13555 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13556 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13557 timeRemaining[0][1] = whiteTimeRemaining;
13558 timeRemaining[1][1] = blackTimeRemaining;
13559 if (commentList[0] != NULL) {
13560 commentList[1] = commentList[0];
13561 commentList[0] = NULL;
13564 currentMove = forwardMostMove = backwardMostMove = 0;
13567 yyboardindex = forwardMostMove;
13568 cm = (ChessMove) Myylex();
13571 if(!creatingBook) {
13572 if (first.pr == NoProc) {
13573 StartChessProgram(&first);
13575 InitChessProgram(&first, FALSE);
13576 if(gameInfo.variant == VariantUnknown && *oldName) {
13577 safeStrCpy(engineVariant, oldName, MSG_SIZ);
13578 gameInfo.variant = v;
13580 SendToProgram("force\n", &first);
13581 if (startedFromSetupPosition) {
13582 SendBoard(&first, forwardMostMove);
13583 if (appData.debugMode) {
13584 fprintf(debugFP, "Load Game\n");
13586 DisplayBothClocks();
13590 /* [HGM] server: flag to write setup moves in broadcast file as one */
13591 loadFlag = appData.suppressLoadMoves;
13593 while (cm == Comment) {
13595 if (appData.debugMode)
13596 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13598 AppendComment(currentMove, p, FALSE);
13599 yyboardindex = forwardMostMove;
13600 cm = (ChessMove) Myylex();
13603 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13604 cm == WhiteWins || cm == BlackWins ||
13605 cm == GameIsDrawn || cm == GameUnfinished) {
13606 DisplayMessage("", _("No moves in game"));
13607 if (cmailMsgLoaded) {
13608 if (appData.debugMode)
13609 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13613 DrawPosition(FALSE, boards[currentMove]);
13614 DisplayBothClocks();
13615 gameMode = EditGame;
13622 // [HGM] PV info: routine tests if comment empty
13623 if (!matchMode && (pausing || appData.timeDelay != 0)) {
13624 DisplayComment(currentMove - 1, commentList[currentMove]);
13626 if (!matchMode && appData.timeDelay != 0)
13627 DrawPosition(FALSE, boards[currentMove]);
13629 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13630 programStats.ok_to_send = 1;
13633 /* if the first token after the PGN tags is a move
13634 * and not move number 1, retrieve it from the parser
13636 if (cm != MoveNumberOne)
13637 LoadGameOneMove(cm);
13639 /* load the remaining moves from the file */
13640 while (LoadGameOneMove(EndOfFile)) {
13641 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13642 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13645 /* rewind to the start of the game */
13646 currentMove = backwardMostMove;
13648 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13650 if (oldGameMode == AnalyzeFile) {
13651 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13652 AnalyzeFileEvent();
13654 if (oldGameMode == AnalyzeMode) {
13655 AnalyzeFileEvent();
13658 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13659 long int w, b; // [HGM] adjourn: restore saved clock times
13660 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13661 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13662 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13663 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13667 if(creatingBook) return TRUE;
13668 if (!matchMode && pos > 0) {
13669 ToNrEvent(pos); // [HGM] no autoplay if selected on position
13671 if (matchMode || appData.timeDelay == 0) {
13673 } else if (appData.timeDelay > 0) {
13674 AutoPlayGameLoop();
13677 if (appData.debugMode)
13678 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13680 loadFlag = 0; /* [HGM] true game starts */
13684 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13686 ReloadPosition (int offset)
13688 int positionNumber = lastLoadPositionNumber + offset;
13689 if (lastLoadPositionFP == NULL) {
13690 DisplayError(_("No position has been loaded yet"), 0);
13693 if (positionNumber <= 0) {
13694 DisplayError(_("Can't back up any further"), 0);
13697 return LoadPosition(lastLoadPositionFP, positionNumber,
13698 lastLoadPositionTitle);
13701 /* Load the nth position from the given file */
13703 LoadPositionFromFile (char *filename, int n, char *title)
13708 if (strcmp(filename, "-") == 0) {
13709 return LoadPosition(stdin, n, "stdin");
13711 f = fopen(filename, "rb");
13713 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13714 DisplayError(buf, errno);
13717 return LoadPosition(f, n, title);
13722 /* Load the nth position from the given open file, and close it */
13724 LoadPosition (FILE *f, int positionNumber, char *title)
13726 char *p, line[MSG_SIZ];
13727 Board initial_position;
13728 int i, j, fenMode, pn;
13730 if (gameMode == Training )
13731 SetTrainingModeOff();
13733 if (gameMode != BeginningOfGame) {
13734 Reset(FALSE, TRUE);
13736 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13737 fclose(lastLoadPositionFP);
13739 if (positionNumber == 0) positionNumber = 1;
13740 lastLoadPositionFP = f;
13741 lastLoadPositionNumber = positionNumber;
13742 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13743 if (first.pr == NoProc && !appData.noChessProgram) {
13744 StartChessProgram(&first);
13745 InitChessProgram(&first, FALSE);
13747 pn = positionNumber;
13748 if (positionNumber < 0) {
13749 /* Negative position number means to seek to that byte offset */
13750 if (fseek(f, -positionNumber, 0) == -1) {
13751 DisplayError(_("Can't seek on position file"), 0);
13756 if (fseek(f, 0, 0) == -1) {
13757 if (f == lastLoadPositionFP ?
13758 positionNumber == lastLoadPositionNumber + 1 :
13759 positionNumber == 1) {
13762 DisplayError(_("Can't seek on position file"), 0);
13767 /* See if this file is FEN or old-style xboard */
13768 if (fgets(line, MSG_SIZ, f) == NULL) {
13769 DisplayError(_("Position not found in file"), 0);
13772 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13773 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13776 if (fenMode || line[0] == '#') pn--;
13778 /* skip positions before number pn */
13779 if (fgets(line, MSG_SIZ, f) == NULL) {
13781 DisplayError(_("Position not found in file"), 0);
13784 if (fenMode || line[0] == '#') pn--;
13790 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13791 DisplayError(_("Bad FEN position in file"), 0);
13794 if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13795 sscanf(p+4, "%[^;]", bestMove);
13796 } else *bestMove = NULLCHAR;
13797 if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13798 sscanf(p+4, "%[^;]", avoidMove);
13799 } else *avoidMove = NULLCHAR;
13801 (void) fgets(line, MSG_SIZ, f);
13802 (void) fgets(line, MSG_SIZ, f);
13804 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13805 (void) fgets(line, MSG_SIZ, f);
13806 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13809 initial_position[i][j++] = CharToPiece(*p);
13813 blackPlaysFirst = FALSE;
13815 (void) fgets(line, MSG_SIZ, f);
13816 if (strncmp(line, "black", strlen("black"))==0)
13817 blackPlaysFirst = TRUE;
13820 startedFromSetupPosition = TRUE;
13822 CopyBoard(boards[0], initial_position);
13823 if (blackPlaysFirst) {
13824 currentMove = forwardMostMove = backwardMostMove = 1;
13825 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13826 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13827 CopyBoard(boards[1], initial_position);
13828 DisplayMessage("", _("Black to play"));
13830 currentMove = forwardMostMove = backwardMostMove = 0;
13831 DisplayMessage("", _("White to play"));
13833 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13834 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13835 SendToProgram("force\n", &first);
13836 SendBoard(&first, forwardMostMove);
13838 if (appData.debugMode) {
13840 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13841 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13842 fprintf(debugFP, "Load Position\n");
13845 if (positionNumber > 1) {
13846 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13847 DisplayTitle(line);
13849 DisplayTitle(title);
13851 gameMode = EditGame;
13854 timeRemaining[0][1] = whiteTimeRemaining;
13855 timeRemaining[1][1] = blackTimeRemaining;
13856 DrawPosition(FALSE, boards[currentMove]);
13863 CopyPlayerNameIntoFileName (char **dest, char *src)
13865 while (*src != NULLCHAR && *src != ',') {
13870 *(*dest)++ = *src++;
13876 DefaultFileName (char *ext)
13878 static char def[MSG_SIZ];
13881 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13883 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13885 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13887 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13894 /* Save the current game to the given file */
13896 SaveGameToFile (char *filename, int append)
13900 int result, i, t,tot=0;
13902 if (strcmp(filename, "-") == 0) {
13903 return SaveGame(stdout, 0, NULL);
13905 for(i=0; i<10; i++) { // upto 10 tries
13906 f = fopen(filename, append ? "a" : "w");
13907 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13908 if(f || errno != 13) break;
13909 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13913 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13914 DisplayError(buf, errno);
13917 safeStrCpy(buf, lastMsg, MSG_SIZ);
13918 DisplayMessage(_("Waiting for access to save file"), "");
13919 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13920 DisplayMessage(_("Saving game"), "");
13921 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13922 result = SaveGame(f, 0, NULL);
13923 DisplayMessage(buf, "");
13930 SavePart (char *str)
13932 static char buf[MSG_SIZ];
13935 p = strchr(str, ' ');
13936 if (p == NULL) return str;
13937 strncpy(buf, str, p - str);
13938 buf[p - str] = NULLCHAR;
13942 #define PGN_MAX_LINE 75
13944 #define PGN_SIDE_WHITE 0
13945 #define PGN_SIDE_BLACK 1
13948 FindFirstMoveOutOfBook (int side)
13952 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13953 int index = backwardMostMove;
13954 int has_book_hit = 0;
13956 if( (index % 2) != side ) {
13960 while( index < forwardMostMove ) {
13961 /* Check to see if engine is in book */
13962 int depth = pvInfoList[index].depth;
13963 int score = pvInfoList[index].score;
13969 else if( score == 0 && depth == 63 ) {
13970 in_book = 1; /* Zappa */
13972 else if( score == 2 && depth == 99 ) {
13973 in_book = 1; /* Abrok */
13976 has_book_hit += in_book;
13992 GetOutOfBookInfo (char * buf)
13996 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13998 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13999 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
14003 if( oob[0] >= 0 || oob[1] >= 0 ) {
14004 for( i=0; i<2; i++ ) {
14008 if( i > 0 && oob[0] >= 0 ) {
14009 strcat( buf, " " );
14012 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
14013 sprintf( buf+strlen(buf), "%s%.2f",
14014 pvInfoList[idx].score >= 0 ? "+" : "",
14015 pvInfoList[idx].score / 100.0 );
14021 /* Save game in PGN style */
14023 SaveGamePGN2 (FILE *f)
14025 int i, offset, linelen, newblock;
14028 int movelen, numlen, blank;
14029 char move_buffer[100]; /* [AS] Buffer for move+PV info */
14031 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14033 PrintPGNTags(f, &gameInfo);
14035 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
14037 if (backwardMostMove > 0 || startedFromSetupPosition) {
14038 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
14039 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
14040 fprintf(f, "\n{--------------\n");
14041 PrintPosition(f, backwardMostMove);
14042 fprintf(f, "--------------}\n");
14046 /* [AS] Out of book annotation */
14047 if( appData.saveOutOfBookInfo ) {
14050 GetOutOfBookInfo( buf );
14052 if( buf[0] != '\0' ) {
14053 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
14060 i = backwardMostMove;
14064 while (i < forwardMostMove) {
14065 /* Print comments preceding this move */
14066 if (commentList[i] != NULL) {
14067 if (linelen > 0) fprintf(f, "\n");
14068 fprintf(f, "%s", commentList[i]);
14073 /* Format move number */
14075 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
14078 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
14080 numtext[0] = NULLCHAR;
14082 numlen = strlen(numtext);
14085 /* Print move number */
14086 blank = linelen > 0 && numlen > 0;
14087 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
14096 fprintf(f, "%s", numtext);
14100 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
14101 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
14104 blank = linelen > 0 && movelen > 0;
14105 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14114 fprintf(f, "%s", move_buffer);
14115 linelen += movelen;
14117 /* [AS] Add PV info if present */
14118 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
14119 /* [HGM] add time */
14120 char buf[MSG_SIZ]; int seconds;
14122 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
14128 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
14131 seconds = (seconds + 4)/10; // round to full seconds
14133 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14135 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14138 if(appData.cumulativeTimePGN) {
14139 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14142 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14143 pvInfoList[i].score >= 0 ? "+" : "",
14144 pvInfoList[i].score / 100.0,
14145 pvInfoList[i].depth,
14148 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14150 /* Print score/depth */
14151 blank = linelen > 0 && movelen > 0;
14152 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14161 fprintf(f, "%s", move_buffer);
14162 linelen += movelen;
14168 /* Start a new line */
14169 if (linelen > 0) fprintf(f, "\n");
14171 /* Print comments after last move */
14172 if (commentList[i] != NULL) {
14173 fprintf(f, "%s\n", commentList[i]);
14177 if (gameInfo.resultDetails != NULL &&
14178 gameInfo.resultDetails[0] != NULLCHAR) {
14179 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14180 if(gameInfo.result == GameUnfinished && appData.clockMode &&
14181 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14182 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14183 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14185 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14189 /* Save game in PGN style and close the file */
14191 SaveGamePGN (FILE *f)
14195 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14199 /* Save game in old style and close the file */
14201 SaveGameOldStyle (FILE *f)
14206 tm = time((time_t *) NULL);
14208 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14211 if (backwardMostMove > 0 || startedFromSetupPosition) {
14212 fprintf(f, "\n[--------------\n");
14213 PrintPosition(f, backwardMostMove);
14214 fprintf(f, "--------------]\n");
14219 i = backwardMostMove;
14220 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14222 while (i < forwardMostMove) {
14223 if (commentList[i] != NULL) {
14224 fprintf(f, "[%s]\n", commentList[i]);
14227 if ((i % 2) == 1) {
14228 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
14231 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
14233 if (commentList[i] != NULL) {
14237 if (i >= forwardMostMove) {
14241 fprintf(f, "%s\n", parseList[i]);
14246 if (commentList[i] != NULL) {
14247 fprintf(f, "[%s]\n", commentList[i]);
14250 /* This isn't really the old style, but it's close enough */
14251 if (gameInfo.resultDetails != NULL &&
14252 gameInfo.resultDetails[0] != NULLCHAR) {
14253 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14254 gameInfo.resultDetails);
14256 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14263 /* Save the current game to open file f and close the file */
14265 SaveGame (FILE *f, int dummy, char *dummy2)
14267 if (gameMode == EditPosition) EditPositionDone(TRUE);
14268 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14269 if (appData.oldSaveStyle)
14270 return SaveGameOldStyle(f);
14272 return SaveGamePGN(f);
14275 /* Save the current position to the given file */
14277 SavePositionToFile (char *filename)
14282 if (strcmp(filename, "-") == 0) {
14283 return SavePosition(stdout, 0, NULL);
14285 f = fopen(filename, "a");
14287 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14288 DisplayError(buf, errno);
14291 safeStrCpy(buf, lastMsg, MSG_SIZ);
14292 DisplayMessage(_("Waiting for access to save file"), "");
14293 flock(fileno(f), LOCK_EX); // [HGM] lock
14294 DisplayMessage(_("Saving position"), "");
14295 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
14296 SavePosition(f, 0, NULL);
14297 DisplayMessage(buf, "");
14303 /* Save the current position to the given open file and close the file */
14305 SavePosition (FILE *f, int dummy, char *dummy2)
14310 if (gameMode == EditPosition) EditPositionDone(TRUE);
14311 if (appData.oldSaveStyle) {
14312 tm = time((time_t *) NULL);
14314 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14316 fprintf(f, "[--------------\n");
14317 PrintPosition(f, currentMove);
14318 fprintf(f, "--------------]\n");
14320 fen = PositionToFEN(currentMove, NULL, 1);
14321 fprintf(f, "%s\n", fen);
14329 ReloadCmailMsgEvent (int unregister)
14332 static char *inFilename = NULL;
14333 static char *outFilename;
14335 struct stat inbuf, outbuf;
14338 /* Any registered moves are unregistered if unregister is set, */
14339 /* i.e. invoked by the signal handler */
14341 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14342 cmailMoveRegistered[i] = FALSE;
14343 if (cmailCommentList[i] != NULL) {
14344 free(cmailCommentList[i]);
14345 cmailCommentList[i] = NULL;
14348 nCmailMovesRegistered = 0;
14351 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14352 cmailResult[i] = CMAIL_NOT_RESULT;
14356 if (inFilename == NULL) {
14357 /* Because the filenames are static they only get malloced once */
14358 /* and they never get freed */
14359 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14360 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14362 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14363 sprintf(outFilename, "%s.out", appData.cmailGameName);
14366 status = stat(outFilename, &outbuf);
14368 cmailMailedMove = FALSE;
14370 status = stat(inFilename, &inbuf);
14371 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14374 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14375 counts the games, notes how each one terminated, etc.
14377 It would be nice to remove this kludge and instead gather all
14378 the information while building the game list. (And to keep it
14379 in the game list nodes instead of having a bunch of fixed-size
14380 parallel arrays.) Note this will require getting each game's
14381 termination from the PGN tags, as the game list builder does
14382 not process the game moves. --mann
14384 cmailMsgLoaded = TRUE;
14385 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14387 /* Load first game in the file or popup game menu */
14388 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14390 #endif /* !WIN32 */
14398 char string[MSG_SIZ];
14400 if ( cmailMailedMove
14401 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14402 return TRUE; /* Allow free viewing */
14405 /* Unregister move to ensure that we don't leave RegisterMove */
14406 /* with the move registered when the conditions for registering no */
14408 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14409 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14410 nCmailMovesRegistered --;
14412 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14414 free(cmailCommentList[lastLoadGameNumber - 1]);
14415 cmailCommentList[lastLoadGameNumber - 1] = NULL;
14419 if (cmailOldMove == -1) {
14420 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14424 if (currentMove > cmailOldMove + 1) {
14425 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14429 if (currentMove < cmailOldMove) {
14430 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14434 if (forwardMostMove > currentMove) {
14435 /* Silently truncate extra moves */
14439 if ( (currentMove == cmailOldMove + 1)
14440 || ( (currentMove == cmailOldMove)
14441 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14442 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14443 if (gameInfo.result != GameUnfinished) {
14444 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14447 if (commentList[currentMove] != NULL) {
14448 cmailCommentList[lastLoadGameNumber - 1]
14449 = StrSave(commentList[currentMove]);
14451 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14453 if (appData.debugMode)
14454 fprintf(debugFP, "Saving %s for game %d\n",
14455 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14457 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14459 f = fopen(string, "w");
14460 if (appData.oldSaveStyle) {
14461 SaveGameOldStyle(f); /* also closes the file */
14463 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14464 f = fopen(string, "w");
14465 SavePosition(f, 0, NULL); /* also closes the file */
14467 fprintf(f, "{--------------\n");
14468 PrintPosition(f, currentMove);
14469 fprintf(f, "--------------}\n\n");
14471 SaveGame(f, 0, NULL); /* also closes the file*/
14474 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14475 nCmailMovesRegistered ++;
14476 } else if (nCmailGames == 1) {
14477 DisplayError(_("You have not made a move yet"), 0);
14488 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14489 FILE *commandOutput;
14490 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14491 int nBytes = 0; /* Suppress warnings on uninitialized variables */
14497 if (! cmailMsgLoaded) {
14498 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14502 if (nCmailGames == nCmailResults) {
14503 DisplayError(_("No unfinished games"), 0);
14507 #if CMAIL_PROHIBIT_REMAIL
14508 if (cmailMailedMove) {
14509 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);
14510 DisplayError(msg, 0);
14515 if (! (cmailMailedMove || RegisterMove())) return;
14517 if ( cmailMailedMove
14518 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14519 snprintf(string, MSG_SIZ, partCommandString,
14520 appData.debugMode ? " -v" : "", appData.cmailGameName);
14521 commandOutput = popen(string, "r");
14523 if (commandOutput == NULL) {
14524 DisplayError(_("Failed to invoke cmail"), 0);
14526 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14527 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14529 if (nBuffers > 1) {
14530 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14531 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14532 nBytes = MSG_SIZ - 1;
14534 (void) memcpy(msg, buffer, nBytes);
14536 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14538 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14539 cmailMailedMove = TRUE; /* Prevent >1 moves */
14542 for (i = 0; i < nCmailGames; i ++) {
14543 if (cmailResult[i] == CMAIL_NOT_RESULT) {
14548 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14550 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14552 appData.cmailGameName,
14554 LoadGameFromFile(buffer, 1, buffer, FALSE);
14555 cmailMsgLoaded = FALSE;
14559 DisplayInformation(msg);
14560 pclose(commandOutput);
14563 if ((*cmailMsg) != '\0') {
14564 DisplayInformation(cmailMsg);
14569 #endif /* !WIN32 */
14578 int prependComma = 0;
14580 char string[MSG_SIZ]; /* Space for game-list */
14583 if (!cmailMsgLoaded) return "";
14585 if (cmailMailedMove) {
14586 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14588 /* Create a list of games left */
14589 snprintf(string, MSG_SIZ, "[");
14590 for (i = 0; i < nCmailGames; i ++) {
14591 if (! ( cmailMoveRegistered[i]
14592 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14593 if (prependComma) {
14594 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14596 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14600 strcat(string, number);
14603 strcat(string, "]");
14605 if (nCmailMovesRegistered + nCmailResults == 0) {
14606 switch (nCmailGames) {
14608 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14612 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14616 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14621 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14623 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14628 if (nCmailResults == nCmailGames) {
14629 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14631 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14636 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14648 if (gameMode == Training)
14649 SetTrainingModeOff();
14652 cmailMsgLoaded = FALSE;
14653 if (appData.icsActive) {
14654 SendToICS(ics_prefix);
14655 SendToICS("refresh\n");
14660 ExitEvent (int status)
14664 /* Give up on clean exit */
14668 /* Keep trying for clean exit */
14672 if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14673 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14675 if (telnetISR != NULL) {
14676 RemoveInputSource(telnetISR);
14678 if (icsPR != NoProc) {
14679 DestroyChildProcess(icsPR, TRUE);
14682 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14683 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14685 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14686 /* make sure this other one finishes before killing it! */
14687 if(endingGame) { int count = 0;
14688 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14689 while(endingGame && count++ < 10) DoSleep(1);
14690 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14693 /* Kill off chess programs */
14694 if (first.pr != NoProc) {
14697 DoSleep( appData.delayBeforeQuit );
14698 SendToProgram("quit\n", &first);
14699 DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14701 if (second.pr != NoProc) {
14702 DoSleep( appData.delayBeforeQuit );
14703 SendToProgram("quit\n", &second);
14704 DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14706 if (first.isr != NULL) {
14707 RemoveInputSource(first.isr);
14709 if (second.isr != NULL) {
14710 RemoveInputSource(second.isr);
14713 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14714 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14716 ShutDownFrontEnd();
14721 PauseEngine (ChessProgramState *cps)
14723 SendToProgram("pause\n", cps);
14728 UnPauseEngine (ChessProgramState *cps)
14730 SendToProgram("resume\n", cps);
14737 if (appData.debugMode)
14738 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14742 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14744 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14745 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14746 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14748 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14749 HandleMachineMove(stashedInputMove, stalledEngine);
14750 stalledEngine = NULL;
14753 if (gameMode == MachinePlaysWhite ||
14754 gameMode == TwoMachinesPlay ||
14755 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14756 if(first.pause) UnPauseEngine(&first);
14757 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14758 if(second.pause) UnPauseEngine(&second);
14759 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14762 DisplayBothClocks();
14764 if (gameMode == PlayFromGameFile) {
14765 if (appData.timeDelay >= 0)
14766 AutoPlayGameLoop();
14767 } else if (gameMode == IcsExamining && pauseExamInvalid) {
14768 Reset(FALSE, TRUE);
14769 SendToICS(ics_prefix);
14770 SendToICS("refresh\n");
14771 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14772 ForwardInner(forwardMostMove);
14774 pauseExamInvalid = FALSE;
14776 switch (gameMode) {
14780 pauseExamForwardMostMove = forwardMostMove;
14781 pauseExamInvalid = FALSE;
14784 case IcsPlayingWhite:
14785 case IcsPlayingBlack:
14789 case PlayFromGameFile:
14790 (void) StopLoadGameTimer();
14794 case BeginningOfGame:
14795 if (appData.icsActive) return;
14796 /* else fall through */
14797 case MachinePlaysWhite:
14798 case MachinePlaysBlack:
14799 case TwoMachinesPlay:
14800 if (forwardMostMove == 0)
14801 return; /* don't pause if no one has moved */
14802 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14803 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14804 if(onMove->pause) { // thinking engine can be paused
14805 PauseEngine(onMove); // do it
14806 if(onMove->other->pause) // pondering opponent can always be paused immediately
14807 PauseEngine(onMove->other);
14809 SendToProgram("easy\n", onMove->other);
14811 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14812 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14814 PauseEngine(&first);
14816 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14817 } else { // human on move, pause pondering by either method
14819 PauseEngine(&first);
14820 else if(appData.ponderNextMove)
14821 SendToProgram("easy\n", &first);
14824 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14834 EditCommentEvent ()
14836 char title[MSG_SIZ];
14838 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14839 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14841 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14842 WhiteOnMove(currentMove - 1) ? " " : ".. ",
14843 parseList[currentMove - 1]);
14846 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14853 char *tags = PGNTags(&gameInfo);
14855 EditTagsPopUp(tags, NULL);
14862 if(WaitForEngine(&second, StartSecond)) return;
14863 InitChessProgram(&second, FALSE);
14864 FeedMovesToProgram(&second, currentMove);
14866 SendToProgram("analyze\n", &second);
14867 second.analyzing = TRUE;
14874 if(second.analyzing) {
14875 SendToProgram("exit\n", &second);
14876 second.analyzing = FALSE;
14882 /* Toggle ShowThinking */
14884 ToggleShowThinking()
14886 appData.showThinking = !appData.showThinking;
14887 ShowThinkingEvent();
14891 AnalyzeModeEvent ()
14895 if (!first.analysisSupport) {
14896 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14897 DisplayError(buf, 0);
14900 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14901 if (appData.icsActive) {
14902 if (gameMode != IcsObserving) {
14903 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14904 DisplayError(buf, 0);
14906 if (appData.icsEngineAnalyze) {
14907 if (appData.debugMode)
14908 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14914 /* if enable, user wants to disable icsEngineAnalyze */
14915 if (appData.icsEngineAnalyze) {
14920 appData.icsEngineAnalyze = TRUE;
14921 if (appData.debugMode)
14922 fprintf(debugFP, "ICS engine analyze starting... \n");
14925 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14926 if (appData.noChessProgram || gameMode == AnalyzeMode)
14929 if (gameMode != AnalyzeFile) {
14930 if (!appData.icsEngineAnalyze) {
14932 if (gameMode != EditGame) return 0;
14934 if (!appData.showThinking) ToggleShowThinking();
14935 ResurrectChessProgram();
14936 SendToProgram("analyze\n", &first);
14937 first.analyzing = TRUE;
14938 /*first.maybeThinking = TRUE;*/
14939 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14940 EngineOutputPopUp();
14942 if (!appData.icsEngineAnalyze) {
14943 gameMode = AnalyzeMode;
14944 ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14950 StartAnalysisClock();
14951 GetTimeMark(&lastNodeCountTime);
14957 AnalyzeFileEvent ()
14959 if (appData.noChessProgram || gameMode == AnalyzeFile)
14962 if (!first.analysisSupport) {
14964 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14965 DisplayError(buf, 0);
14969 if (gameMode != AnalyzeMode) {
14970 keepInfo = 1; // mere annotating should not alter PGN tags
14973 if (gameMode != EditGame) return;
14974 if (!appData.showThinking) ToggleShowThinking();
14975 ResurrectChessProgram();
14976 SendToProgram("analyze\n", &first);
14977 first.analyzing = TRUE;
14978 /*first.maybeThinking = TRUE;*/
14979 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14980 EngineOutputPopUp();
14982 gameMode = AnalyzeFile;
14986 StartAnalysisClock();
14987 GetTimeMark(&lastNodeCountTime);
14989 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14990 AnalysisPeriodicEvent(1);
14994 MachineWhiteEvent ()
14997 char *bookHit = NULL;
14999 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
15003 if (gameMode == PlayFromGameFile ||
15004 gameMode == TwoMachinesPlay ||
15005 gameMode == Training ||
15006 gameMode == AnalyzeMode ||
15007 gameMode == EndOfGame)
15010 if (gameMode == EditPosition)
15011 EditPositionDone(TRUE);
15013 if (!WhiteOnMove(currentMove)) {
15014 DisplayError(_("It is not White's turn"), 0);
15018 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
15021 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15022 gameMode == AnalyzeFile)
15025 ResurrectChessProgram(); /* in case it isn't running */
15026 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
15027 gameMode = MachinePlaysWhite;
15030 gameMode = MachinePlaysWhite;
15034 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15036 if (first.sendName) {
15037 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
15038 SendToProgram(buf, &first);
15040 if (first.sendTime) {
15041 if (first.useColors) {
15042 SendToProgram("black\n", &first); /*gnu kludge*/
15044 SendTimeRemaining(&first, TRUE);
15046 if (first.useColors) {
15047 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
15049 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15050 SetMachineThinkingEnables();
15051 first.maybeThinking = TRUE;
15055 if (appData.autoFlipView && !flipView) {
15056 flipView = !flipView;
15057 DrawPosition(FALSE, NULL);
15058 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
15061 if(bookHit) { // [HGM] book: simulate book reply
15062 static char bookMove[MSG_SIZ]; // a bit generous?
15064 programStats.nodes = programStats.depth = programStats.time =
15065 programStats.score = programStats.got_only_move = 0;
15066 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15068 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15069 strcat(bookMove, bookHit);
15070 savedMessage = bookMove; // args for deferred call
15071 savedState = &first;
15072 ScheduleDelayedEvent(DeferredBookMove, 1);
15077 MachineBlackEvent ()
15080 char *bookHit = NULL;
15082 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
15086 if (gameMode == PlayFromGameFile ||
15087 gameMode == TwoMachinesPlay ||
15088 gameMode == Training ||
15089 gameMode == AnalyzeMode ||
15090 gameMode == EndOfGame)
15093 if (gameMode == EditPosition)
15094 EditPositionDone(TRUE);
15096 if (WhiteOnMove(currentMove)) {
15097 DisplayError(_("It is not Black's turn"), 0);
15101 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
15104 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15105 gameMode == AnalyzeFile)
15108 ResurrectChessProgram(); /* in case it isn't running */
15109 gameMode = MachinePlaysBlack;
15113 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15115 if (first.sendName) {
15116 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
15117 SendToProgram(buf, &first);
15119 if (first.sendTime) {
15120 if (first.useColors) {
15121 SendToProgram("white\n", &first); /*gnu kludge*/
15123 SendTimeRemaining(&first, FALSE);
15125 if (first.useColors) {
15126 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
15128 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15129 SetMachineThinkingEnables();
15130 first.maybeThinking = TRUE;
15133 if (appData.autoFlipView && flipView) {
15134 flipView = !flipView;
15135 DrawPosition(FALSE, NULL);
15136 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
15138 if(bookHit) { // [HGM] book: simulate book reply
15139 static char bookMove[MSG_SIZ]; // a bit generous?
15141 programStats.nodes = programStats.depth = programStats.time =
15142 programStats.score = programStats.got_only_move = 0;
15143 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15145 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15146 strcat(bookMove, bookHit);
15147 savedMessage = bookMove; // args for deferred call
15148 savedState = &first;
15149 ScheduleDelayedEvent(DeferredBookMove, 1);
15155 DisplayTwoMachinesTitle ()
15158 if (appData.matchGames > 0) {
15159 if(appData.tourneyFile[0]) {
15160 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15161 gameInfo.white, _("vs."), gameInfo.black,
15162 nextGame+1, appData.matchGames+1,
15163 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15165 if (first.twoMachinesColor[0] == 'w') {
15166 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15167 gameInfo.white, _("vs."), gameInfo.black,
15168 first.matchWins, second.matchWins,
15169 matchGame - 1 - (first.matchWins + second.matchWins));
15171 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15172 gameInfo.white, _("vs."), gameInfo.black,
15173 second.matchWins, first.matchWins,
15174 matchGame - 1 - (first.matchWins + second.matchWins));
15177 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15183 SettingsMenuIfReady ()
15185 if (second.lastPing != second.lastPong) {
15186 DisplayMessage("", _("Waiting for second chess program"));
15187 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15191 DisplayMessage("", "");
15192 SettingsPopUp(&second);
15196 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15199 if (cps->pr == NoProc) {
15200 StartChessProgram(cps);
15201 if (cps->protocolVersion == 1) {
15203 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15205 /* kludge: allow timeout for initial "feature" command */
15206 if(retry != TwoMachinesEventIfReady) FreezeUI();
15207 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15208 DisplayMessage("", buf);
15209 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15217 TwoMachinesEvent P((void))
15219 int i, move = forwardMostMove;
15221 ChessProgramState *onmove;
15222 char *bookHit = NULL;
15223 static int stalling = 0;
15227 if (appData.noChessProgram) return;
15229 switch (gameMode) {
15230 case TwoMachinesPlay:
15232 case MachinePlaysWhite:
15233 case MachinePlaysBlack:
15234 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15235 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15239 case BeginningOfGame:
15240 case PlayFromGameFile:
15243 if (gameMode != EditGame) return;
15246 EditPositionDone(TRUE);
15257 // forwardMostMove = currentMove;
15258 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15259 startingEngine = TRUE;
15261 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15263 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15264 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15265 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15269 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15271 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15272 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15273 startingEngine = matchMode = FALSE;
15274 DisplayError("second engine does not play this", 0);
15275 gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15276 EditGameEvent(); // switch back to EditGame mode
15281 InitChessProgram(&second, FALSE); // unbalances ping of second engine
15282 SendToProgram("force\n", &second);
15284 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15288 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15289 if(appData.matchPause>10000 || appData.matchPause<10)
15290 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15291 wait = SubtractTimeMarks(&now, &pauseStart);
15292 if(wait < appData.matchPause) {
15293 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15296 // we are now committed to starting the game
15298 DisplayMessage("", "");
15300 if (startedFromSetupPosition) {
15301 SendBoard(&second, backwardMostMove);
15302 if (appData.debugMode) {
15303 fprintf(debugFP, "Two Machines\n");
15306 for (i = backwardMostMove; i < forwardMostMove; i++) {
15307 SendMoveToProgram(i, &second);
15311 gameMode = TwoMachinesPlay;
15312 pausing = startingEngine = FALSE;
15313 ModeHighlight(); // [HGM] logo: this triggers display update of logos
15315 DisplayTwoMachinesTitle();
15317 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15322 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15323 SendToProgram(first.computerString, &first);
15324 if (first.sendName) {
15325 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15326 SendToProgram(buf, &first);
15329 SendToProgram(second.computerString, &second);
15330 if (second.sendName) {
15331 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15332 SendToProgram(buf, &second);
15336 if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15338 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15339 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15341 if (onmove->sendTime) {
15342 if (onmove->useColors) {
15343 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15345 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15347 if (onmove->useColors) {
15348 SendToProgram(onmove->twoMachinesColor, onmove);
15350 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15351 // SendToProgram("go\n", onmove);
15352 onmove->maybeThinking = TRUE;
15353 SetMachineThinkingEnables();
15357 if(bookHit) { // [HGM] book: simulate book reply
15358 static char bookMove[MSG_SIZ]; // a bit generous?
15360 programStats.nodes = programStats.depth = programStats.time =
15361 programStats.score = programStats.got_only_move = 0;
15362 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15364 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15365 strcat(bookMove, bookHit);
15366 savedMessage = bookMove; // args for deferred call
15367 savedState = onmove;
15368 ScheduleDelayedEvent(DeferredBookMove, 1);
15375 if (gameMode == Training) {
15376 SetTrainingModeOff();
15377 gameMode = PlayFromGameFile;
15378 DisplayMessage("", _("Training mode off"));
15380 gameMode = Training;
15381 animateTraining = appData.animate;
15383 /* make sure we are not already at the end of the game */
15384 if (currentMove < forwardMostMove) {
15385 SetTrainingModeOn();
15386 DisplayMessage("", _("Training mode on"));
15388 gameMode = PlayFromGameFile;
15389 DisplayError(_("Already at end of game"), 0);
15398 if (!appData.icsActive) return;
15399 switch (gameMode) {
15400 case IcsPlayingWhite:
15401 case IcsPlayingBlack:
15404 case BeginningOfGame:
15412 EditPositionDone(TRUE);
15425 gameMode = IcsIdle;
15435 switch (gameMode) {
15437 SetTrainingModeOff();
15439 case MachinePlaysWhite:
15440 case MachinePlaysBlack:
15441 case BeginningOfGame:
15442 SendToProgram("force\n", &first);
15443 if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15444 if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15446 abortEngineThink = TRUE;
15447 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15448 SendToProgram(buf, &first);
15449 DisplayMessage("Aborting engine think", "");
15453 SetUserThinkingEnables();
15455 case PlayFromGameFile:
15456 (void) StopLoadGameTimer();
15457 if (gameFileFP != NULL) {
15462 EditPositionDone(TRUE);
15467 SendToProgram("force\n", &first);
15469 case TwoMachinesPlay:
15470 GameEnds(EndOfFile, NULL, GE_PLAYER);
15471 ResurrectChessProgram();
15472 SetUserThinkingEnables();
15475 ResurrectChessProgram();
15477 case IcsPlayingBlack:
15478 case IcsPlayingWhite:
15479 DisplayError(_("Warning: You are still playing a game"), 0);
15482 DisplayError(_("Warning: You are still observing a game"), 0);
15485 DisplayError(_("Warning: You are still examining a game"), 0);
15496 first.offeredDraw = second.offeredDraw = 0;
15498 if (gameMode == PlayFromGameFile) {
15499 whiteTimeRemaining = timeRemaining[0][currentMove];
15500 blackTimeRemaining = timeRemaining[1][currentMove];
15504 if (gameMode == MachinePlaysWhite ||
15505 gameMode == MachinePlaysBlack ||
15506 gameMode == TwoMachinesPlay ||
15507 gameMode == EndOfGame) {
15508 i = forwardMostMove;
15509 while (i > currentMove) {
15510 SendToProgram("undo\n", &first);
15513 if(!adjustedClock) {
15514 whiteTimeRemaining = timeRemaining[0][currentMove];
15515 blackTimeRemaining = timeRemaining[1][currentMove];
15516 DisplayBothClocks();
15518 if (whiteFlag || blackFlag) {
15519 whiteFlag = blackFlag = 0;
15524 gameMode = EditGame;
15530 EditPositionEvent ()
15533 if (gameMode == EditPosition) {
15539 if (gameMode != EditGame) return;
15541 gameMode = EditPosition;
15544 CopyBoard(rightsBoard, nullBoard);
15545 if (currentMove > 0)
15546 CopyBoard(boards[0], boards[currentMove]);
15547 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15548 rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15550 blackPlaysFirst = !WhiteOnMove(currentMove);
15552 currentMove = forwardMostMove = backwardMostMove = 0;
15553 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15555 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15561 /* [DM] icsEngineAnalyze - possible call from other functions */
15562 if (appData.icsEngineAnalyze) {
15563 appData.icsEngineAnalyze = FALSE;
15565 DisplayMessage("",_("Close ICS engine analyze..."));
15567 if (first.analysisSupport && first.analyzing) {
15568 SendToBoth("exit\n");
15569 first.analyzing = second.analyzing = FALSE;
15571 thinkOutput[0] = NULLCHAR;
15575 EditPositionDone (Boolean fakeRights)
15577 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15579 startedFromSetupPosition = TRUE;
15580 InitChessProgram(&first, FALSE);
15581 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15583 boards[0][EP_STATUS] = EP_NONE;
15584 for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15585 for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15586 if(rightsBoard[r][f]) {
15587 ChessSquare p = boards[0][r][f];
15588 if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15589 else if(p == king) boards[0][CASTLING][2] = f;
15590 else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15591 else rightsBoard[r][f] = 2; // mark for second pass
15594 for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15595 if(rightsBoard[r][f] == 2) {
15596 ChessSquare p = boards[0][r][f];
15597 if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15598 if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15602 SendToProgram("force\n", &first);
15603 if (blackPlaysFirst) {
15604 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15605 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15606 currentMove = forwardMostMove = backwardMostMove = 1;
15607 CopyBoard(boards[1], boards[0]);
15609 currentMove = forwardMostMove = backwardMostMove = 0;
15611 SendBoard(&first, forwardMostMove);
15612 if (appData.debugMode) {
15613 fprintf(debugFP, "EditPosDone\n");
15616 DisplayMessage("", "");
15617 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15618 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15619 gameMode = EditGame;
15621 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15622 ClearHighlights(); /* [AS] */
15625 /* Pause for `ms' milliseconds */
15626 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15628 TimeDelay (long ms)
15635 } while (SubtractTimeMarks(&m2, &m1) < ms);
15638 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15640 SendMultiLineToICS (char *buf)
15642 char temp[MSG_SIZ+1], *p;
15649 strncpy(temp, buf, len);
15654 if (*p == '\n' || *p == '\r')
15659 strcat(temp, "\n");
15661 SendToPlayer(temp, strlen(temp));
15665 SetWhiteToPlayEvent ()
15667 if (gameMode == EditPosition) {
15668 blackPlaysFirst = FALSE;
15669 DisplayBothClocks(); /* works because currentMove is 0 */
15670 } else if (gameMode == IcsExamining) {
15671 SendToICS(ics_prefix);
15672 SendToICS("tomove white\n");
15677 SetBlackToPlayEvent ()
15679 if (gameMode == EditPosition) {
15680 blackPlaysFirst = TRUE;
15681 currentMove = 1; /* kludge */
15682 DisplayBothClocks();
15684 } else if (gameMode == IcsExamining) {
15685 SendToICS(ics_prefix);
15686 SendToICS("tomove black\n");
15691 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15694 ChessSquare piece = boards[0][y][x];
15695 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15696 static int lastVariant;
15697 int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15699 if (gameMode != EditPosition && gameMode != IcsExamining) return;
15701 switch (selection) {
15703 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15704 MarkTargetSquares(1);
15705 CopyBoard(currentBoard, boards[0]);
15706 CopyBoard(menuBoard, initialPosition);
15707 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15708 SendToICS(ics_prefix);
15709 SendToICS("bsetup clear\n");
15710 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15711 SendToICS(ics_prefix);
15712 SendToICS("clearboard\n");
15715 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15716 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15717 for (y = 0; y < BOARD_HEIGHT; y++) {
15718 if (gameMode == IcsExamining) {
15719 if (boards[currentMove][y][x] != EmptySquare) {
15720 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15724 } else if(boards[0][y][x] != DarkSquare) {
15725 if(boards[0][y][x] != p) nonEmpty++;
15726 boards[0][y][x] = p;
15730 CopyBoard(rightsBoard, nullBoard);
15731 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15733 for(r = 0; r < BOARD_HEIGHT; r++) {
15734 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
15735 ChessSquare p = menuBoard[r][x];
15736 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15739 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15740 DisplayMessage("Clicking clock again restores position", "");
15741 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15742 if(!nonEmpty) { // asked to clear an empty board
15743 CopyBoard(boards[0], menuBoard);
15745 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15746 CopyBoard(boards[0], initialPosition);
15748 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15749 && !CompareBoards(nullBoard, erasedBoard)) {
15750 CopyBoard(boards[0], erasedBoard);
15752 CopyBoard(erasedBoard, currentBoard);
15754 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15755 rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15758 if (gameMode == EditPosition) {
15759 DrawPosition(FALSE, boards[0]);
15764 SetWhiteToPlayEvent();
15768 SetBlackToPlayEvent();
15772 if (gameMode == IcsExamining) {
15773 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15774 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15777 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15778 if(x == BOARD_LEFT-2) {
15779 if(y < handSize-1-gameInfo.holdingsSize) break;
15780 boards[0][y][1] = 0;
15782 if(x == BOARD_RGHT+1) {
15783 if(y >= gameInfo.holdingsSize) break;
15784 boards[0][y][BOARD_WIDTH-2] = 0;
15787 boards[0][y][x] = EmptySquare;
15788 DrawPosition(FALSE, boards[0]);
15793 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15794 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
15795 selection = (ChessSquare) (PROMOTED(piece));
15796 } else if(piece == EmptySquare) selection = WhiteSilver;
15797 else selection = (ChessSquare)((int)piece - 1);
15801 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15802 piece > (int)BlackMan && piece <= (int)BlackKing ) {
15803 selection = (ChessSquare) (DEMOTED(piece));
15804 } else if(piece == EmptySquare) selection = BlackSilver;
15805 else selection = (ChessSquare)((int)piece + 1);
15810 if(gameInfo.variant == VariantShatranj ||
15811 gameInfo.variant == VariantXiangqi ||
15812 gameInfo.variant == VariantCourier ||
15813 gameInfo.variant == VariantASEAN ||
15814 gameInfo.variant == VariantMakruk )
15815 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15821 if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15822 if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15828 if(gameInfo.variant == VariantXiangqi)
15829 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15830 if(gameInfo.variant == VariantKnightmate)
15831 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15832 if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15835 if (gameMode == IcsExamining) {
15836 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15837 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15838 PieceToChar(selection), AAA + x, ONE + y);
15841 rightsBoard[y][x] = hasRights;
15842 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15844 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15845 n = PieceToNumber(selection - BlackPawn);
15846 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15847 boards[0][handSize-1-n][0] = selection;
15848 boards[0][handSize-1-n][1]++;
15850 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15851 n = PieceToNumber(selection);
15852 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15853 boards[0][n][BOARD_WIDTH-1] = selection;
15854 boards[0][n][BOARD_WIDTH-2]++;
15857 boards[0][y][x] = selection;
15858 DrawPosition(TRUE, boards[0]);
15860 fromX = fromY = -1;
15868 DropMenuEvent (ChessSquare selection, int x, int y)
15870 ChessMove moveType;
15872 switch (gameMode) {
15873 case IcsPlayingWhite:
15874 case MachinePlaysBlack:
15875 if (!WhiteOnMove(currentMove)) {
15876 DisplayMoveError(_("It is Black's turn"));
15879 moveType = WhiteDrop;
15881 case IcsPlayingBlack:
15882 case MachinePlaysWhite:
15883 if (WhiteOnMove(currentMove)) {
15884 DisplayMoveError(_("It is White's turn"));
15887 moveType = BlackDrop;
15890 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15896 if (moveType == BlackDrop && selection < BlackPawn) {
15897 selection = (ChessSquare) ((int) selection
15898 + (int) BlackPawn - (int) WhitePawn);
15900 if (boards[currentMove][y][x] != EmptySquare) {
15901 DisplayMoveError(_("That square is occupied"));
15905 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15911 /* Accept a pending offer of any kind from opponent */
15913 if (appData.icsActive) {
15914 SendToICS(ics_prefix);
15915 SendToICS("accept\n");
15916 } else if (cmailMsgLoaded) {
15917 if (currentMove == cmailOldMove &&
15918 commentList[cmailOldMove] != NULL &&
15919 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15920 "Black offers a draw" : "White offers a draw")) {
15922 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15923 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15925 DisplayError(_("There is no pending offer on this move"), 0);
15926 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15929 /* Not used for offers from chess program */
15936 /* Decline a pending offer of any kind from opponent */
15938 if (appData.icsActive) {
15939 SendToICS(ics_prefix);
15940 SendToICS("decline\n");
15941 } else if (cmailMsgLoaded) {
15942 if (currentMove == cmailOldMove &&
15943 commentList[cmailOldMove] != NULL &&
15944 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15945 "Black offers a draw" : "White offers a draw")) {
15947 AppendComment(cmailOldMove, "Draw declined", TRUE);
15948 DisplayComment(cmailOldMove - 1, "Draw declined");
15951 DisplayError(_("There is no pending offer on this move"), 0);
15954 /* Not used for offers from chess program */
15961 /* Issue ICS rematch command */
15962 if (appData.icsActive) {
15963 SendToICS(ics_prefix);
15964 SendToICS("rematch\n");
15971 /* Call your opponent's flag (claim a win on time) */
15972 if (appData.icsActive) {
15973 SendToICS(ics_prefix);
15974 SendToICS("flag\n");
15976 switch (gameMode) {
15979 case MachinePlaysWhite:
15982 GameEnds(GameIsDrawn, "Both players ran out of time",
15985 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15987 DisplayError(_("Your opponent is not out of time"), 0);
15990 case MachinePlaysBlack:
15993 GameEnds(GameIsDrawn, "Both players ran out of time",
15996 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15998 DisplayError(_("Your opponent is not out of time"), 0);
16006 ClockClick (int which)
16007 { // [HGM] code moved to back-end from winboard.c
16008 if(which) { // black clock
16009 if (gameMode == EditPosition || gameMode == IcsExamining) {
16010 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
16011 SetBlackToPlayEvent();
16012 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
16013 gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
16014 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
16015 } else if (shiftKey) {
16016 AdjustClock(which, -1);
16017 } else if (gameMode == IcsPlayingWhite ||
16018 gameMode == MachinePlaysBlack) {
16021 } else { // white clock
16022 if (gameMode == EditPosition || gameMode == IcsExamining) {
16023 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
16024 SetWhiteToPlayEvent();
16025 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
16026 gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
16027 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
16028 } else if (shiftKey) {
16029 AdjustClock(which, -1);
16030 } else if (gameMode == IcsPlayingBlack ||
16031 gameMode == MachinePlaysWhite) {
16040 /* Offer draw or accept pending draw offer from opponent */
16042 if (appData.icsActive) {
16043 /* Note: tournament rules require draw offers to be
16044 made after you make your move but before you punch
16045 your clock. Currently ICS doesn't let you do that;
16046 instead, you immediately punch your clock after making
16047 a move, but you can offer a draw at any time. */
16049 SendToICS(ics_prefix);
16050 SendToICS("draw\n");
16051 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
16052 } else if (cmailMsgLoaded) {
16053 if (currentMove == cmailOldMove &&
16054 commentList[cmailOldMove] != NULL &&
16055 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
16056 "Black offers a draw" : "White offers a draw")) {
16057 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
16058 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
16059 } else if (currentMove == cmailOldMove + 1) {
16060 char *offer = WhiteOnMove(cmailOldMove) ?
16061 "White offers a draw" : "Black offers a draw";
16062 AppendComment(currentMove, offer, TRUE);
16063 DisplayComment(currentMove - 1, offer);
16064 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
16066 DisplayError(_("You must make your move before offering a draw"), 0);
16067 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
16069 } else if (first.offeredDraw) {
16070 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
16072 if (first.sendDrawOffers) {
16073 SendToProgram("draw\n", &first);
16074 userOfferedDraw = TRUE;
16082 /* Offer Adjourn or accept pending Adjourn offer from opponent */
16084 if (appData.icsActive) {
16085 SendToICS(ics_prefix);
16086 SendToICS("adjourn\n");
16088 /* Currently GNU Chess doesn't offer or accept Adjourns */
16096 /* Offer Abort or accept pending Abort offer from opponent */
16098 if (appData.icsActive) {
16099 SendToICS(ics_prefix);
16100 SendToICS("abort\n");
16102 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
16109 /* Resign. You can do this even if it's not your turn. */
16111 if (appData.icsActive) {
16112 SendToICS(ics_prefix);
16113 SendToICS("resign\n");
16115 switch (gameMode) {
16116 case MachinePlaysWhite:
16117 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16119 case MachinePlaysBlack:
16120 GameEnds(BlackWins, "White resigns", GE_PLAYER);
16123 if (cmailMsgLoaded) {
16125 if (WhiteOnMove(cmailOldMove)) {
16126 GameEnds(BlackWins, "White resigns", GE_PLAYER);
16128 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16130 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
16141 StopObservingEvent ()
16143 /* Stop observing current games */
16144 SendToICS(ics_prefix);
16145 SendToICS("unobserve\n");
16149 StopExaminingEvent ()
16151 /* Stop observing current game */
16152 SendToICS(ics_prefix);
16153 SendToICS("unexamine\n");
16157 ForwardInner (int target)
16159 int limit; int oldSeekGraphUp = seekGraphUp;
16161 if (appData.debugMode)
16162 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16163 target, currentMove, forwardMostMove);
16165 if (gameMode == EditPosition)
16168 seekGraphUp = FALSE;
16169 MarkTargetSquares(1);
16170 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16172 if (gameMode == PlayFromGameFile && !pausing)
16175 if (gameMode == IcsExamining && pausing)
16176 limit = pauseExamForwardMostMove;
16178 limit = forwardMostMove;
16180 if (target > limit) target = limit;
16182 if (target > 0 && moveList[target - 1][0]) {
16183 int fromX, fromY, toX, toY;
16184 toX = moveList[target - 1][2] - AAA;
16185 toY = moveList[target - 1][3] - ONE;
16186 if (moveList[target - 1][1] == '@') {
16187 if (appData.highlightLastMove) {
16188 SetHighlights(-1, -1, toX, toY);
16191 fromX = moveList[target - 1][0] - AAA;
16192 fromY = moveList[target - 1][1] - ONE;
16193 if (target == currentMove + 1) {
16194 if(moveList[target - 1][4] == ';') { // multi-leg
16195 killX = moveList[target - 1][5] - AAA;
16196 killY = moveList[target - 1][6] - ONE;
16198 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16199 killX = killY = -1;
16201 if (appData.highlightLastMove) {
16202 SetHighlights(fromX, fromY, toX, toY);
16206 if (gameMode == EditGame || gameMode == AnalyzeMode ||
16207 gameMode == Training || gameMode == PlayFromGameFile ||
16208 gameMode == AnalyzeFile) {
16209 while (currentMove < target) {
16210 if(second.analyzing) SendMoveToProgram(currentMove, &second);
16211 SendMoveToProgram(currentMove++, &first);
16214 currentMove = target;
16217 if (gameMode == EditGame || gameMode == EndOfGame) {
16218 whiteTimeRemaining = timeRemaining[0][currentMove];
16219 blackTimeRemaining = timeRemaining[1][currentMove];
16221 DisplayBothClocks();
16222 DisplayMove(currentMove - 1);
16223 DrawPosition(oldSeekGraphUp, boards[currentMove]);
16224 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16225 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16226 DisplayComment(currentMove - 1, commentList[currentMove]);
16228 ClearMap(); // [HGM] exclude: invalidate map
16235 if (gameMode == IcsExamining && !pausing) {
16236 SendToICS(ics_prefix);
16237 SendToICS("forward\n");
16239 ForwardInner(currentMove + 1);
16246 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16247 /* to optimze, we temporarily turn off analysis mode while we feed
16248 * the remaining moves to the engine. Otherwise we get analysis output
16251 if (first.analysisSupport) {
16252 SendToProgram("exit\nforce\n", &first);
16253 first.analyzing = FALSE;
16257 if (gameMode == IcsExamining && !pausing) {
16258 SendToICS(ics_prefix);
16259 SendToICS("forward 999999\n");
16261 ForwardInner(forwardMostMove);
16264 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16265 /* we have fed all the moves, so reactivate analysis mode */
16266 SendToProgram("analyze\n", &first);
16267 first.analyzing = TRUE;
16268 /*first.maybeThinking = TRUE;*/
16269 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16274 BackwardInner (int target)
16276 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16278 if (appData.debugMode)
16279 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16280 target, currentMove, forwardMostMove);
16282 if (gameMode == EditPosition) return;
16283 seekGraphUp = FALSE;
16284 MarkTargetSquares(1);
16285 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16286 if (currentMove <= backwardMostMove) {
16288 DrawPosition(full_redraw, boards[currentMove]);
16291 if (gameMode == PlayFromGameFile && !pausing)
16294 if (moveList[target][0]) {
16295 int fromX, fromY, toX, toY;
16296 toX = moveList[target][2] - AAA;
16297 toY = moveList[target][3] - ONE;
16298 if (moveList[target][1] == '@') {
16299 if (appData.highlightLastMove) {
16300 SetHighlights(-1, -1, toX, toY);
16303 fromX = moveList[target][0] - AAA;
16304 fromY = moveList[target][1] - ONE;
16305 if (target == currentMove - 1) {
16306 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16308 if (appData.highlightLastMove) {
16309 SetHighlights(fromX, fromY, toX, toY);
16313 if (gameMode == EditGame || gameMode==AnalyzeMode ||
16314 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16315 while (currentMove > target) {
16316 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16317 // null move cannot be undone. Reload program with move history before it.
16319 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16320 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16322 SendBoard(&first, i);
16323 if(second.analyzing) SendBoard(&second, i);
16324 for(currentMove=i; currentMove<target; currentMove++) {
16325 SendMoveToProgram(currentMove, &first);
16326 if(second.analyzing) SendMoveToProgram(currentMove, &second);
16330 SendToBoth("undo\n");
16334 currentMove = target;
16337 if (gameMode == EditGame || gameMode == EndOfGame) {
16338 whiteTimeRemaining = timeRemaining[0][currentMove];
16339 blackTimeRemaining = timeRemaining[1][currentMove];
16341 DisplayBothClocks();
16342 DisplayMove(currentMove - 1);
16343 DrawPosition(full_redraw, boards[currentMove]);
16344 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16345 // [HGM] PV info: routine tests if comment empty
16346 DisplayComment(currentMove - 1, commentList[currentMove]);
16347 ClearMap(); // [HGM] exclude: invalidate map
16353 if (gameMode == IcsExamining && !pausing) {
16354 SendToICS(ics_prefix);
16355 SendToICS("backward\n");
16357 BackwardInner(currentMove - 1);
16364 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16365 /* to optimize, we temporarily turn off analysis mode while we undo
16366 * all the moves. Otherwise we get analysis output after each undo.
16368 if (first.analysisSupport) {
16369 SendToProgram("exit\nforce\n", &first);
16370 first.analyzing = FALSE;
16374 if (gameMode == IcsExamining && !pausing) {
16375 SendToICS(ics_prefix);
16376 SendToICS("backward 999999\n");
16378 BackwardInner(backwardMostMove);
16381 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16382 /* we have fed all the moves, so reactivate analysis mode */
16383 SendToProgram("analyze\n", &first);
16384 first.analyzing = TRUE;
16385 /*first.maybeThinking = TRUE;*/
16386 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16393 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16394 if (to >= forwardMostMove) to = forwardMostMove;
16395 if (to <= backwardMostMove) to = backwardMostMove;
16396 if (to < currentMove) {
16404 RevertEvent (Boolean annotate)
16406 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16409 if (gameMode != IcsExamining) {
16410 DisplayError(_("You are not examining a game"), 0);
16414 DisplayError(_("You can't revert while pausing"), 0);
16417 SendToICS(ics_prefix);
16418 SendToICS("revert\n");
16422 RetractMoveEvent ()
16424 switch (gameMode) {
16425 case MachinePlaysWhite:
16426 case MachinePlaysBlack:
16427 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16428 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16431 if (forwardMostMove < 2) return;
16432 currentMove = forwardMostMove = forwardMostMove - 2;
16433 whiteTimeRemaining = timeRemaining[0][currentMove];
16434 blackTimeRemaining = timeRemaining[1][currentMove];
16435 DisplayBothClocks();
16436 DisplayMove(currentMove - 1);
16437 ClearHighlights();/*!! could figure this out*/
16438 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16439 SendToProgram("remove\n", &first);
16440 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16443 case BeginningOfGame:
16447 case IcsPlayingWhite:
16448 case IcsPlayingBlack:
16449 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16450 SendToICS(ics_prefix);
16451 SendToICS("takeback 2\n");
16453 SendToICS(ics_prefix);
16454 SendToICS("takeback 1\n");
16463 ChessProgramState *cps;
16465 switch (gameMode) {
16466 case MachinePlaysWhite:
16467 if (!WhiteOnMove(forwardMostMove)) {
16468 DisplayError(_("It is your turn"), 0);
16473 case MachinePlaysBlack:
16474 if (WhiteOnMove(forwardMostMove)) {
16475 DisplayError(_("It is your turn"), 0);
16480 case TwoMachinesPlay:
16481 if (WhiteOnMove(forwardMostMove) ==
16482 (first.twoMachinesColor[0] == 'w')) {
16488 case BeginningOfGame:
16492 SendToProgram("?\n", cps);
16496 TruncateGameEvent ()
16499 if (gameMode != EditGame) return;
16506 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16507 if (forwardMostMove > currentMove) {
16508 if (gameInfo.resultDetails != NULL) {
16509 free(gameInfo.resultDetails);
16510 gameInfo.resultDetails = NULL;
16511 gameInfo.result = GameUnfinished;
16513 forwardMostMove = currentMove;
16514 HistorySet(parseList, backwardMostMove, forwardMostMove,
16522 if (appData.noChessProgram) return;
16523 switch (gameMode) {
16524 case MachinePlaysWhite:
16525 if (WhiteOnMove(forwardMostMove)) {
16526 DisplayError(_("Wait until your turn."), 0);
16530 case BeginningOfGame:
16531 case MachinePlaysBlack:
16532 if (!WhiteOnMove(forwardMostMove)) {
16533 DisplayError(_("Wait until your turn."), 0);
16538 DisplayError(_("No hint available"), 0);
16541 SendToProgram("hint\n", &first);
16542 hintRequested = TRUE;
16546 SaveSelected (FILE *g, int dummy, char *dummy2)
16548 ListGame * lg = (ListGame *) gameList.head;
16552 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16553 DisplayError(_("Game list not loaded or empty"), 0);
16557 creatingBook = TRUE; // suppresses stuff during load game
16559 /* Get list size */
16560 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16561 if(lg->position >= 0) { // selected?
16562 LoadGame(f, nItem, "", TRUE);
16563 SaveGamePGN2(g); // leaves g open
16566 lg = (ListGame *) lg->node.succ;
16570 creatingBook = FALSE;
16578 ListGame * lg = (ListGame *) gameList.head;
16581 static int secondTime = FALSE;
16583 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16584 DisplayError(_("Game list not loaded or empty"), 0);
16588 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16591 DisplayNote(_("Book file exists! Try again for overwrite."));
16595 creatingBook = TRUE;
16596 secondTime = FALSE;
16598 /* Get list size */
16599 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16600 if(lg->position >= 0) {
16601 LoadGame(f, nItem, "", TRUE);
16602 AddGameToBook(TRUE);
16605 lg = (ListGame *) lg->node.succ;
16608 creatingBook = FALSE;
16615 if (appData.noChessProgram) return;
16616 switch (gameMode) {
16617 case MachinePlaysWhite:
16618 if (WhiteOnMove(forwardMostMove)) {
16619 DisplayError(_("Wait until your turn."), 0);
16623 case BeginningOfGame:
16624 case MachinePlaysBlack:
16625 if (!WhiteOnMove(forwardMostMove)) {
16626 DisplayError(_("Wait until your turn."), 0);
16631 EditPositionDone(TRUE);
16633 case TwoMachinesPlay:
16638 SendToProgram("bk\n", &first);
16639 bookOutput[0] = NULLCHAR;
16640 bookRequested = TRUE;
16646 char *tags = PGNTags(&gameInfo);
16647 TagsPopUp(tags, CmailMsg());
16651 /* end button procedures */
16654 PrintPosition (FILE *fp, int move)
16658 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16659 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16660 char c = PieceToChar(boards[move][i][j]);
16661 fputc(c == '?' ? '.' : c, fp);
16662 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16665 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16666 fprintf(fp, "white to play\n");
16668 fprintf(fp, "black to play\n");
16672 PrintOpponents (FILE *fp)
16674 if (gameInfo.white != NULL) {
16675 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16681 /* Find last component of program's own name, using some heuristics */
16683 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16686 int local = (strcmp(host, "localhost") == 0);
16687 while (!local && (p = strchr(prog, ';')) != NULL) {
16689 while (*p == ' ') p++;
16692 if (*prog == '"' || *prog == '\'') {
16693 q = strchr(prog + 1, *prog);
16695 q = strchr(prog, ' ');
16697 if (q == NULL) q = prog + strlen(prog);
16699 while (p >= prog && *p != '/' && *p != '\\') p--;
16701 if(p == prog && *p == '"') p++;
16703 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16704 memcpy(buf, p, q - p);
16705 buf[q - p] = NULLCHAR;
16713 TimeControlTagValue ()
16716 if (!appData.clockMode) {
16717 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16718 } else if (movesPerSession > 0) {
16719 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16720 } else if (timeIncrement == 0) {
16721 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16723 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16725 return StrSave(buf);
16731 /* This routine is used only for certain modes */
16732 VariantClass v = gameInfo.variant;
16733 ChessMove r = GameUnfinished;
16736 if(keepInfo) return;
16738 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16739 r = gameInfo.result;
16740 p = gameInfo.resultDetails;
16741 gameInfo.resultDetails = NULL;
16743 ClearGameInfo(&gameInfo);
16744 gameInfo.variant = v;
16746 switch (gameMode) {
16747 case MachinePlaysWhite:
16748 gameInfo.event = StrSave( appData.pgnEventHeader );
16749 gameInfo.site = StrSave(HostName());
16750 gameInfo.date = PGNDate();
16751 gameInfo.round = StrSave("-");
16752 gameInfo.white = StrSave(first.tidy);
16753 gameInfo.black = StrSave(UserName());
16754 gameInfo.timeControl = TimeControlTagValue();
16757 case MachinePlaysBlack:
16758 gameInfo.event = StrSave( appData.pgnEventHeader );
16759 gameInfo.site = StrSave(HostName());
16760 gameInfo.date = PGNDate();
16761 gameInfo.round = StrSave("-");
16762 gameInfo.white = StrSave(UserName());
16763 gameInfo.black = StrSave(first.tidy);
16764 gameInfo.timeControl = TimeControlTagValue();
16767 case TwoMachinesPlay:
16768 gameInfo.event = StrSave( appData.pgnEventHeader );
16769 gameInfo.site = StrSave(HostName());
16770 gameInfo.date = PGNDate();
16773 snprintf(buf, MSG_SIZ, "%d", roundNr);
16774 gameInfo.round = StrSave(buf);
16776 gameInfo.round = StrSave("-");
16778 if (first.twoMachinesColor[0] == 'w') {
16779 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16780 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16782 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16783 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16785 gameInfo.timeControl = TimeControlTagValue();
16789 gameInfo.event = StrSave("Edited game");
16790 gameInfo.site = StrSave(HostName());
16791 gameInfo.date = PGNDate();
16792 gameInfo.round = StrSave("-");
16793 gameInfo.white = StrSave("-");
16794 gameInfo.black = StrSave("-");
16795 gameInfo.result = r;
16796 gameInfo.resultDetails = p;
16800 gameInfo.event = StrSave("Edited position");
16801 gameInfo.site = StrSave(HostName());
16802 gameInfo.date = PGNDate();
16803 gameInfo.round = StrSave("-");
16804 gameInfo.white = StrSave("-");
16805 gameInfo.black = StrSave("-");
16808 case IcsPlayingWhite:
16809 case IcsPlayingBlack:
16814 case PlayFromGameFile:
16815 gameInfo.event = StrSave("Game from non-PGN file");
16816 gameInfo.site = StrSave(HostName());
16817 gameInfo.date = PGNDate();
16818 gameInfo.round = StrSave("-");
16819 gameInfo.white = StrSave("?");
16820 gameInfo.black = StrSave("?");
16829 ReplaceComment (int index, char *text)
16835 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16836 pvInfoList[index-1].depth == len &&
16837 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16838 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16839 while (*text == '\n') text++;
16840 len = strlen(text);
16841 while (len > 0 && text[len - 1] == '\n') len--;
16843 if (commentList[index] != NULL)
16844 free(commentList[index]);
16847 commentList[index] = NULL;
16850 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16851 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16852 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16853 commentList[index] = (char *) malloc(len + 2);
16854 strncpy(commentList[index], text, len);
16855 commentList[index][len] = '\n';
16856 commentList[index][len + 1] = NULLCHAR;
16858 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16860 commentList[index] = (char *) malloc(len + 7);
16861 safeStrCpy(commentList[index], "{\n", 3);
16862 safeStrCpy(commentList[index]+2, text, len+1);
16863 commentList[index][len+2] = NULLCHAR;
16864 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16865 strcat(commentList[index], "\n}\n");
16870 CrushCRs (char *text)
16878 if (ch == '\r') continue;
16880 } while (ch != '\0');
16884 AppendComment (int index, char *text, Boolean addBraces)
16885 /* addBraces tells if we should add {} */
16890 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16891 if(addBraces == 3) addBraces = 0; else // force appending literally
16892 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16895 while (*text == '\n') text++;
16896 len = strlen(text);
16897 while (len > 0 && text[len - 1] == '\n') len--;
16898 text[len] = NULLCHAR;
16900 if (len == 0) return;
16902 if (commentList[index] != NULL) {
16903 Boolean addClosingBrace = addBraces;
16904 old = commentList[index];
16905 oldlen = strlen(old);
16906 while(commentList[index][oldlen-1] == '\n')
16907 commentList[index][--oldlen] = NULLCHAR;
16908 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16909 safeStrCpy(commentList[index], old, oldlen + len + 6);
16911 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16912 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16913 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16914 while (*text == '\n') { text++; len--; }
16915 commentList[index][--oldlen] = NULLCHAR;
16917 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16918 else strcat(commentList[index], "\n");
16919 strcat(commentList[index], text);
16920 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16921 else strcat(commentList[index], "\n");
16923 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16925 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16926 else commentList[index][0] = NULLCHAR;
16927 strcat(commentList[index], text);
16928 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16929 if(addBraces == TRUE) strcat(commentList[index], "}\n");
16934 FindStr (char * text, char * sub_text)
16936 char * result = strstr( text, sub_text );
16938 if( result != NULL ) {
16939 result += strlen( sub_text );
16945 /* [AS] Try to extract PV info from PGN comment */
16946 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16948 GetInfoFromComment (int index, char * text)
16950 char * sep = text, *p;
16952 if( text != NULL && index > 0 ) {
16955 int time = -1, sec = 0, deci;
16956 char * s_eval = FindStr( text, "[%eval " );
16957 char * s_emt = FindStr( text, "[%emt " );
16959 if( s_eval != NULL || s_emt != NULL ) {
16961 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16966 if( s_eval != NULL ) {
16967 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16971 if( delim != ']' ) {
16976 if( s_emt != NULL ) {
16981 /* We expect something like: [+|-]nnn.nn/dd */
16984 if(*text != '{') return text; // [HGM] braces: must be normal comment
16986 sep = strchr( text, '/' );
16987 if( sep == NULL || sep < (text+4) ) {
16992 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16993 if(p[1] == '(') { // comment starts with PV
16994 p = strchr(p, ')'); // locate end of PV
16995 if(p == NULL || sep < p+5) return text;
16996 // at this point we have something like "{(.*) +0.23/6 ..."
16997 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16998 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16999 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
17001 time = -1; sec = -1; deci = -1;
17002 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
17003 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
17004 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
17005 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
17009 if( score_lo < 0 || score_lo >= 100 ) {
17013 if(sec >= 0) time = 600*time + 10*sec; else
17014 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
17016 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
17018 /* [HGM] PV time: now locate end of PV info */
17019 while( *++sep >= '0' && *sep <= '9'); // strip depth
17021 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
17023 while( *++sep >= '0' && *sep <= '9'); // strip seconds
17025 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
17026 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
17037 pvInfoList[index-1].depth = depth;
17038 pvInfoList[index-1].score = score;
17039 pvInfoList[index-1].time = 10*time; // centi-sec
17040 if(*sep == '}') *sep = 0; else *--sep = '{';
17042 while(*p++ = *sep++)
17045 } // squeeze out space between PV and comment, and return both
17051 SendToProgram (char *message, ChessProgramState *cps)
17053 int count, outCount, error;
17056 if (cps->pr == NoProc) return;
17059 if (appData.debugMode) {
17062 fprintf(debugFP, "%ld >%-6s: %s",
17063 SubtractTimeMarks(&now, &programStartTime),
17064 cps->which, message);
17066 fprintf(serverFP, "%ld >%-6s: %s",
17067 SubtractTimeMarks(&now, &programStartTime),
17068 cps->which, message), fflush(serverFP);
17071 count = strlen(message);
17072 outCount = OutputToProcess(cps->pr, message, count, &error);
17073 if (outCount < count && !exiting
17074 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
17075 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
17076 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
17077 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17078 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17079 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17080 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17081 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17083 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17084 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17085 gameInfo.result = res;
17087 gameInfo.resultDetails = StrSave(buf);
17089 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17090 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17095 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
17099 ChessProgramState *cps = (ChessProgramState *)closure;
17101 if (isr != cps->isr) return; /* Killed intentionally */
17104 RemoveInputSource(cps->isr);
17105 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
17106 _(cps->which), cps->program);
17107 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
17108 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17109 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17110 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17111 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17112 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17114 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17115 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17116 gameInfo.result = res;
17118 gameInfo.resultDetails = StrSave(buf);
17120 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17121 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
17123 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
17124 _(cps->which), cps->program);
17125 RemoveInputSource(cps->isr);
17127 /* [AS] Program is misbehaving badly... kill it */
17128 if( count == -2 ) {
17129 DestroyChildProcess( cps->pr, 9 );
17133 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17138 if ((end_str = strchr(message, '\r')) != NULL)
17139 *end_str = NULLCHAR;
17140 if ((end_str = strchr(message, '\n')) != NULL)
17141 *end_str = NULLCHAR;
17143 if (appData.debugMode) {
17144 TimeMark now; int print = 1;
17145 char *quote = ""; char c; int i;
17147 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17148 char start = message[0];
17149 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17150 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17151 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
17152 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17153 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17154 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
17155 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17156 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
17157 sscanf(message, "hint: %c", &c)!=1 &&
17158 sscanf(message, "pong %c", &c)!=1 && start != '#') {
17159 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17160 print = (appData.engineComments >= 2);
17162 message[0] = start; // restore original message
17166 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17167 SubtractTimeMarks(&now, &programStartTime), cps->which,
17171 fprintf(serverFP, "%ld <%-6s: %s%s\n",
17172 SubtractTimeMarks(&now, &programStartTime), cps->which,
17174 message), fflush(serverFP);
17178 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17179 if (appData.icsEngineAnalyze) {
17180 if (strstr(message, "whisper") != NULL ||
17181 strstr(message, "kibitz") != NULL ||
17182 strstr(message, "tellics") != NULL) return;
17185 HandleMachineMove(message, cps);
17190 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17195 if( timeControl_2 > 0 ) {
17196 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17197 tc = timeControl_2;
17200 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17201 inc /= cps->timeOdds;
17202 st /= cps->timeOdds;
17204 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17207 /* Set exact time per move, normally using st command */
17208 if (cps->stKludge) {
17209 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17211 if (seconds == 0) {
17212 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17214 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17217 snprintf(buf, MSG_SIZ, "st %d\n", st);
17220 /* Set conventional or incremental time control, using level command */
17221 if (seconds == 0) {
17222 /* Note old gnuchess bug -- minutes:seconds used to not work.
17223 Fixed in later versions, but still avoid :seconds
17224 when seconds is 0. */
17225 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17227 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17228 seconds, inc/1000.);
17231 SendToProgram(buf, cps);
17233 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17234 /* Orthogonally, limit search to given depth */
17236 if (cps->sdKludge) {
17237 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17239 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17241 SendToProgram(buf, cps);
17244 if(cps->nps >= 0) { /* [HGM] nps */
17245 if(cps->supportsNPS == FALSE)
17246 cps->nps = -1; // don't use if engine explicitly says not supported!
17248 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17249 SendToProgram(buf, cps);
17254 ChessProgramState *
17256 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17258 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17259 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17265 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17267 char message[MSG_SIZ];
17270 /* Note: this routine must be called when the clocks are stopped
17271 or when they have *just* been set or switched; otherwise
17272 it will be off by the time since the current tick started.
17274 if (machineWhite) {
17275 time = whiteTimeRemaining / 10;
17276 otime = blackTimeRemaining / 10;
17278 time = blackTimeRemaining / 10;
17279 otime = whiteTimeRemaining / 10;
17281 /* [HGM] translate opponent's time by time-odds factor */
17282 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17284 if (time <= 0) time = 1;
17285 if (otime <= 0) otime = 1;
17287 snprintf(message, MSG_SIZ, "time %ld\n", time);
17288 SendToProgram(message, cps);
17290 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17291 SendToProgram(message, cps);
17295 EngineDefinedVariant (ChessProgramState *cps, int n)
17296 { // return name of n-th unknown variant that engine supports
17297 static char buf[MSG_SIZ];
17298 char *p, *s = cps->variants;
17299 if(!s) return NULL;
17300 do { // parse string from variants feature
17302 p = strchr(s, ',');
17303 if(p) *p = NULLCHAR;
17304 v = StringToVariant(s);
17305 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17306 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17307 if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17308 !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17309 !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17310 !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17311 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17314 if(n < 0) return buf;
17320 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17323 int len = strlen(name);
17326 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17328 sscanf(*p, "%d", &val);
17330 while (**p && **p != ' ')
17332 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17333 SendToProgram(buf, cps);
17340 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17343 int len = strlen(name);
17344 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17346 sscanf(*p, "%d", loc);
17347 while (**p && **p != ' ') (*p)++;
17348 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17349 SendToProgram(buf, cps);
17356 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17359 int len = strlen(name);
17360 if (strncmp((*p), name, len) == 0
17361 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17363 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
17364 FREE(*loc); *loc = malloc(len);
17365 strncpy(*loc, *p, len);
17366 sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17367 while (**p && **p != '\"') (*p)++;
17368 if (**p == '\"') (*p)++;
17369 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17370 SendToProgram(buf, cps);
17377 ParseOption (Option *opt, ChessProgramState *cps)
17378 // [HGM] options: process the string that defines an engine option, and determine
17379 // name, type, default value, and allowed value range
17381 char *p, *q, buf[MSG_SIZ];
17382 int n, min = (-1)<<31, max = 1<<31, def;
17384 opt->target = &opt->value; // OK for spin/slider and checkbox
17385 if(p = strstr(opt->name, " -spin ")) {
17386 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17387 if(max < min) max = min; // enforce consistency
17388 if(def < min) def = min;
17389 if(def > max) def = max;
17394 } else if((p = strstr(opt->name, " -slider "))) {
17395 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17396 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17397 if(max < min) max = min; // enforce consistency
17398 if(def < min) def = min;
17399 if(def > max) def = max;
17403 opt->type = Spin; // Slider;
17404 } else if((p = strstr(opt->name, " -string "))) {
17405 opt->textValue = p+9;
17406 opt->type = TextBox;
17407 opt->target = &opt->textValue;
17408 } else if((p = strstr(opt->name, " -file "))) {
17409 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17410 opt->target = opt->textValue = p+7;
17411 opt->type = FileName; // FileName;
17412 opt->target = &opt->textValue;
17413 } else if((p = strstr(opt->name, " -path "))) {
17414 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17415 opt->target = opt->textValue = p+7;
17416 opt->type = PathName; // PathName;
17417 opt->target = &opt->textValue;
17418 } else if(p = strstr(opt->name, " -check ")) {
17419 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17420 opt->value = (def != 0);
17421 opt->type = CheckBox;
17422 } else if(p = strstr(opt->name, " -combo ")) {
17423 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17424 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17425 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17426 opt->value = n = 0;
17427 while(q = StrStr(q, " /// ")) {
17428 n++; *q = 0; // count choices, and null-terminate each of them
17430 if(*q == '*') { // remember default, which is marked with * prefix
17434 cps->comboList[cps->comboCnt++] = q;
17436 cps->comboList[cps->comboCnt++] = NULL;
17438 opt->type = ComboBox;
17439 } else if(p = strstr(opt->name, " -button")) {
17440 opt->type = Button;
17441 } else if(p = strstr(opt->name, " -save")) {
17442 opt->type = SaveButton;
17443 } else return FALSE;
17444 *p = 0; // terminate option name
17445 *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17446 if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17447 // now look if the command-line options define a setting for this engine option.
17448 if(cps->optionSettings && cps->optionSettings[0])
17449 p = strstr(cps->optionSettings, opt->name); else p = NULL;
17450 if(p && (p == cps->optionSettings || p[-1] == ',')) {
17451 snprintf(buf, MSG_SIZ, "option %s", p);
17452 if(p = strstr(buf, ",")) *p = 0;
17453 if(q = strchr(buf, '=')) switch(opt->type) {
17455 for(n=0; n<opt->max; n++)
17456 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17461 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17465 opt->value = atoi(q+1);
17470 SendToProgram(buf, cps);
17476 FeatureDone (ChessProgramState *cps, int val)
17478 DelayedEventCallback cb = GetDelayedEvent();
17479 if ((cb == InitBackEnd3 && cps == &first) ||
17480 (cb == SettingsMenuIfReady && cps == &second) ||
17481 (cb == LoadEngine) || (cb == StartSecond) ||
17482 (cb == TwoMachinesEventIfReady)) {
17483 CancelDelayedEvent();
17484 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17485 } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17486 cps->initDone = val;
17487 if(val) cps->reload = FALSE, RefreshSettingsDialog(cps, val);
17490 /* Parse feature command from engine */
17492 ParseFeatures (char *args, ChessProgramState *cps)
17500 while (*p == ' ') p++;
17501 if (*p == NULLCHAR) return;
17503 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17504 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17505 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17506 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17507 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17508 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17509 if (BoolFeature(&p, "reuse", &val, cps)) {
17510 /* Engine can disable reuse, but can't enable it if user said no */
17511 if (!val) cps->reuse = FALSE;
17514 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17515 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17516 if (gameMode == TwoMachinesPlay) {
17517 DisplayTwoMachinesTitle();
17523 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17524 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17525 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17526 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17527 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17528 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17529 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17530 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17531 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17532 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17533 if (IntFeature(&p, "done", &val, cps)) {
17534 FeatureDone(cps, val);
17537 /* Added by Tord: */
17538 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17539 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17540 /* End of additions by Tord */
17542 /* [HGM] added features: */
17543 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17544 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17545 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17546 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17547 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17548 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17549 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17550 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17551 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17552 if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17553 FREE(cps->option[cps->nrOptions].name);
17554 cps->option[cps->nrOptions].name = q; q = NULL;
17555 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17556 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17557 SendToProgram(buf, cps);
17560 if(cps->nrOptions >= MAX_OPTIONS) {
17562 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17563 DisplayError(buf, 0);
17567 /* End of additions by HGM */
17569 /* unknown feature: complain and skip */
17571 while (*q && *q != '=') q++;
17572 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17573 SendToProgram(buf, cps);
17579 while (*p && *p != '\"') p++;
17580 if (*p == '\"') p++;
17582 while (*p && *p != ' ') p++;
17590 PeriodicUpdatesEvent (int newState)
17592 if (newState == appData.periodicUpdates)
17595 appData.periodicUpdates=newState;
17597 /* Display type changes, so update it now */
17598 // DisplayAnalysis();
17600 /* Get the ball rolling again... */
17602 AnalysisPeriodicEvent(1);
17603 StartAnalysisClock();
17608 PonderNextMoveEvent (int newState)
17610 if (newState == appData.ponderNextMove) return;
17611 if (gameMode == EditPosition) EditPositionDone(TRUE);
17613 SendToProgram("hard\n", &first);
17614 if (gameMode == TwoMachinesPlay) {
17615 SendToProgram("hard\n", &second);
17618 SendToProgram("easy\n", &first);
17619 thinkOutput[0] = NULLCHAR;
17620 if (gameMode == TwoMachinesPlay) {
17621 SendToProgram("easy\n", &second);
17624 appData.ponderNextMove = newState;
17628 NewSettingEvent (int option, int *feature, char *command, int value)
17632 if (gameMode == EditPosition) EditPositionDone(TRUE);
17633 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17634 if(feature == NULL || *feature) SendToProgram(buf, &first);
17635 if (gameMode == TwoMachinesPlay) {
17636 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17641 ShowThinkingEvent ()
17642 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17644 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17645 int newState = appData.showThinking
17646 // [HGM] thinking: other features now need thinking output as well
17647 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17649 if (oldState == newState) return;
17650 oldState = newState;
17651 if (gameMode == EditPosition) EditPositionDone(TRUE);
17653 SendToProgram("post\n", &first);
17654 if (gameMode == TwoMachinesPlay) {
17655 SendToProgram("post\n", &second);
17658 SendToProgram("nopost\n", &first);
17659 thinkOutput[0] = NULLCHAR;
17660 if (gameMode == TwoMachinesPlay) {
17661 SendToProgram("nopost\n", &second);
17664 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17668 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17670 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17671 if (pr == NoProc) return;
17672 AskQuestion(title, question, replyPrefix, pr);
17676 TypeInEvent (char firstChar)
17678 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17679 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17680 gameMode == AnalyzeMode || gameMode == EditGame ||
17681 gameMode == EditPosition || gameMode == IcsExamining ||
17682 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17683 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17684 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17685 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
17686 gameMode == Training) PopUpMoveDialog(firstChar);
17690 TypeInDoneEvent (char *move)
17693 int n, fromX, fromY, toX, toY;
17695 ChessMove moveType;
17698 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17699 EditPositionPasteFEN(move);
17702 // [HGM] movenum: allow move number to be typed in any mode
17703 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17707 // undocumented kludge: allow command-line option to be typed in!
17708 // (potentially fatal, and does not implement the effect of the option.)
17709 // should only be used for options that are values on which future decisions will be made,
17710 // and definitely not on options that would be used during initialization.
17711 if(strstr(move, "!!! -") == move) {
17712 ParseArgsFromString(move+4);
17716 if (gameMode != EditGame && currentMove != forwardMostMove &&
17717 gameMode != Training) {
17718 DisplayMoveError(_("Displayed move is not current"));
17720 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17721 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17722 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17723 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17724 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17725 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17727 DisplayMoveError(_("Could not parse move"));
17733 DisplayMove (int moveNumber)
17735 char message[MSG_SIZ];
17737 char cpThinkOutput[MSG_SIZ];
17739 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17741 if (moveNumber == forwardMostMove - 1 ||
17742 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17744 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17746 if (strchr(cpThinkOutput, '\n')) {
17747 *strchr(cpThinkOutput, '\n') = NULLCHAR;
17750 *cpThinkOutput = NULLCHAR;
17753 /* [AS] Hide thinking from human user */
17754 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17755 *cpThinkOutput = NULLCHAR;
17756 if( thinkOutput[0] != NULLCHAR ) {
17759 for( i=0; i<=hiddenThinkOutputState; i++ ) {
17760 cpThinkOutput[i] = '.';
17762 cpThinkOutput[i] = NULLCHAR;
17763 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17767 if (moveNumber == forwardMostMove - 1 &&
17768 gameInfo.resultDetails != NULL) {
17769 if (gameInfo.resultDetails[0] == NULLCHAR) {
17770 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17772 snprintf(res, MSG_SIZ, " {%s} %s",
17773 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17779 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17780 DisplayMessage(res, cpThinkOutput);
17782 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17783 WhiteOnMove(moveNumber) ? " " : ".. ",
17784 parseList[moveNumber], res);
17785 DisplayMessage(message, cpThinkOutput);
17790 DisplayComment (int moveNumber, char *text)
17792 char title[MSG_SIZ];
17794 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17795 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17797 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17798 WhiteOnMove(moveNumber) ? " " : ".. ",
17799 parseList[moveNumber]);
17801 if (text != NULL && (appData.autoDisplayComment || commentUp))
17802 CommentPopUp(title, text);
17805 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17806 * might be busy thinking or pondering. It can be omitted if your
17807 * gnuchess is configured to stop thinking immediately on any user
17808 * input. However, that gnuchess feature depends on the FIONREAD
17809 * ioctl, which does not work properly on some flavors of Unix.
17812 Attention (ChessProgramState *cps)
17815 if (!cps->useSigint) return;
17816 if (appData.noChessProgram || (cps->pr == NoProc)) return;
17817 switch (gameMode) {
17818 case MachinePlaysWhite:
17819 case MachinePlaysBlack:
17820 case TwoMachinesPlay:
17821 case IcsPlayingWhite:
17822 case IcsPlayingBlack:
17825 /* Skip if we know it isn't thinking */
17826 if (!cps->maybeThinking) return;
17827 if (appData.debugMode)
17828 fprintf(debugFP, "Interrupting %s\n", cps->which);
17829 InterruptChildProcess(cps->pr);
17830 cps->maybeThinking = FALSE;
17835 #endif /*ATTENTION*/
17841 if (whiteTimeRemaining <= 0) {
17844 if (appData.icsActive) {
17845 if (appData.autoCallFlag &&
17846 gameMode == IcsPlayingBlack && !blackFlag) {
17847 SendToICS(ics_prefix);
17848 SendToICS("flag\n");
17852 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17854 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17855 if (appData.autoCallFlag) {
17856 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17863 if (blackTimeRemaining <= 0) {
17866 if (appData.icsActive) {
17867 if (appData.autoCallFlag &&
17868 gameMode == IcsPlayingWhite && !whiteFlag) {
17869 SendToICS(ics_prefix);
17870 SendToICS("flag\n");
17874 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17876 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17877 if (appData.autoCallFlag) {
17878 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17889 CheckTimeControl ()
17891 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17892 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17895 * add time to clocks when time control is achieved ([HGM] now also used for increment)
17897 if ( !WhiteOnMove(forwardMostMove) ) {
17898 /* White made time control */
17899 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17900 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17901 /* [HGM] time odds: correct new time quota for time odds! */
17902 / WhitePlayer()->timeOdds;
17903 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17905 lastBlack -= blackTimeRemaining;
17906 /* Black made time control */
17907 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17908 / WhitePlayer()->other->timeOdds;
17909 lastWhite = whiteTimeRemaining;
17914 DisplayBothClocks ()
17916 int wom = gameMode == EditPosition ?
17917 !blackPlaysFirst : WhiteOnMove(currentMove);
17918 DisplayWhiteClock(whiteTimeRemaining, wom);
17919 DisplayBlackClock(blackTimeRemaining, !wom);
17923 /* Timekeeping seems to be a portability nightmare. I think everyone
17924 has ftime(), but I'm really not sure, so I'm including some ifdefs
17925 to use other calls if you don't. Clocks will be less accurate if
17926 you have neither ftime nor gettimeofday.
17929 /* VS 2008 requires the #include outside of the function */
17930 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17931 #include <sys/timeb.h>
17934 /* Get the current time as a TimeMark */
17936 GetTimeMark (TimeMark *tm)
17938 #if HAVE_GETTIMEOFDAY
17940 struct timeval timeVal;
17941 struct timezone timeZone;
17943 gettimeofday(&timeVal, &timeZone);
17944 tm->sec = (long) timeVal.tv_sec;
17945 tm->ms = (int) (timeVal.tv_usec / 1000L);
17947 #else /*!HAVE_GETTIMEOFDAY*/
17950 // include <sys/timeb.h> / moved to just above start of function
17951 struct timeb timeB;
17954 tm->sec = (long) timeB.time;
17955 tm->ms = (int) timeB.millitm;
17957 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17958 tm->sec = (long) time(NULL);
17964 /* Return the difference in milliseconds between two
17965 time marks. We assume the difference will fit in a long!
17968 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17970 return 1000L*(tm2->sec - tm1->sec) +
17971 (long) (tm2->ms - tm1->ms);
17976 * Code to manage the game clocks.
17978 * In tournament play, black starts the clock and then white makes a move.
17979 * We give the human user a slight advantage if he is playing white---the
17980 * clocks don't run until he makes his first move, so it takes zero time.
17981 * Also, we don't account for network lag, so we could get out of sync
17982 * with GNU Chess's clock -- but then, referees are always right.
17985 static TimeMark tickStartTM;
17986 static long intendedTickLength;
17989 NextTickLength (long timeRemaining)
17991 long nominalTickLength, nextTickLength;
17993 if (timeRemaining > 0L && timeRemaining <= 10000L)
17994 nominalTickLength = 100L;
17996 nominalTickLength = 1000L;
17997 nextTickLength = timeRemaining % nominalTickLength;
17998 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
18000 return nextTickLength;
18003 /* Adjust clock one minute up or down */
18005 AdjustClock (Boolean which, int dir)
18007 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
18008 if(which) blackTimeRemaining += 60000*dir;
18009 else whiteTimeRemaining += 60000*dir;
18010 DisplayBothClocks();
18011 adjustedClock = TRUE;
18014 /* Stop clocks and reset to a fresh time control */
18018 (void) StopClockTimer();
18019 if (appData.icsActive) {
18020 whiteTimeRemaining = blackTimeRemaining = 0;
18021 } else if (searchTime) {
18022 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18023 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18024 } else { /* [HGM] correct new time quote for time odds */
18025 whiteTC = blackTC = fullTimeControlString;
18026 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
18027 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
18029 if (whiteFlag || blackFlag) {
18031 whiteFlag = blackFlag = FALSE;
18033 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
18034 DisplayBothClocks();
18035 adjustedClock = FALSE;
18038 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
18040 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
18042 /* Decrement running clock by amount of time that has passed */
18047 long lastTickLength, fudge;
18050 if (!appData.clockMode) return;
18051 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
18055 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18057 /* Fudge if we woke up a little too soon */
18058 fudge = intendedTickLength - lastTickLength;
18059 if (fudge < 0 || fudge > FUDGE) fudge = 0;
18061 if (WhiteOnMove(forwardMostMove)) {
18062 if(whiteNPS >= 0) lastTickLength = 0;
18063 tRemaining = whiteTimeRemaining -= lastTickLength;
18064 if( tRemaining < 0 && !appData.icsActive) {
18065 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
18066 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
18067 whiteStartMove = forwardMostMove; whiteTC = nextSession;
18068 lastWhite= tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
18071 if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
18072 DisplayWhiteClock(whiteTimeRemaining - fudge,
18073 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18076 if(blackNPS >= 0) lastTickLength = 0;
18077 tRemaining = blackTimeRemaining -= lastTickLength;
18078 if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
18079 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
18081 blackStartMove = forwardMostMove;
18082 lastBlack = tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
18085 if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
18086 DisplayBlackClock(blackTimeRemaining - fudge,
18087 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18090 if (CheckFlags()) return;
18092 if(twoBoards) { // count down secondary board's clocks as well
18093 activePartnerTime -= lastTickLength;
18095 if(activePartner == 'W')
18096 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
18098 DisplayBlackClock(activePartnerTime, TRUE);
18103 intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
18104 StartClockTimer(intendedTickLength);
18106 /* if the time remaining has fallen below the alarm threshold, sound the
18107 * alarm. if the alarm has sounded and (due to a takeback or time control
18108 * with increment) the time remaining has increased to a level above the
18109 * threshold, reset the alarm so it can sound again.
18112 if (appData.icsActive && appData.icsAlarm) {
18114 /* make sure we are dealing with the user's clock */
18115 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
18116 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
18119 if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
18120 alarmSounded = FALSE;
18121 } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
18123 alarmSounded = TRUE;
18129 /* A player has just moved, so stop the previously running
18130 clock and (if in clock mode) start the other one.
18131 We redisplay both clocks in case we're in ICS mode, because
18132 ICS gives us an update to both clocks after every move.
18133 Note that this routine is called *after* forwardMostMove
18134 is updated, so the last fractional tick must be subtracted
18135 from the color that is *not* on move now.
18138 SwitchClocks (int newMoveNr)
18140 long lastTickLength;
18142 int flagged = FALSE;
18146 if (StopClockTimer() && appData.clockMode) {
18147 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18148 if (!WhiteOnMove(forwardMostMove)) {
18149 if(blackNPS >= 0) lastTickLength = 0;
18150 blackTimeRemaining -= lastTickLength;
18151 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18152 // if(pvInfoList[forwardMostMove].time == -1)
18153 pvInfoList[forwardMostMove].time = // use GUI time
18154 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18156 if(whiteNPS >= 0) lastTickLength = 0;
18157 whiteTimeRemaining -= lastTickLength;
18158 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18159 // if(pvInfoList[forwardMostMove].time == -1)
18160 pvInfoList[forwardMostMove].time =
18161 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18163 flagged = CheckFlags();
18165 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18166 CheckTimeControl();
18168 if (flagged || !appData.clockMode) return;
18170 switch (gameMode) {
18171 case MachinePlaysBlack:
18172 case MachinePlaysWhite:
18173 case BeginningOfGame:
18174 if (pausing) return;
18178 case PlayFromGameFile:
18186 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18187 if(WhiteOnMove(forwardMostMove))
18188 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18189 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18193 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18194 whiteTimeRemaining : blackTimeRemaining);
18195 StartClockTimer(intendedTickLength);
18199 /* Stop both clocks */
18203 long lastTickLength;
18206 if (!StopClockTimer()) return;
18207 if (!appData.clockMode) return;
18211 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18212 if (WhiteOnMove(forwardMostMove)) {
18213 if(whiteNPS >= 0) lastTickLength = 0;
18214 whiteTimeRemaining -= lastTickLength;
18215 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18217 if(blackNPS >= 0) lastTickLength = 0;
18218 blackTimeRemaining -= lastTickLength;
18219 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18224 /* Start clock of player on move. Time may have been reset, so
18225 if clock is already running, stop and restart it. */
18229 (void) StopClockTimer(); /* in case it was running already */
18230 DisplayBothClocks();
18231 if (CheckFlags()) return;
18233 if (!appData.clockMode) return;
18234 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18236 GetTimeMark(&tickStartTM);
18237 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18238 whiteTimeRemaining : blackTimeRemaining);
18240 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18241 whiteNPS = blackNPS = -1;
18242 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18243 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18244 whiteNPS = first.nps;
18245 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18246 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18247 blackNPS = first.nps;
18248 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18249 whiteNPS = second.nps;
18250 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18251 blackNPS = second.nps;
18252 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18254 StartClockTimer(intendedTickLength);
18258 TimeString (long ms)
18260 long second, minute, hour, day;
18262 static char buf[40], moveTime[8];
18264 if (ms > 0 && ms <= 9900) {
18265 /* convert milliseconds to tenths, rounding up */
18266 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18268 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18272 /* convert milliseconds to seconds, rounding up */
18273 /* use floating point to avoid strangeness of integer division
18274 with negative dividends on many machines */
18275 second = (long) floor(((double) (ms + 999L)) / 1000.0);
18282 day = second / (60 * 60 * 24);
18283 second = second % (60 * 60 * 24);
18284 hour = second / (60 * 60);
18285 second = second % (60 * 60);
18286 minute = second / 60;
18287 second = second % 60;
18289 if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18290 else *moveTime = NULLCHAR;
18293 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18294 sign, day, hour, minute, second, moveTime);
18296 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18298 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18305 * This is necessary because some C libraries aren't ANSI C compliant yet.
18308 StrStr (char *string, char *match)
18312 length = strlen(match);
18314 for (i = strlen(string) - length; i >= 0; i--, string++)
18315 if (!strncmp(match, string, length))
18322 StrCaseStr (char *string, char *match)
18326 length = strlen(match);
18328 for (i = strlen(string) - length; i >= 0; i--, string++) {
18329 for (j = 0; j < length; j++) {
18330 if (ToLower(match[j]) != ToLower(string[j]))
18333 if (j == length) return string;
18341 StrCaseCmp (char *s1, char *s2)
18346 c1 = ToLower(*s1++);
18347 c2 = ToLower(*s2++);
18348 if (c1 > c2) return 1;
18349 if (c1 < c2) return -1;
18350 if (c1 == NULLCHAR) return 0;
18358 return isupper(c) ? tolower(c) : c;
18365 return islower(c) ? toupper(c) : c;
18367 #endif /* !_amigados */
18374 if ((ret = (char *) malloc(strlen(s) + 1)))
18376 safeStrCpy(ret, s, strlen(s)+1);
18382 StrSavePtr (char *s, char **savePtr)
18387 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18388 safeStrCpy(*savePtr, s, strlen(s)+1);
18400 clock = time((time_t *)NULL);
18401 tm = localtime(&clock);
18402 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18403 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18404 return StrSave(buf);
18409 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18411 int i, j, fromX, fromY, toX, toY;
18412 int whiteToPlay, haveRights = nrCastlingRights;
18418 whiteToPlay = (gameMode == EditPosition) ?
18419 !blackPlaysFirst : (move % 2 == 0);
18422 /* Piece placement data */
18423 for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18424 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18426 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18427 if (boards[move][i][j] == EmptySquare) {
18429 } else { ChessSquare piece = boards[move][i][j];
18430 if (emptycount > 0) {
18431 if(emptycount<10) /* [HGM] can be >= 10 */
18432 *p++ = '0' + emptycount;
18433 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18436 if(PieceToChar(piece) == '+') {
18437 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18439 piece = (ChessSquare)(CHUDEMOTED(piece));
18441 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18442 if(*p = PieceSuffix(piece)) p++;
18444 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18445 p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18450 if (emptycount > 0) {
18451 if(emptycount<10) /* [HGM] can be >= 10 */
18452 *p++ = '0' + emptycount;
18453 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18460 /* [HGM] print Crazyhouse or Shogi holdings */
18461 if( gameInfo.holdingsWidth ) {
18462 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18464 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18465 piece = boards[move][i][BOARD_WIDTH-1];
18466 if( piece != EmptySquare )
18467 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18468 *p++ = PieceToChar(piece);
18470 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18471 piece = boards[move][handSize-i-1][0];
18472 if( piece != EmptySquare )
18473 for(j=0; j<(int) boards[move][handSize-i-1][1]; j++)
18474 *p++ = PieceToChar(piece);
18477 if( q == p ) *p++ = '-';
18483 *p++ = whiteToPlay ? 'w' : 'b';
18486 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18487 haveRights = 0; q = p;
18488 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18489 piece = boards[move][0][i];
18490 if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18491 if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18494 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18495 piece = boards[move][BOARD_HEIGHT-1][i];
18496 if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18497 if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18500 if(p == q) *p++ = '-';
18504 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18507 if(q != overrideCastling+1) p[-1] = ' '; else --p;
18510 int handW=0, handB=0;
18511 if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18512 for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18513 for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18516 if(appData.fischerCastling) {
18517 if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18518 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18519 if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18521 /* [HGM] write directly from rights */
18522 if(boards[move][CASTLING][2] != NoRights &&
18523 boards[move][CASTLING][0] != NoRights )
18524 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18525 if(boards[move][CASTLING][2] != NoRights &&
18526 boards[move][CASTLING][1] != NoRights )
18527 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18530 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18531 if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18533 if(boards[move][CASTLING][5] != NoRights &&
18534 boards[move][CASTLING][3] != NoRights )
18535 *p++ = boards[move][CASTLING][3] + AAA;
18536 if(boards[move][CASTLING][5] != NoRights &&
18537 boards[move][CASTLING][4] != NoRights )
18538 *p++ = boards[move][CASTLING][4] + AAA;
18542 /* [HGM] write true castling rights */
18543 if( nrCastlingRights == 6 ) {
18545 if(boards[move][CASTLING][0] != NoRights &&
18546 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
18547 q = (boards[move][CASTLING][1] != NoRights &&
18548 boards[move][CASTLING][2] != NoRights );
18549 if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18550 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18551 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18552 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18556 if(boards[move][CASTLING][3] != NoRights &&
18557 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
18558 q = (boards[move][CASTLING][4] != NoRights &&
18559 boards[move][CASTLING][5] != NoRights );
18561 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18562 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18563 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18568 if (q == p) *p++ = '-'; /* No castling rights */
18572 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18573 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18574 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18575 /* En passant target square */
18576 if (move > backwardMostMove) {
18577 fromX = moveList[move - 1][0] - AAA;
18578 fromY = moveList[move - 1][1] - ONE;
18579 toX = moveList[move - 1][2] - AAA;
18580 toY = moveList[move - 1][3] - ONE;
18581 if ((whiteToPlay ? toY < fromY - 1 : toY > fromY + 1) &&
18582 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) ) {
18583 /* 2-square pawn move just happened */
18584 *p++ = (3*toX + 5*fromX + 4)/8 + AAA;
18585 *p++ = (3*toY + 5*fromY + 4)/8 + ONE;
18586 if(gameInfo.variant == VariantBerolina) {
18593 } else if(move == backwardMostMove) {
18594 // [HGM] perhaps we should always do it like this, and forget the above?
18595 if((signed char)boards[move][EP_STATUS] >= 0) {
18596 *p++ = boards[move][EP_STATUS] + AAA;
18597 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18608 i = boards[move][CHECK_COUNT];
18610 sprintf(p, "%d+%d ", i&255, i>>8);
18615 { int i = 0, j=move;
18617 /* [HGM] find reversible plies */
18618 if (appData.debugMode) { int k;
18619 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18620 for(k=backwardMostMove; k<=forwardMostMove; k++)
18621 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18625 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18626 if( j == backwardMostMove ) i += initialRulePlies;
18627 sprintf(p, "%d ", i);
18628 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18630 /* Fullmove number */
18631 sprintf(p, "%d", (move / 2) + 1);
18632 } else *--p = NULLCHAR;
18634 return StrSave(buf);
18638 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18640 int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18642 int emptycount, virgin[BOARD_FILES];
18643 ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18647 for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18649 /* Piece placement data */
18650 for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18653 if (*p == '/' || *p == ' ' || *p == '[' ) {
18655 emptycount = gameInfo.boardWidth - j;
18656 while (emptycount--)
18657 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18658 if (*p == '/') p++;
18659 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18660 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18661 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18663 appData.NrRanks = gameInfo.boardHeight - i; i=0;
18666 #if(BOARD_FILES >= 10)*0
18667 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18668 p++; emptycount=10;
18669 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18670 while (emptycount--)
18671 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18673 } else if (*p == '*') {
18674 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18675 } else if (isdigit(*p)) {
18676 emptycount = *p++ - '0';
18677 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18678 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18679 while (emptycount--)
18680 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18681 } else if (*p == '<') {
18682 if(i == BOARD_HEIGHT-1) shuffle = 1;
18683 else if (i != 0 || !shuffle) return FALSE;
18685 } else if (shuffle && *p == '>') {
18686 p++; // for now ignore closing shuffle range, and assume rank-end
18687 } else if (*p == '?') {
18688 if (j >= gameInfo.boardWidth) return FALSE;
18689 if (i != 0 && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18690 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18691 } else if (*p == '+' || isalpha(*p)) {
18692 char *q, *s = SUFFIXES;
18693 if (j >= gameInfo.boardWidth) return FALSE;
18696 if(q = strchr(s, p[1])) p++;
18697 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18698 if(piece == EmptySquare) return FALSE; /* unknown piece */
18699 piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18700 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18703 if(q = strchr(s, *p)) p++;
18704 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18707 if(piece==EmptySquare) return FALSE; /* unknown piece */
18708 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18709 piece = (ChessSquare) (PROMOTED(piece));
18710 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18713 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18714 if(piece == king) wKingRank = i;
18715 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18721 while (*p == '/' || *p == ' ') p++;
18723 if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18725 /* [HGM] by default clear Crazyhouse holdings, if present */
18726 if(gameInfo.holdingsWidth) {
18727 for(i=0; i<handSize; i++) {
18728 board[i][0] = EmptySquare; /* black holdings */
18729 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18730 board[i][1] = (ChessSquare) 0; /* black counts */
18731 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18735 /* [HGM] look for Crazyhouse holdings here */
18736 while(*p==' ') p++;
18737 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18738 int swap=0, wcnt=0, bcnt=0;
18740 if(*p == '<') swap++, p++;
18741 if(*p == '-' ) p++; /* empty holdings */ else {
18742 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18743 /* if we would allow FEN reading to set board size, we would */
18744 /* have to add holdings and shift the board read so far here */
18745 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18747 if((int) piece >= (int) BlackPawn ) {
18748 i = (int)piece - (int)BlackPawn;
18749 i = PieceToNumber((ChessSquare)i);
18750 if( i >= gameInfo.holdingsSize ) return FALSE;
18751 board[handSize-1-i][0] = piece; /* black holdings */
18752 board[handSize-1-i][1]++; /* black counts */
18755 i = (int)piece - (int)WhitePawn;
18756 i = PieceToNumber((ChessSquare)i);
18757 if( i >= gameInfo.holdingsSize ) return FALSE;
18758 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
18759 board[i][BOARD_WIDTH-2]++; /* black holdings */
18763 if(subst) { // substitute back-rank question marks by holdings pieces
18764 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18765 int k, m, n = bcnt + 1;
18766 if(board[0][j] == ClearBoard) {
18767 if(!wcnt) return FALSE;
18769 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18770 board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18771 if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18775 if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18776 if(!bcnt) return FALSE;
18777 if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18778 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[handSize-1-k][1]) < 0) {
18779 board[BOARD_HEIGHT-1][j] = board[handSize-1-k][0]; bcnt--;
18780 if(--board[handSize-1-k][1] == 0) board[handSize-1-k][0] = EmptySquare;
18791 if(subst) return FALSE; // substitution requested, but no holdings
18793 while(*p == ' ') p++;
18797 if(appData.colorNickNames) {
18798 if( c == appData.colorNickNames[0] ) c = 'w'; else
18799 if( c == appData.colorNickNames[1] ) c = 'b';
18803 *blackPlaysFirst = FALSE;
18806 *blackPlaysFirst = TRUE;
18812 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18813 /* return the extra info in global variiables */
18815 while(*p==' ') p++;
18817 if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18818 if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18819 if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18822 /* set defaults in case FEN is incomplete */
18823 board[EP_STATUS] = EP_UNKNOWN;
18824 board[TOUCHED_W] = board[TOUCHED_B] = 0;
18825 for(i=0; i<nrCastlingRights; i++ ) {
18826 board[CASTLING][i] =
18827 appData.fischerCastling ? NoRights : initialRights[i];
18828 } /* assume possible unless obviously impossible */
18829 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18830 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18831 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18832 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18833 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18834 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18835 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18836 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18839 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18842 while(isalpha(*p)) {
18843 if(isupper(*p)) w |= 1 << (*p++ - 'A');
18844 if(islower(*p)) b |= 1 << (*p++ - 'a');
18848 board[TOUCHED_W] = ~w;
18849 board[TOUCHED_B] = ~b;
18850 while(*p == ' ') p++;
18854 if(nrCastlingRights) {
18856 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18857 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18858 /* castling indicator present, so default becomes no castlings */
18859 for(i=0; i<nrCastlingRights; i++ ) {
18860 board[CASTLING][i] = NoRights;
18863 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18864 (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18865 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18866 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
18867 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18869 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18870 if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18871 if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18873 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18874 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18875 if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18876 && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18877 if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18878 && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18881 for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18882 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18883 board[CASTLING][2] = whiteKingFile;
18884 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18885 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18886 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18889 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18890 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18891 board[CASTLING][2] = whiteKingFile;
18892 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18893 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18894 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18897 for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18898 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18899 board[CASTLING][5] = blackKingFile;
18900 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18901 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18902 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18905 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18906 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18907 board[CASTLING][5] = blackKingFile;
18908 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18909 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18910 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18913 default: /* FRC castlings */
18914 if(c >= 'a') { /* black rights */
18915 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18916 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18917 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18918 if(i == BOARD_RGHT) break;
18919 board[CASTLING][5] = i;
18921 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
18922 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
18924 board[CASTLING][3] = c;
18926 board[CASTLING][4] = c;
18927 } else { /* white rights */
18928 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18929 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18930 if(board[0][i] == WhiteKing) break;
18931 if(i == BOARD_RGHT) break;
18932 board[CASTLING][2] = i;
18933 c -= AAA - 'a' + 'A';
18934 if(board[0][c] >= WhiteKing) break;
18936 board[CASTLING][0] = c;
18938 board[CASTLING][1] = c;
18942 for(i=0; i<nrCastlingRights; i++)
18943 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18944 if(gameInfo.variant == VariantSChess)
18945 for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18946 if(fischer && shuffle) appData.fischerCastling = TRUE;
18947 if (appData.debugMode) {
18948 fprintf(debugFP, "FEN castling rights:");
18949 for(i=0; i<nrCastlingRights; i++)
18950 fprintf(debugFP, " %d", board[CASTLING][i]);
18951 fprintf(debugFP, "\n");
18954 while(*p==' ') p++;
18957 if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18959 /* read e.p. field in games that know e.p. capture */
18960 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18961 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18962 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18964 p++; board[EP_STATUS] = EP_NONE;
18966 int d, r, c = *p - AAA;
18968 if(c >= BOARD_LEFT && c < BOARD_RGHT) {
18970 board[EP_STATUS] = board[EP_FILE] = c; r = 0;
18971 if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18972 d = (r < BOARD_HEIGHT << 1 ? 1 : -1); // assume double-push (P next to e.p. square nearer center)
18973 if(board[r+d][c] == EmptySquare) d *= 2; // but if no Pawn there, triple push
18974 board[LAST_TO] = 256*(r + d) + c;
18976 if(c >= BOARD_LEFT && c < BOARD_RGHT) { // mover explicitly mentioned
18977 if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18978 board[LAST_TO] = 256*r + c;
18979 if(!(board[EP_RANK]-r & 1)) board[EP_RANK] |= 128;
18985 while(*p == ' ') p++;
18987 board[CHECK_COUNT] = 0; // [HGM] 3check: check-count field
18988 if(sscanf(p, "%d+%d", &i, &j) == 2) {
18989 board[CHECK_COUNT] = i + 256*j;
18990 while(*p && *p != ' ') p++;
18993 c = sscanf(p, "%d%*d +%d+%d", &i, &j, &k);
18995 FENrulePlies = i; /* 50-move ply counter */
18996 /* (The move number is still ignored) */
18997 if(c == 3 && !board[CHECK_COUNT]) board[CHECK_COUNT] = (3 - j) + 256*(3 - k); // SCIDB-style check count
19004 EditPositionPasteFEN (char *fen)
19007 Board initial_position;
19009 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
19010 DisplayError(_("Bad FEN position in clipboard"), 0);
19013 int savedBlackPlaysFirst = blackPlaysFirst;
19014 EditPositionEvent();
19015 blackPlaysFirst = savedBlackPlaysFirst;
19016 CopyBoard(boards[0], initial_position);
19017 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
19018 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
19019 DisplayBothClocks();
19020 DrawPosition(FALSE, boards[currentMove]);
19025 static char cseq[12] = "\\ ";
19028 set_cont_sequence (char *new_seq)
19033 // handle bad attempts to set the sequence
19035 return 0; // acceptable error - no debug
19037 len = strlen(new_seq);
19038 ret = (len > 0) && (len < sizeof(cseq));
19040 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
19041 else if (appData.debugMode)
19042 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
19047 reformat a source message so words don't cross the width boundary. internal
19048 newlines are not removed. returns the wrapped size (no null character unless
19049 included in source message). If dest is NULL, only calculate the size required
19050 for the dest buffer. lp argument indicats line position upon entry, and it's
19051 passed back upon exit.
19054 wrap (char *dest, char *src, int count, int width, int *lp)
19056 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
19058 cseq_len = strlen(cseq);
19059 old_line = line = *lp;
19060 ansi = len = clen = 0;
19062 for (i=0; i < count; i++)
19064 if (src[i] == '\033')
19067 // if we hit the width, back up
19068 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
19070 // store i & len in case the word is too long
19071 old_i = i, old_len = len;
19073 // find the end of the last word
19074 while (i && src[i] != ' ' && src[i] != '\n')
19080 // word too long? restore i & len before splitting it
19081 if ((old_i-i+clen) >= width)
19088 if (i && src[i-1] == ' ')
19091 if (src[i] != ' ' && src[i] != '\n')
19098 // now append the newline and continuation sequence
19103 strncpy(dest+len, cseq, cseq_len);
19111 dest[len] = src[i];
19115 if (src[i] == '\n')
19120 if (dest && appData.debugMode)
19122 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
19123 count, width, line, len, *lp);
19124 show_bytes(debugFP, src, count);
19125 fprintf(debugFP, "\ndest: ");
19126 show_bytes(debugFP, dest, len);
19127 fprintf(debugFP, "\n");
19129 *lp = dest ? line : old_line;
19134 // [HGM] vari: routines for shelving variations
19135 Boolean modeRestore = FALSE;
19138 PushInner (int firstMove, int lastMove)
19140 int i, j, nrMoves = lastMove - firstMove;
19142 // push current tail of game on stack
19143 savedResult[storedGames] = gameInfo.result;
19144 savedDetails[storedGames] = gameInfo.resultDetails;
19145 gameInfo.resultDetails = NULL;
19146 savedFirst[storedGames] = firstMove;
19147 savedLast [storedGames] = lastMove;
19148 savedFramePtr[storedGames] = framePtr;
19149 framePtr -= nrMoves; // reserve space for the boards
19150 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
19151 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
19152 for(j=0; j<MOVE_LEN; j++)
19153 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
19154 for(j=0; j<2*MOVE_LEN; j++)
19155 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
19156 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
19157 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
19158 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
19159 pvInfoList[firstMove+i-1].depth = 0;
19160 commentList[framePtr+i] = commentList[firstMove+i];
19161 commentList[firstMove+i] = NULL;
19165 forwardMostMove = firstMove; // truncate game so we can start variation
19169 PushTail (int firstMove, int lastMove)
19171 if(appData.icsActive) { // only in local mode
19172 forwardMostMove = currentMove; // mimic old ICS behavior
19175 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19177 PushInner(firstMove, lastMove);
19178 if(storedGames == 1) GreyRevert(FALSE);
19179 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19183 PopInner (Boolean annotate)
19186 char buf[8000], moveBuf[20];
19188 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19189 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19190 nrMoves = savedLast[storedGames] - currentMove;
19193 if(!WhiteOnMove(currentMove))
19194 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19195 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19196 for(i=currentMove; i<forwardMostMove; i++) {
19198 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19199 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19200 strcat(buf, moveBuf);
19201 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19202 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19206 for(i=1; i<=nrMoves; i++) { // copy last variation back
19207 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19208 for(j=0; j<MOVE_LEN; j++)
19209 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19210 for(j=0; j<2*MOVE_LEN; j++)
19211 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19212 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19213 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19214 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19215 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19216 commentList[currentMove+i] = commentList[framePtr+i];
19217 commentList[framePtr+i] = NULL;
19219 if(annotate) AppendComment(currentMove+1, buf, FALSE);
19220 framePtr = savedFramePtr[storedGames];
19221 gameInfo.result = savedResult[storedGames];
19222 if(gameInfo.resultDetails != NULL) {
19223 free(gameInfo.resultDetails);
19225 gameInfo.resultDetails = savedDetails[storedGames];
19226 forwardMostMove = currentMove + nrMoves;
19230 PopTail (Boolean annotate)
19232 if(appData.icsActive) return FALSE; // only in local mode
19233 if(!storedGames) return FALSE; // sanity
19234 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19236 PopInner(annotate);
19237 if(currentMove < forwardMostMove) ForwardEvent(); else
19238 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19240 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19246 { // remove all shelved variations
19248 for(i=0; i<storedGames; i++) {
19249 if(savedDetails[i])
19250 free(savedDetails[i]);
19251 savedDetails[i] = NULL;
19253 for(i=framePtr; i<MAX_MOVES; i++) {
19254 if(commentList[i]) free(commentList[i]);
19255 commentList[i] = NULL;
19257 framePtr = MAX_MOVES-1;
19262 LoadVariation (int index, char *text)
19263 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19264 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19265 int level = 0, move;
19267 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19268 // first find outermost bracketing variation
19269 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19270 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19271 if(*p == '{') wait = '}'; else
19272 if(*p == '[') wait = ']'; else
19273 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19274 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19276 if(*p == wait) wait = NULLCHAR; // closing ]} found
19279 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19280 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19281 end[1] = NULLCHAR; // clip off comment beyond variation
19282 ToNrEvent(currentMove-1);
19283 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19284 // kludge: use ParsePV() to append variation to game
19285 move = currentMove;
19286 ParsePV(start, TRUE, TRUE);
19287 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19288 ClearPremoveHighlights();
19290 ToNrEvent(currentMove+1);
19293 int transparency[2];
19298 #define BUF_SIZ (2*MSG_SIZ)
19299 char *p, *q, buf[BUF_SIZ];
19300 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19301 snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19302 ParseArgsFromString(buf);
19303 ActivateTheme(TRUE); // also redo colors
19307 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19310 q = appData.themeNames;
19311 snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19312 if(appData.useBitmaps) {
19313 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19314 Shorten(appData.liteBackTextureFile));
19315 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19316 Shorten(appData.darkBackTextureFile),
19317 appData.liteBackTextureMode,
19318 appData.darkBackTextureMode );
19320 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19322 if(!appData.useBitmaps || transparency[0]) {
19323 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19325 if(!appData.useBitmaps || transparency[1]) {
19326 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19328 if(appData.useBorder) {
19329 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19332 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19334 if(appData.useFont) {
19335 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19336 appData.renderPiecesWithFont,
19337 appData.fontToPieceTable,
19338 Col2Text(9), // appData.fontBackColorWhite
19339 Col2Text(10) ); // appData.fontForeColorBlack
19341 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19342 if(appData.pieceDirectory[0]) {
19343 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19344 if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19345 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19347 if(!appData.pieceDirectory[0] || !appData.trueColors)
19348 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19349 Col2Text(0), // whitePieceColor
19350 Col2Text(1) ); // blackPieceColor
19352 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19353 Col2Text(4), // highlightSquareColor
19354 Col2Text(5) ); // premoveHighlightColor
19355 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19356 if(insert != q) insert[-1] = NULLCHAR;
19357 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19360 ActivateTheme(FALSE);