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 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 int flock(int f, int code);
75 #include <sys/types.h>
84 #else /* not STDC_HEADERS */
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
105 # include <sys/time.h>
111 #if defined(_amigados) && !defined(__GNUC__)
116 extern int gettimeofday(struct timeval *, struct timezone *);
124 #include "frontend.h"
131 #include "backendz.h"
132 #include "evalgraph.h"
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153 char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180 char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182 int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((void));
231 extern void ConsoleCreate();
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
254 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
279 /* States for ics_getting_history */
281 #define H_REQUESTED 1
282 #define H_GOT_REQ_HEADER 2
283 #define H_GOT_UNREQ_HEADER 3
284 #define H_GETTING_MOVES 4
285 #define H_GOT_UNWANTED_HEADER 5
287 /* whosays values for GameEnds */
296 /* Maximum number of games in a cmail message */
297 #define CMAIL_MAX_GAMES 20
299 /* Different types of move when calling RegisterMove */
301 #define CMAIL_RESIGN 1
303 #define CMAIL_ACCEPT 3
305 /* Different types of result to remember for each game */
306 #define CMAIL_NOT_RESULT 0
307 #define CMAIL_OLD_RESULT 1
308 #define CMAIL_NEW_RESULT 2
310 /* Telnet protocol constants */
321 safeStrCpy (char *dst, const char *src, size_t count)
324 assert( dst != NULL );
325 assert( src != NULL );
328 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
329 if( i == count && dst[count-1] != NULLCHAR)
331 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
332 if(appData.debugMode)
333 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
339 /* Some compiler can't cast u64 to double
340 * This function do the job for us:
342 * We use the highest bit for cast, this only
343 * works if the highest bit is not
344 * in use (This should not happen)
346 * We used this for all compiler
349 u64ToDouble (u64 value)
352 u64 tmp = value & u64Const(0x7fffffffffffffff);
353 r = (double)(s64)tmp;
354 if (value & u64Const(0x8000000000000000))
355 r += 9.2233720368547758080e18; /* 2^63 */
359 /* Fake up flags for now, as we aren't keeping track of castling
360 availability yet. [HGM] Change of logic: the flag now only
361 indicates the type of castlings allowed by the rule of the game.
362 The actual rights themselves are maintained in the array
363 castlingRights, as part of the game history, and are not probed
369 int flags = F_ALL_CASTLE_OK;
370 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
371 switch (gameInfo.variant) {
373 flags &= ~F_ALL_CASTLE_OK;
374 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
375 flags |= F_IGNORE_CHECK;
377 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
380 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
382 case VariantKriegspiel:
383 flags |= F_KRIEGSPIEL_CAPTURE;
385 case VariantCapaRandom:
386 case VariantFischeRandom:
387 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
388 case VariantNoCastle:
389 case VariantShatranj:
394 flags &= ~F_ALL_CASTLE_OK;
402 FILE *gameFileFP, *debugFP, *serverFP;
403 char *currentDebugFile; // [HGM] debug split: to remember name
406 [AS] Note: sometimes, the sscanf() function is used to parse the input
407 into a fixed-size buffer. Because of this, we must be prepared to
408 receive strings as long as the size of the input buffer, which is currently
409 set to 4K for Windows and 8K for the rest.
410 So, we must either allocate sufficiently large buffers here, or
411 reduce the size of the input buffer in the input reading part.
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
418 ChessProgramState first, second, pairing;
420 /* premove variables */
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
430 char *ics_prefix = "$";
431 enum ICS_TYPE ics_type = ICS_GENERIC;
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey, controlKey; // [HGM] set by mouse handler
461 int have_sent_ICS_logon = 0;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE, startingEngine = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
475 /* animateTraining preserves the state of appData.animate
476 * when Training mode is activated. This allows the
477 * response to be animated when appData.animate == TRUE and
478 * appData.animateDragging == TRUE.
480 Boolean animateTraining;
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char initialRights[BOARD_FILES];
490 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int initialRulePlies, FENrulePlies;
492 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
513 ChessSquare FIDEArray[2][BOARD_FILES] = {
514 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517 BlackKing, BlackBishop, BlackKnight, BlackRook }
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524 BlackKing, BlackKing, BlackKnight, BlackRook }
527 ChessSquare KnightmateArray[2][BOARD_FILES] = {
528 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530 { BlackRook, BlackMan, BlackBishop, BlackQueen,
531 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558 { BlackRook, BlackKnight, BlackMan, BlackFerz,
559 BlackKing, BlackMan, BlackKnight, BlackRook }
562 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
563 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
564 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
565 { BlackRook, BlackKnight, BlackMan, BlackFerz,
566 BlackKing, BlackMan, BlackKnight, BlackRook }
569 ChessSquare lionArray[2][BOARD_FILES] = {
570 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
571 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
572 { BlackRook, BlackLion, BlackBishop, BlackQueen,
573 BlackKing, BlackBishop, BlackKnight, BlackRook }
577 #if (BOARD_FILES>=10)
578 ChessSquare ShogiArray[2][BOARD_FILES] = {
579 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
580 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
581 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
582 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
585 ChessSquare XiangqiArray[2][BOARD_FILES] = {
586 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
587 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
588 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
589 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
592 ChessSquare CapablancaArray[2][BOARD_FILES] = {
593 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
594 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
595 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
596 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
599 ChessSquare GreatArray[2][BOARD_FILES] = {
600 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
601 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
602 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
603 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
606 ChessSquare JanusArray[2][BOARD_FILES] = {
607 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
608 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
609 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
610 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
613 ChessSquare GrandArray[2][BOARD_FILES] = {
614 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
615 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
616 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
617 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
621 ChessSquare GothicArray[2][BOARD_FILES] = {
622 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
623 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
624 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
625 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
628 #define GothicArray CapablancaArray
632 ChessSquare FalconArray[2][BOARD_FILES] = {
633 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
634 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
635 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
636 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
639 #define FalconArray CapablancaArray
642 #else // !(BOARD_FILES>=10)
643 #define XiangqiPosition FIDEArray
644 #define CapablancaArray FIDEArray
645 #define GothicArray FIDEArray
646 #define GreatArray FIDEArray
647 #endif // !(BOARD_FILES>=10)
649 #if (BOARD_FILES>=12)
650 ChessSquare CourierArray[2][BOARD_FILES] = {
651 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
652 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
653 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
654 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
656 ChessSquare ChuArray[6][BOARD_FILES] = {
657 { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
658 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
659 { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
660 BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
661 { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
662 WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
663 { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
664 BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
665 { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
666 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
667 { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
668 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
670 #else // !(BOARD_FILES>=12)
671 #define CourierArray CapablancaArray
672 #define ChuArray CapablancaArray
673 #endif // !(BOARD_FILES>=12)
676 Board initialPosition;
679 /* Convert str to a rating. Checks for special cases of "----",
681 "++++", etc. Also strips ()'s */
683 string_to_rating (char *str)
685 while(*str && !isdigit(*str)) ++str;
687 return 0; /* One of the special "no rating" cases */
695 /* Init programStats */
696 programStats.movelist[0] = 0;
697 programStats.depth = 0;
698 programStats.nr_moves = 0;
699 programStats.moves_left = 0;
700 programStats.nodes = 0;
701 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
702 programStats.score = 0;
703 programStats.got_only_move = 0;
704 programStats.got_fail = 0;
705 programStats.line_is_book = 0;
710 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
711 if (appData.firstPlaysBlack) {
712 first.twoMachinesColor = "black\n";
713 second.twoMachinesColor = "white\n";
715 first.twoMachinesColor = "white\n";
716 second.twoMachinesColor = "black\n";
719 first.other = &second;
720 second.other = &first;
723 if(appData.timeOddsMode) {
724 norm = appData.timeOdds[0];
725 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
727 first.timeOdds = appData.timeOdds[0]/norm;
728 second.timeOdds = appData.timeOdds[1]/norm;
731 if(programVersion) free(programVersion);
732 if (appData.noChessProgram) {
733 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
734 sprintf(programVersion, "%s", PACKAGE_STRING);
736 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
737 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
738 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
743 UnloadEngine (ChessProgramState *cps)
745 /* Kill off first chess program */
746 if (cps->isr != NULL)
747 RemoveInputSource(cps->isr);
750 if (cps->pr != NoProc) {
752 DoSleep( appData.delayBeforeQuit );
753 SendToProgram("quit\n", cps);
754 DoSleep( appData.delayAfterQuit );
755 DestroyChildProcess(cps->pr, cps->useSigterm);
758 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
762 ClearOptions (ChessProgramState *cps)
765 cps->nrOptions = cps->comboCnt = 0;
766 for(i=0; i<MAX_OPTIONS; i++) {
767 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
768 cps->option[i].textValue = 0;
772 char *engineNames[] = {
773 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
774 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
776 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
777 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
782 InitEngine (ChessProgramState *cps, int n)
783 { // [HGM] all engine initialiation put in a function that does one engine
787 cps->which = engineNames[n];
788 cps->maybeThinking = FALSE;
792 cps->sendDrawOffers = 1;
794 cps->program = appData.chessProgram[n];
795 cps->host = appData.host[n];
796 cps->dir = appData.directory[n];
797 cps->initString = appData.engInitString[n];
798 cps->computerString = appData.computerString[n];
799 cps->useSigint = TRUE;
800 cps->useSigterm = TRUE;
801 cps->reuse = appData.reuse[n];
802 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
803 cps->useSetboard = FALSE;
805 cps->usePing = FALSE;
808 cps->usePlayother = FALSE;
809 cps->useColors = TRUE;
810 cps->useUsermove = FALSE;
811 cps->sendICS = FALSE;
812 cps->sendName = appData.icsActive;
813 cps->sdKludge = FALSE;
814 cps->stKludge = FALSE;
815 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
816 TidyProgramName(cps->program, cps->host, cps->tidy);
818 ASSIGN(cps->variants, appData.variant);
819 cps->analysisSupport = 2; /* detect */
820 cps->analyzing = FALSE;
821 cps->initDone = FALSE;
824 /* New features added by Tord: */
825 cps->useFEN960 = FALSE;
826 cps->useOOCastle = TRUE;
827 /* End of new features added by Tord. */
828 cps->fenOverride = appData.fenOverride[n];
830 /* [HGM] time odds: set factor for each machine */
831 cps->timeOdds = appData.timeOdds[n];
833 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
834 cps->accumulateTC = appData.accumulateTC[n];
835 cps->maxNrOfSessions = 1;
840 cps->supportsNPS = UNKNOWN;
841 cps->memSize = FALSE;
842 cps->maxCores = FALSE;
843 ASSIGN(cps->egtFormats, "");
846 cps->optionSettings = appData.engOptions[n];
848 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
849 cps->isUCI = appData.isUCI[n]; /* [AS] */
850 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
853 if (appData.protocolVersion[n] > PROTOVER
854 || appData.protocolVersion[n] < 1)
859 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
860 appData.protocolVersion[n]);
861 if( (len >= MSG_SIZ) && appData.debugMode )
862 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
864 DisplayFatalError(buf, 0, 2);
868 cps->protocolVersion = appData.protocolVersion[n];
871 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
872 ParseFeatures(appData.featureDefaults, cps);
875 ChessProgramState *savCps;
883 if(WaitForEngine(savCps, LoadEngine)) return;
884 CommonEngineInit(); // recalculate time odds
885 if(gameInfo.variant != StringToVariant(appData.variant)) {
886 // we changed variant when loading the engine; this forces us to reset
887 Reset(TRUE, savCps != &first);
888 oldMode = BeginningOfGame; // to prevent restoring old mode
890 InitChessProgram(savCps, FALSE);
891 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
892 DisplayMessage("", "");
893 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
894 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
897 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
901 ReplaceEngine (ChessProgramState *cps, int n)
903 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
905 if(oldMode != BeginningOfGame) EditGameEvent();
908 appData.noChessProgram = FALSE;
909 appData.clockMode = TRUE;
912 if(n) return; // only startup first engine immediately; second can wait
913 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
917 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
918 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
920 static char resetOptions[] =
921 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
922 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
923 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
924 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
927 FloatToFront(char **list, char *engineLine)
929 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
931 if(appData.recentEngines <= 0) return;
932 TidyProgramName(engineLine, "localhost", tidy+1);
933 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
934 strncpy(buf+1, *list, MSG_SIZ-50);
935 if(p = strstr(buf, tidy)) { // tidy name appears in list
936 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
937 while(*p++ = *++q); // squeeze out
939 strcat(tidy, buf+1); // put list behind tidy name
940 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
941 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
942 ASSIGN(*list, tidy+1);
945 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
948 Load (ChessProgramState *cps, int i)
950 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
951 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
952 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
953 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
954 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
955 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
956 appData.firstProtocolVersion = PROTOVER;
957 ParseArgsFromString(buf);
959 ReplaceEngine(cps, i);
960 FloatToFront(&appData.recentEngineList, engineLine);
964 while(q = strchr(p, SLASH)) p = q+1;
965 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
966 if(engineDir[0] != NULLCHAR) {
967 ASSIGN(appData.directory[i], engineDir); p = engineName;
968 } else if(p != engineName) { // derive directory from engine path, when not given
970 ASSIGN(appData.directory[i], engineName);
972 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
973 } else { ASSIGN(appData.directory[i], "."); }
974 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
976 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
977 snprintf(command, MSG_SIZ, "%s %s", p, params);
980 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
981 ASSIGN(appData.chessProgram[i], p);
982 appData.isUCI[i] = isUCI;
983 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
984 appData.hasOwnBookUCI[i] = hasBook;
985 if(!nickName[0]) useNick = FALSE;
986 if(useNick) ASSIGN(appData.pgnName[i], nickName);
990 q = firstChessProgramNames;
991 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
992 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
993 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
994 quote, p, quote, appData.directory[i],
995 useNick ? " -fn \"" : "",
996 useNick ? nickName : "",
998 v1 ? " -firstProtocolVersion 1" : "",
999 hasBook ? "" : " -fNoOwnBookUCI",
1000 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1001 storeVariant ? " -variant " : "",
1002 storeVariant ? VariantName(gameInfo.variant) : "");
1003 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1004 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1005 if(insert != q) insert[-1] = NULLCHAR;
1006 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1008 FloatToFront(&appData.recentEngineList, buf);
1010 ReplaceEngine(cps, i);
1016 int matched, min, sec;
1018 * Parse timeControl resource
1020 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1021 appData.movesPerSession)) {
1023 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1024 DisplayFatalError(buf, 0, 2);
1028 * Parse searchTime resource
1030 if (*appData.searchTime != NULLCHAR) {
1031 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1033 searchTime = min * 60;
1034 } else if (matched == 2) {
1035 searchTime = min * 60 + sec;
1038 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1039 DisplayFatalError(buf, 0, 2);
1048 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1049 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1051 GetTimeMark(&programStartTime);
1052 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1053 appData.seedBase = random() + (random()<<15);
1054 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1056 ClearProgramStats();
1057 programStats.ok_to_send = 1;
1058 programStats.seen_stat = 0;
1061 * Initialize game list
1067 * Internet chess server status
1069 if (appData.icsActive) {
1070 appData.matchMode = FALSE;
1071 appData.matchGames = 0;
1073 appData.noChessProgram = !appData.zippyPlay;
1075 appData.zippyPlay = FALSE;
1076 appData.zippyTalk = FALSE;
1077 appData.noChessProgram = TRUE;
1079 if (*appData.icsHelper != NULLCHAR) {
1080 appData.useTelnet = TRUE;
1081 appData.telnetProgram = appData.icsHelper;
1084 appData.zippyTalk = appData.zippyPlay = FALSE;
1087 /* [AS] Initialize pv info list [HGM] and game state */
1091 for( i=0; i<=framePtr; i++ ) {
1092 pvInfoList[i].depth = -1;
1093 boards[i][EP_STATUS] = EP_NONE;
1094 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1100 /* [AS] Adjudication threshold */
1101 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1103 InitEngine(&first, 0);
1104 InitEngine(&second, 1);
1107 pairing.which = "pairing"; // pairing engine
1108 pairing.pr = NoProc;
1110 pairing.program = appData.pairingEngine;
1111 pairing.host = "localhost";
1114 if (appData.icsActive) {
1115 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1116 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1117 appData.clockMode = FALSE;
1118 first.sendTime = second.sendTime = 0;
1122 /* Override some settings from environment variables, for backward
1123 compatibility. Unfortunately it's not feasible to have the env
1124 vars just set defaults, at least in xboard. Ugh.
1126 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1131 if (!appData.icsActive) {
1135 /* Check for variants that are supported only in ICS mode,
1136 or not at all. Some that are accepted here nevertheless
1137 have bugs; see comments below.
1139 VariantClass variant = StringToVariant(appData.variant);
1141 case VariantBughouse: /* need four players and two boards */
1142 case VariantKriegspiel: /* need to hide pieces and move details */
1143 /* case VariantFischeRandom: (Fabien: moved below) */
1144 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1145 if( (len >= MSG_SIZ) && appData.debugMode )
1146 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1148 DisplayFatalError(buf, 0, 2);
1151 case VariantUnknown:
1152 case VariantLoadable:
1162 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1163 if( (len >= MSG_SIZ) && appData.debugMode )
1164 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1166 DisplayFatalError(buf, 0, 2);
1169 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1170 case VariantFairy: /* [HGM] TestLegality definitely off! */
1171 case VariantGothic: /* [HGM] should work */
1172 case VariantCapablanca: /* [HGM] should work */
1173 case VariantCourier: /* [HGM] initial forced moves not implemented */
1174 case VariantShogi: /* [HGM] could still mate with pawn drop */
1175 case VariantChu: /* [HGM] experimental */
1176 case VariantKnightmate: /* [HGM] should work */
1177 case VariantCylinder: /* [HGM] untested */
1178 case VariantFalcon: /* [HGM] untested */
1179 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1180 offboard interposition not understood */
1181 case VariantNormal: /* definitely works! */
1182 case VariantWildCastle: /* pieces not automatically shuffled */
1183 case VariantNoCastle: /* pieces not automatically shuffled */
1184 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1185 case VariantLosers: /* should work except for win condition,
1186 and doesn't know captures are mandatory */
1187 case VariantSuicide: /* should work except for win condition,
1188 and doesn't know captures are mandatory */
1189 case VariantGiveaway: /* should work except for win condition,
1190 and doesn't know captures are mandatory */
1191 case VariantTwoKings: /* should work */
1192 case VariantAtomic: /* should work except for win condition */
1193 case Variant3Check: /* should work except for win condition */
1194 case VariantShatranj: /* should work except for all win conditions */
1195 case VariantMakruk: /* should work except for draw countdown */
1196 case VariantASEAN : /* should work except for draw countdown */
1197 case VariantBerolina: /* might work if TestLegality is off */
1198 case VariantCapaRandom: /* should work */
1199 case VariantJanus: /* should work */
1200 case VariantSuper: /* experimental */
1201 case VariantGreat: /* experimental, requires legality testing to be off */
1202 case VariantSChess: /* S-Chess, should work */
1203 case VariantGrand: /* should work */
1204 case VariantSpartan: /* should work */
1205 case VariantLion: /* should work */
1213 NextIntegerFromString (char ** str, long * value)
1218 while( *s == ' ' || *s == '\t' ) {
1224 if( *s >= '0' && *s <= '9' ) {
1225 while( *s >= '0' && *s <= '9' ) {
1226 *value = *value * 10 + (*s - '0');
1239 NextTimeControlFromString (char ** str, long * value)
1242 int result = NextIntegerFromString( str, &temp );
1245 *value = temp * 60; /* Minutes */
1246 if( **str == ':' ) {
1248 result = NextIntegerFromString( str, &temp );
1249 *value += temp; /* Seconds */
1257 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1258 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1259 int result = -1, type = 0; long temp, temp2;
1261 if(**str != ':') return -1; // old params remain in force!
1263 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1264 if( NextIntegerFromString( str, &temp ) ) return -1;
1265 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1268 /* time only: incremental or sudden-death time control */
1269 if(**str == '+') { /* increment follows; read it */
1271 if(**str == '!') type = *(*str)++; // Bronstein TC
1272 if(result = NextIntegerFromString( str, &temp2)) return -1;
1273 *inc = temp2 * 1000;
1274 if(**str == '.') { // read fraction of increment
1275 char *start = ++(*str);
1276 if(result = NextIntegerFromString( str, &temp2)) return -1;
1278 while(start++ < *str) temp2 /= 10;
1282 *moves = 0; *tc = temp * 1000; *incType = type;
1286 (*str)++; /* classical time control */
1287 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1299 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1300 { /* [HGM] get time to add from the multi-session time-control string */
1301 int incType, moves=1; /* kludge to force reading of first session */
1302 long time, increment;
1305 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1307 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1308 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1309 if(movenr == -1) return time; /* last move before new session */
1310 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1311 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1312 if(!moves) return increment; /* current session is incremental */
1313 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1314 } while(movenr >= -1); /* try again for next session */
1316 return 0; // no new time quota on this move
1320 ParseTimeControl (char *tc, float ti, int mps)
1324 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1327 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1328 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1329 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1333 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1335 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1338 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1340 snprintf(buf, MSG_SIZ, ":%s", mytc);
1342 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1344 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1349 /* Parse second time control */
1352 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1360 timeControl_2 = tc2 * 1000;
1370 timeControl = tc1 * 1000;
1373 timeIncrement = ti * 1000; /* convert to ms */
1374 movesPerSession = 0;
1377 movesPerSession = mps;
1385 if (appData.debugMode) {
1386 # ifdef __GIT_VERSION
1387 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1389 fprintf(debugFP, "Version: %s\n", programVersion);
1392 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1394 set_cont_sequence(appData.wrapContSeq);
1395 if (appData.matchGames > 0) {
1396 appData.matchMode = TRUE;
1397 } else if (appData.matchMode) {
1398 appData.matchGames = 1;
1400 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1401 appData.matchGames = appData.sameColorGames;
1402 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1403 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1404 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1407 if (appData.noChessProgram || first.protocolVersion == 1) {
1410 /* kludge: allow timeout for initial "feature" commands */
1412 DisplayMessage("", _("Starting chess program"));
1413 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1418 CalculateIndex (int index, int gameNr)
1419 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1421 if(index > 0) return index; // fixed nmber
1422 if(index == 0) return 1;
1423 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1424 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1429 LoadGameOrPosition (int gameNr)
1430 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1431 if (*appData.loadGameFile != NULLCHAR) {
1432 if (!LoadGameFromFile(appData.loadGameFile,
1433 CalculateIndex(appData.loadGameIndex, gameNr),
1434 appData.loadGameFile, FALSE)) {
1435 DisplayFatalError(_("Bad game file"), 0, 1);
1438 } else if (*appData.loadPositionFile != NULLCHAR) {
1439 if (!LoadPositionFromFile(appData.loadPositionFile,
1440 CalculateIndex(appData.loadPositionIndex, gameNr),
1441 appData.loadPositionFile)) {
1442 DisplayFatalError(_("Bad position file"), 0, 1);
1450 ReserveGame (int gameNr, char resChar)
1452 FILE *tf = fopen(appData.tourneyFile, "r+");
1453 char *p, *q, c, buf[MSG_SIZ];
1454 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1455 safeStrCpy(buf, lastMsg, MSG_SIZ);
1456 DisplayMessage(_("Pick new game"), "");
1457 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1458 ParseArgsFromFile(tf);
1459 p = q = appData.results;
1460 if(appData.debugMode) {
1461 char *r = appData.participants;
1462 fprintf(debugFP, "results = '%s'\n", p);
1463 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1464 fprintf(debugFP, "\n");
1466 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1468 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1469 safeStrCpy(q, p, strlen(p) + 2);
1470 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1471 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1472 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1473 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1476 fseek(tf, -(strlen(p)+4), SEEK_END);
1478 if(c != '"') // depending on DOS or Unix line endings we can be one off
1479 fseek(tf, -(strlen(p)+2), SEEK_END);
1480 else fseek(tf, -(strlen(p)+3), SEEK_END);
1481 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1482 DisplayMessage(buf, "");
1483 free(p); appData.results = q;
1484 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1485 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1486 int round = appData.defaultMatchGames * appData.tourneyType;
1487 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1488 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1489 UnloadEngine(&first); // next game belongs to other pairing;
1490 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1492 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1496 MatchEvent (int mode)
1497 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1499 if(matchMode) { // already in match mode: switch it off
1501 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1504 // if(gameMode != BeginningOfGame) {
1505 // DisplayError(_("You can only start a match from the initial position."), 0);
1509 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1510 /* Set up machine vs. machine match */
1512 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1513 if(appData.tourneyFile[0]) {
1515 if(nextGame > appData.matchGames) {
1517 if(strchr(appData.results, '*') == NULL) {
1519 appData.tourneyCycles++;
1520 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1522 NextTourneyGame(-1, &dummy);
1524 if(nextGame <= appData.matchGames) {
1525 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1527 ScheduleDelayedEvent(NextMatchGame, 10000);
1532 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1533 DisplayError(buf, 0);
1534 appData.tourneyFile[0] = 0;
1538 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1539 DisplayFatalError(_("Can't have a match with no chess programs"),
1544 matchGame = roundNr = 1;
1545 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1549 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1552 InitBackEnd3 P((void))
1554 GameMode initialMode;
1558 InitChessProgram(&first, startedFromSetupPosition);
1560 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1561 free(programVersion);
1562 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1563 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1564 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1567 if (appData.icsActive) {
1569 /* [DM] Make a console window if needed [HGM] merged ifs */
1575 if (*appData.icsCommPort != NULLCHAR)
1576 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1577 appData.icsCommPort);
1579 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1580 appData.icsHost, appData.icsPort);
1582 if( (len >= MSG_SIZ) && appData.debugMode )
1583 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1585 DisplayFatalError(buf, err, 1);
1590 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1592 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1593 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1594 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1595 } else if (appData.noChessProgram) {
1601 if (*appData.cmailGameName != NULLCHAR) {
1603 OpenLoopback(&cmailPR);
1605 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1609 DisplayMessage("", "");
1610 if (StrCaseCmp(appData.initialMode, "") == 0) {
1611 initialMode = BeginningOfGame;
1612 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1613 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1614 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1615 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1618 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1619 initialMode = TwoMachinesPlay;
1620 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1621 initialMode = AnalyzeFile;
1622 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1623 initialMode = AnalyzeMode;
1624 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1625 initialMode = MachinePlaysWhite;
1626 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1627 initialMode = MachinePlaysBlack;
1628 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1629 initialMode = EditGame;
1630 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1631 initialMode = EditPosition;
1632 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1633 initialMode = Training;
1635 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1636 if( (len >= MSG_SIZ) && appData.debugMode )
1637 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1639 DisplayFatalError(buf, 0, 2);
1643 if (appData.matchMode) {
1644 if(appData.tourneyFile[0]) { // start tourney from command line
1646 if(f = fopen(appData.tourneyFile, "r")) {
1647 ParseArgsFromFile(f); // make sure tourney parmeters re known
1649 appData.clockMode = TRUE;
1651 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1654 } else if (*appData.cmailGameName != NULLCHAR) {
1655 /* Set up cmail mode */
1656 ReloadCmailMsgEvent(TRUE);
1658 /* Set up other modes */
1659 if (initialMode == AnalyzeFile) {
1660 if (*appData.loadGameFile == NULLCHAR) {
1661 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1665 if (*appData.loadGameFile != NULLCHAR) {
1666 (void) LoadGameFromFile(appData.loadGameFile,
1667 appData.loadGameIndex,
1668 appData.loadGameFile, TRUE);
1669 } else if (*appData.loadPositionFile != NULLCHAR) {
1670 (void) LoadPositionFromFile(appData.loadPositionFile,
1671 appData.loadPositionIndex,
1672 appData.loadPositionFile);
1673 /* [HGM] try to make self-starting even after FEN load */
1674 /* to allow automatic setup of fairy variants with wtm */
1675 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1676 gameMode = BeginningOfGame;
1677 setboardSpoiledMachineBlack = 1;
1679 /* [HGM] loadPos: make that every new game uses the setup */
1680 /* from file as long as we do not switch variant */
1681 if(!blackPlaysFirst) {
1682 startedFromPositionFile = TRUE;
1683 CopyBoard(filePosition, boards[0]);
1686 if (initialMode == AnalyzeMode) {
1687 if (appData.noChessProgram) {
1688 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1691 if (appData.icsActive) {
1692 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1696 } else if (initialMode == AnalyzeFile) {
1697 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1698 ShowThinkingEvent();
1700 AnalysisPeriodicEvent(1);
1701 } else if (initialMode == MachinePlaysWhite) {
1702 if (appData.noChessProgram) {
1703 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1707 if (appData.icsActive) {
1708 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1712 MachineWhiteEvent();
1713 } else if (initialMode == MachinePlaysBlack) {
1714 if (appData.noChessProgram) {
1715 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1719 if (appData.icsActive) {
1720 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1724 MachineBlackEvent();
1725 } else if (initialMode == TwoMachinesPlay) {
1726 if (appData.noChessProgram) {
1727 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1731 if (appData.icsActive) {
1732 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1737 } else if (initialMode == EditGame) {
1739 } else if (initialMode == EditPosition) {
1740 EditPositionEvent();
1741 } else if (initialMode == Training) {
1742 if (*appData.loadGameFile == NULLCHAR) {
1743 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1752 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1754 DisplayBook(current+1);
1756 MoveHistorySet( movelist, first, last, current, pvInfoList );
1758 EvalGraphSet( first, last, current, pvInfoList );
1760 MakeEngineOutputTitle();
1764 * Establish will establish a contact to a remote host.port.
1765 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1766 * used to talk to the host.
1767 * Returns 0 if okay, error code if not.
1774 if (*appData.icsCommPort != NULLCHAR) {
1775 /* Talk to the host through a serial comm port */
1776 return OpenCommPort(appData.icsCommPort, &icsPR);
1778 } else if (*appData.gateway != NULLCHAR) {
1779 if (*appData.remoteShell == NULLCHAR) {
1780 /* Use the rcmd protocol to run telnet program on a gateway host */
1781 snprintf(buf, sizeof(buf), "%s %s %s",
1782 appData.telnetProgram, appData.icsHost, appData.icsPort);
1783 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1786 /* Use the rsh program to run telnet program on a gateway host */
1787 if (*appData.remoteUser == NULLCHAR) {
1788 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1789 appData.gateway, appData.telnetProgram,
1790 appData.icsHost, appData.icsPort);
1792 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1793 appData.remoteShell, appData.gateway,
1794 appData.remoteUser, appData.telnetProgram,
1795 appData.icsHost, appData.icsPort);
1797 return StartChildProcess(buf, "", &icsPR);
1800 } else if (appData.useTelnet) {
1801 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1804 /* TCP socket interface differs somewhat between
1805 Unix and NT; handle details in the front end.
1807 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1812 EscapeExpand (char *p, char *q)
1813 { // [HGM] initstring: routine to shape up string arguments
1814 while(*p++ = *q++) if(p[-1] == '\\')
1816 case 'n': p[-1] = '\n'; break;
1817 case 'r': p[-1] = '\r'; break;
1818 case 't': p[-1] = '\t'; break;
1819 case '\\': p[-1] = '\\'; break;
1820 case 0: *p = 0; return;
1821 default: p[-1] = q[-1]; break;
1826 show_bytes (FILE *fp, char *buf, int count)
1829 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1830 fprintf(fp, "\\%03o", *buf & 0xff);
1839 /* Returns an errno value */
1841 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1843 char buf[8192], *p, *q, *buflim;
1844 int left, newcount, outcount;
1846 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1847 *appData.gateway != NULLCHAR) {
1848 if (appData.debugMode) {
1849 fprintf(debugFP, ">ICS: ");
1850 show_bytes(debugFP, message, count);
1851 fprintf(debugFP, "\n");
1853 return OutputToProcess(pr, message, count, outError);
1856 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1863 if (appData.debugMode) {
1864 fprintf(debugFP, ">ICS: ");
1865 show_bytes(debugFP, buf, newcount);
1866 fprintf(debugFP, "\n");
1868 outcount = OutputToProcess(pr, buf, newcount, outError);
1869 if (outcount < newcount) return -1; /* to be sure */
1876 } else if (((unsigned char) *p) == TN_IAC) {
1877 *q++ = (char) TN_IAC;
1884 if (appData.debugMode) {
1885 fprintf(debugFP, ">ICS: ");
1886 show_bytes(debugFP, buf, newcount);
1887 fprintf(debugFP, "\n");
1889 outcount = OutputToProcess(pr, buf, newcount, outError);
1890 if (outcount < newcount) return -1; /* to be sure */
1895 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1897 int outError, outCount;
1898 static int gotEof = 0;
1901 /* Pass data read from player on to ICS */
1904 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1905 if (outCount < count) {
1906 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1908 if(have_sent_ICS_logon == 2) {
1909 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1910 fprintf(ini, "%s", message);
1911 have_sent_ICS_logon = 3;
1913 have_sent_ICS_logon = 1;
1914 } else if(have_sent_ICS_logon == 3) {
1915 fprintf(ini, "%s", message);
1917 have_sent_ICS_logon = 1;
1919 } else if (count < 0) {
1920 RemoveInputSource(isr);
1921 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1922 } else if (gotEof++ > 0) {
1923 RemoveInputSource(isr);
1924 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1930 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1931 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1932 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1933 SendToICS("date\n");
1934 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1937 /* added routine for printf style output to ics */
1939 ics_printf (char *format, ...)
1941 char buffer[MSG_SIZ];
1944 va_start(args, format);
1945 vsnprintf(buffer, sizeof(buffer), format, args);
1946 buffer[sizeof(buffer)-1] = '\0';
1954 int count, outCount, outError;
1956 if (icsPR == NoProc) return;
1959 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1960 if (outCount < count) {
1961 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1965 /* This is used for sending logon scripts to the ICS. Sending
1966 without a delay causes problems when using timestamp on ICC
1967 (at least on my machine). */
1969 SendToICSDelayed (char *s, long msdelay)
1971 int count, outCount, outError;
1973 if (icsPR == NoProc) return;
1976 if (appData.debugMode) {
1977 fprintf(debugFP, ">ICS: ");
1978 show_bytes(debugFP, s, count);
1979 fprintf(debugFP, "\n");
1981 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1983 if (outCount < count) {
1984 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1989 /* Remove all highlighting escape sequences in s
1990 Also deletes any suffix starting with '('
1993 StripHighlightAndTitle (char *s)
1995 static char retbuf[MSG_SIZ];
1998 while (*s != NULLCHAR) {
1999 while (*s == '\033') {
2000 while (*s != NULLCHAR && !isalpha(*s)) s++;
2001 if (*s != NULLCHAR) s++;
2003 while (*s != NULLCHAR && *s != '\033') {
2004 if (*s == '(' || *s == '[') {
2015 /* Remove all highlighting escape sequences in s */
2017 StripHighlight (char *s)
2019 static char retbuf[MSG_SIZ];
2022 while (*s != NULLCHAR) {
2023 while (*s == '\033') {
2024 while (*s != NULLCHAR && !isalpha(*s)) s++;
2025 if (*s != NULLCHAR) s++;
2027 while (*s != NULLCHAR && *s != '\033') {
2035 char engineVariant[MSG_SIZ];
2036 char *variantNames[] = VARIANT_NAMES;
2038 VariantName (VariantClass v)
2040 if(v == VariantUnknown || *engineVariant) return engineVariant;
2041 return variantNames[v];
2045 /* Identify a variant from the strings the chess servers use or the
2046 PGN Variant tag names we use. */
2048 StringToVariant (char *e)
2052 VariantClass v = VariantNormal;
2053 int i, found = FALSE;
2059 /* [HGM] skip over optional board-size prefixes */
2060 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2061 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2062 while( *e++ != '_');
2065 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2069 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2070 if (StrCaseStr(e, variantNames[i])) {
2071 v = (VariantClass) i;
2078 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2079 || StrCaseStr(e, "wild/fr")
2080 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2081 v = VariantFischeRandom;
2082 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2083 (i = 1, p = StrCaseStr(e, "w"))) {
2085 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2092 case 0: /* FICS only, actually */
2094 /* Castling legal even if K starts on d-file */
2095 v = VariantWildCastle;
2100 /* Castling illegal even if K & R happen to start in
2101 normal positions. */
2102 v = VariantNoCastle;
2115 /* Castling legal iff K & R start in normal positions */
2121 /* Special wilds for position setup; unclear what to do here */
2122 v = VariantLoadable;
2125 /* Bizarre ICC game */
2126 v = VariantTwoKings;
2129 v = VariantKriegspiel;
2135 v = VariantFischeRandom;
2138 v = VariantCrazyhouse;
2141 v = VariantBughouse;
2147 /* Not quite the same as FICS suicide! */
2148 v = VariantGiveaway;
2154 v = VariantShatranj;
2157 /* Temporary names for future ICC types. The name *will* change in
2158 the next xboard/WinBoard release after ICC defines it. */
2196 v = VariantCapablanca;
2199 v = VariantKnightmate;
2205 v = VariantCylinder;
2211 v = VariantCapaRandom;
2214 v = VariantBerolina;
2226 /* Found "wild" or "w" in the string but no number;
2227 must assume it's normal chess. */
2231 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2232 if( (len >= MSG_SIZ) && appData.debugMode )
2233 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2235 DisplayError(buf, 0);
2241 if (appData.debugMode) {
2242 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2243 e, wnum, VariantName(v));
2248 static int leftover_start = 0, leftover_len = 0;
2249 char star_match[STAR_MATCH_N][MSG_SIZ];
2251 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2252 advance *index beyond it, and set leftover_start to the new value of
2253 *index; else return FALSE. If pattern contains the character '*', it
2254 matches any sequence of characters not containing '\r', '\n', or the
2255 character following the '*' (if any), and the matched sequence(s) are
2256 copied into star_match.
2259 looking_at ( char *buf, int *index, char *pattern)
2261 char *bufp = &buf[*index], *patternp = pattern;
2263 char *matchp = star_match[0];
2266 if (*patternp == NULLCHAR) {
2267 *index = leftover_start = bufp - buf;
2271 if (*bufp == NULLCHAR) return FALSE;
2272 if (*patternp == '*') {
2273 if (*bufp == *(patternp + 1)) {
2275 matchp = star_match[++star_count];
2279 } else if (*bufp == '\n' || *bufp == '\r') {
2281 if (*patternp == NULLCHAR)
2286 *matchp++ = *bufp++;
2290 if (*patternp != *bufp) return FALSE;
2297 SendToPlayer (char *data, int length)
2299 int error, outCount;
2300 outCount = OutputToProcess(NoProc, data, length, &error);
2301 if (outCount < length) {
2302 DisplayFatalError(_("Error writing to display"), error, 1);
2307 PackHolding (char packed[], char *holding)
2317 switch (runlength) {
2328 sprintf(q, "%d", runlength);
2340 /* Telnet protocol requests from the front end */
2342 TelnetRequest (unsigned char ddww, unsigned char option)
2344 unsigned char msg[3];
2345 int outCount, outError;
2347 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2349 if (appData.debugMode) {
2350 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2366 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2375 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2378 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2383 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2385 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2392 if (!appData.icsActive) return;
2393 TelnetRequest(TN_DO, TN_ECHO);
2399 if (!appData.icsActive) return;
2400 TelnetRequest(TN_DONT, TN_ECHO);
2404 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2406 /* put the holdings sent to us by the server on the board holdings area */
2407 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2411 if(gameInfo.holdingsWidth < 2) return;
2412 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2413 return; // prevent overwriting by pre-board holdings
2415 if( (int)lowestPiece >= BlackPawn ) {
2418 holdingsStartRow = BOARD_HEIGHT-1;
2421 holdingsColumn = BOARD_WIDTH-1;
2422 countsColumn = BOARD_WIDTH-2;
2423 holdingsStartRow = 0;
2427 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2428 board[i][holdingsColumn] = EmptySquare;
2429 board[i][countsColumn] = (ChessSquare) 0;
2431 while( (p=*holdings++) != NULLCHAR ) {
2432 piece = CharToPiece( ToUpper(p) );
2433 if(piece == EmptySquare) continue;
2434 /*j = (int) piece - (int) WhitePawn;*/
2435 j = PieceToNumber(piece);
2436 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2437 if(j < 0) continue; /* should not happen */
2438 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2439 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2440 board[holdingsStartRow+j*direction][countsColumn]++;
2446 VariantSwitch (Board board, VariantClass newVariant)
2448 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2449 static Board oldBoard;
2451 startedFromPositionFile = FALSE;
2452 if(gameInfo.variant == newVariant) return;
2454 /* [HGM] This routine is called each time an assignment is made to
2455 * gameInfo.variant during a game, to make sure the board sizes
2456 * are set to match the new variant. If that means adding or deleting
2457 * holdings, we shift the playing board accordingly
2458 * This kludge is needed because in ICS observe mode, we get boards
2459 * of an ongoing game without knowing the variant, and learn about the
2460 * latter only later. This can be because of the move list we requested,
2461 * in which case the game history is refilled from the beginning anyway,
2462 * but also when receiving holdings of a crazyhouse game. In the latter
2463 * case we want to add those holdings to the already received position.
2467 if (appData.debugMode) {
2468 fprintf(debugFP, "Switch board from %s to %s\n",
2469 VariantName(gameInfo.variant), VariantName(newVariant));
2470 setbuf(debugFP, NULL);
2472 shuffleOpenings = 0; /* [HGM] shuffle */
2473 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2477 newWidth = 9; newHeight = 9;
2478 gameInfo.holdingsSize = 7;
2479 case VariantBughouse:
2480 case VariantCrazyhouse:
2481 newHoldingsWidth = 2; break;
2485 newHoldingsWidth = 2;
2486 gameInfo.holdingsSize = 8;
2489 case VariantCapablanca:
2490 case VariantCapaRandom:
2493 newHoldingsWidth = gameInfo.holdingsSize = 0;
2496 if(newWidth != gameInfo.boardWidth ||
2497 newHeight != gameInfo.boardHeight ||
2498 newHoldingsWidth != gameInfo.holdingsWidth ) {
2500 /* shift position to new playing area, if needed */
2501 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2502 for(i=0; i<BOARD_HEIGHT; i++)
2503 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2504 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2506 for(i=0; i<newHeight; i++) {
2507 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2508 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2510 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2511 for(i=0; i<BOARD_HEIGHT; i++)
2512 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2513 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2516 board[HOLDINGS_SET] = 0;
2517 gameInfo.boardWidth = newWidth;
2518 gameInfo.boardHeight = newHeight;
2519 gameInfo.holdingsWidth = newHoldingsWidth;
2520 gameInfo.variant = newVariant;
2521 InitDrawingSizes(-2, 0);
2522 } else gameInfo.variant = newVariant;
2523 CopyBoard(oldBoard, board); // remember correctly formatted board
2524 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2525 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2528 static int loggedOn = FALSE;
2530 /*-- Game start info cache: --*/
2532 char gs_kind[MSG_SIZ];
2533 static char player1Name[128] = "";
2534 static char player2Name[128] = "";
2535 static char cont_seq[] = "\n\\ ";
2536 static int player1Rating = -1;
2537 static int player2Rating = -1;
2538 /*----------------------------*/
2540 ColorClass curColor = ColorNormal;
2541 int suppressKibitz = 0;
2544 Boolean soughtPending = FALSE;
2545 Boolean seekGraphUp;
2546 #define MAX_SEEK_ADS 200
2548 char *seekAdList[MAX_SEEK_ADS];
2549 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2550 float tcList[MAX_SEEK_ADS];
2551 char colorList[MAX_SEEK_ADS];
2552 int nrOfSeekAds = 0;
2553 int minRating = 1010, maxRating = 2800;
2554 int hMargin = 10, vMargin = 20, h, w;
2555 extern int squareSize, lineGap;
2560 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2561 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2562 if(r < minRating+100 && r >=0 ) r = minRating+100;
2563 if(r > maxRating) r = maxRating;
2564 if(tc < 1.f) tc = 1.f;
2565 if(tc > 95.f) tc = 95.f;
2566 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2567 y = ((double)r - minRating)/(maxRating - minRating)
2568 * (h-vMargin-squareSize/8-1) + vMargin;
2569 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2570 if(strstr(seekAdList[i], " u ")) color = 1;
2571 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2572 !strstr(seekAdList[i], "bullet") &&
2573 !strstr(seekAdList[i], "blitz") &&
2574 !strstr(seekAdList[i], "standard") ) color = 2;
2575 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2576 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2580 PlotSingleSeekAd (int i)
2586 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2588 char buf[MSG_SIZ], *ext = "";
2589 VariantClass v = StringToVariant(type);
2590 if(strstr(type, "wild")) {
2591 ext = type + 4; // append wild number
2592 if(v == VariantFischeRandom) type = "chess960"; else
2593 if(v == VariantLoadable) type = "setup"; else
2594 type = VariantName(v);
2596 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2597 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2598 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2599 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2600 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2601 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2602 seekNrList[nrOfSeekAds] = nr;
2603 zList[nrOfSeekAds] = 0;
2604 seekAdList[nrOfSeekAds++] = StrSave(buf);
2605 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2610 EraseSeekDot (int i)
2612 int x = xList[i], y = yList[i], d=squareSize/4, k;
2613 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2614 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2615 // now replot every dot that overlapped
2616 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2617 int xx = xList[k], yy = yList[k];
2618 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2619 DrawSeekDot(xx, yy, colorList[k]);
2624 RemoveSeekAd (int nr)
2627 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2629 if(seekAdList[i]) free(seekAdList[i]);
2630 seekAdList[i] = seekAdList[--nrOfSeekAds];
2631 seekNrList[i] = seekNrList[nrOfSeekAds];
2632 ratingList[i] = ratingList[nrOfSeekAds];
2633 colorList[i] = colorList[nrOfSeekAds];
2634 tcList[i] = tcList[nrOfSeekAds];
2635 xList[i] = xList[nrOfSeekAds];
2636 yList[i] = yList[nrOfSeekAds];
2637 zList[i] = zList[nrOfSeekAds];
2638 seekAdList[nrOfSeekAds] = NULL;
2644 MatchSoughtLine (char *line)
2646 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2647 int nr, base, inc, u=0; char dummy;
2649 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2650 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2652 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2653 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2654 // match: compact and save the line
2655 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2665 if(!seekGraphUp) return FALSE;
2666 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2667 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2669 DrawSeekBackground(0, 0, w, h);
2670 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2671 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2672 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2673 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2675 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2678 snprintf(buf, MSG_SIZ, "%d", i);
2679 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2682 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2683 for(i=1; i<100; i+=(i<10?1:5)) {
2684 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2685 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2686 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2688 snprintf(buf, MSG_SIZ, "%d", i);
2689 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2692 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2697 SeekGraphClick (ClickType click, int x, int y, int moving)
2699 static int lastDown = 0, displayed = 0, lastSecond;
2700 if(y < 0) return FALSE;
2701 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2702 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2703 if(!seekGraphUp) return FALSE;
2704 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2705 DrawPosition(TRUE, NULL);
2708 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2709 if(click == Release || moving) return FALSE;
2711 soughtPending = TRUE;
2712 SendToICS(ics_prefix);
2713 SendToICS("sought\n"); // should this be "sought all"?
2714 } else { // issue challenge based on clicked ad
2715 int dist = 10000; int i, closest = 0, second = 0;
2716 for(i=0; i<nrOfSeekAds; i++) {
2717 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2718 if(d < dist) { dist = d; closest = i; }
2719 second += (d - zList[i] < 120); // count in-range ads
2720 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2724 second = (second > 1);
2725 if(displayed != closest || second != lastSecond) {
2726 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2727 lastSecond = second; displayed = closest;
2729 if(click == Press) {
2730 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2733 } // on press 'hit', only show info
2734 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2735 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2736 SendToICS(ics_prefix);
2738 return TRUE; // let incoming board of started game pop down the graph
2739 } else if(click == Release) { // release 'miss' is ignored
2740 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2741 if(moving == 2) { // right up-click
2742 nrOfSeekAds = 0; // refresh graph
2743 soughtPending = TRUE;
2744 SendToICS(ics_prefix);
2745 SendToICS("sought\n"); // should this be "sought all"?
2748 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2749 // press miss or release hit 'pop down' seek graph
2750 seekGraphUp = FALSE;
2751 DrawPosition(TRUE, NULL);
2757 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2759 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2760 #define STARTED_NONE 0
2761 #define STARTED_MOVES 1
2762 #define STARTED_BOARD 2
2763 #define STARTED_OBSERVE 3
2764 #define STARTED_HOLDINGS 4
2765 #define STARTED_CHATTER 5
2766 #define STARTED_COMMENT 6
2767 #define STARTED_MOVES_NOHIDE 7
2769 static int started = STARTED_NONE;
2770 static char parse[20000];
2771 static int parse_pos = 0;
2772 static char buf[BUF_SIZE + 1];
2773 static int firstTime = TRUE, intfSet = FALSE;
2774 static ColorClass prevColor = ColorNormal;
2775 static int savingComment = FALSE;
2776 static int cmatch = 0; // continuation sequence match
2783 int backup; /* [DM] For zippy color lines */
2785 char talker[MSG_SIZ]; // [HGM] chat
2788 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2790 if (appData.debugMode) {
2792 fprintf(debugFP, "<ICS: ");
2793 show_bytes(debugFP, data, count);
2794 fprintf(debugFP, "\n");
2798 if (appData.debugMode) { int f = forwardMostMove;
2799 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2800 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2801 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2804 /* If last read ended with a partial line that we couldn't parse,
2805 prepend it to the new read and try again. */
2806 if (leftover_len > 0) {
2807 for (i=0; i<leftover_len; i++)
2808 buf[i] = buf[leftover_start + i];
2811 /* copy new characters into the buffer */
2812 bp = buf + leftover_len;
2813 buf_len=leftover_len;
2814 for (i=0; i<count; i++)
2817 if (data[i] == '\r')
2820 // join lines split by ICS?
2821 if (!appData.noJoin)
2824 Joining just consists of finding matches against the
2825 continuation sequence, and discarding that sequence
2826 if found instead of copying it. So, until a match
2827 fails, there's nothing to do since it might be the
2828 complete sequence, and thus, something we don't want
2831 if (data[i] == cont_seq[cmatch])
2834 if (cmatch == strlen(cont_seq))
2836 cmatch = 0; // complete match. just reset the counter
2839 it's possible for the ICS to not include the space
2840 at the end of the last word, making our [correct]
2841 join operation fuse two separate words. the server
2842 does this when the space occurs at the width setting.
2844 if (!buf_len || buf[buf_len-1] != ' ')
2855 match failed, so we have to copy what matched before
2856 falling through and copying this character. In reality,
2857 this will only ever be just the newline character, but
2858 it doesn't hurt to be precise.
2860 strncpy(bp, cont_seq, cmatch);
2872 buf[buf_len] = NULLCHAR;
2873 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2878 while (i < buf_len) {
2879 /* Deal with part of the TELNET option negotiation
2880 protocol. We refuse to do anything beyond the
2881 defaults, except that we allow the WILL ECHO option,
2882 which ICS uses to turn off password echoing when we are
2883 directly connected to it. We reject this option
2884 if localLineEditing mode is on (always on in xboard)
2885 and we are talking to port 23, which might be a real
2886 telnet server that will try to keep WILL ECHO on permanently.
2888 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2889 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2890 unsigned char option;
2892 switch ((unsigned char) buf[++i]) {
2894 if (appData.debugMode)
2895 fprintf(debugFP, "\n<WILL ");
2896 switch (option = (unsigned char) buf[++i]) {
2898 if (appData.debugMode)
2899 fprintf(debugFP, "ECHO ");
2900 /* Reply only if this is a change, according
2901 to the protocol rules. */
2902 if (remoteEchoOption) break;
2903 if (appData.localLineEditing &&
2904 atoi(appData.icsPort) == TN_PORT) {
2905 TelnetRequest(TN_DONT, TN_ECHO);
2908 TelnetRequest(TN_DO, TN_ECHO);
2909 remoteEchoOption = TRUE;
2913 if (appData.debugMode)
2914 fprintf(debugFP, "%d ", option);
2915 /* Whatever this is, we don't want it. */
2916 TelnetRequest(TN_DONT, option);
2921 if (appData.debugMode)
2922 fprintf(debugFP, "\n<WONT ");
2923 switch (option = (unsigned char) buf[++i]) {
2925 if (appData.debugMode)
2926 fprintf(debugFP, "ECHO ");
2927 /* Reply only if this is a change, according
2928 to the protocol rules. */
2929 if (!remoteEchoOption) break;
2931 TelnetRequest(TN_DONT, TN_ECHO);
2932 remoteEchoOption = FALSE;
2935 if (appData.debugMode)
2936 fprintf(debugFP, "%d ", (unsigned char) option);
2937 /* Whatever this is, it must already be turned
2938 off, because we never agree to turn on
2939 anything non-default, so according to the
2940 protocol rules, we don't reply. */
2945 if (appData.debugMode)
2946 fprintf(debugFP, "\n<DO ");
2947 switch (option = (unsigned char) buf[++i]) {
2949 /* Whatever this is, we refuse to do it. */
2950 if (appData.debugMode)
2951 fprintf(debugFP, "%d ", option);
2952 TelnetRequest(TN_WONT, option);
2957 if (appData.debugMode)
2958 fprintf(debugFP, "\n<DONT ");
2959 switch (option = (unsigned char) buf[++i]) {
2961 if (appData.debugMode)
2962 fprintf(debugFP, "%d ", option);
2963 /* Whatever this is, we are already not doing
2964 it, because we never agree to do anything
2965 non-default, so according to the protocol
2966 rules, we don't reply. */
2971 if (appData.debugMode)
2972 fprintf(debugFP, "\n<IAC ");
2973 /* Doubled IAC; pass it through */
2977 if (appData.debugMode)
2978 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2979 /* Drop all other telnet commands on the floor */
2982 if (oldi > next_out)
2983 SendToPlayer(&buf[next_out], oldi - next_out);
2989 /* OK, this at least will *usually* work */
2990 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2994 if (loggedOn && !intfSet) {
2995 if (ics_type == ICS_ICC) {
2996 snprintf(str, MSG_SIZ,
2997 "/set-quietly interface %s\n/set-quietly style 12\n",
2999 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3000 strcat(str, "/set-2 51 1\n/set seek 1\n");
3001 } else if (ics_type == ICS_CHESSNET) {
3002 snprintf(str, MSG_SIZ, "/style 12\n");
3004 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3005 strcat(str, programVersion);
3006 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3007 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3008 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3010 strcat(str, "$iset nohighlight 1\n");
3012 strcat(str, "$iset lock 1\n$style 12\n");
3015 NotifyFrontendLogin();
3019 if (started == STARTED_COMMENT) {
3020 /* Accumulate characters in comment */
3021 parse[parse_pos++] = buf[i];
3022 if (buf[i] == '\n') {
3023 parse[parse_pos] = NULLCHAR;
3024 if(chattingPartner>=0) {
3026 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3027 OutputChatMessage(chattingPartner, mess);
3028 chattingPartner = -1;
3029 next_out = i+1; // [HGM] suppress printing in ICS window
3031 if(!suppressKibitz) // [HGM] kibitz
3032 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3033 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3034 int nrDigit = 0, nrAlph = 0, j;
3035 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3036 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3037 parse[parse_pos] = NULLCHAR;
3038 // try to be smart: if it does not look like search info, it should go to
3039 // ICS interaction window after all, not to engine-output window.
3040 for(j=0; j<parse_pos; j++) { // count letters and digits
3041 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3042 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3043 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3045 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3046 int depth=0; float score;
3047 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3048 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3049 pvInfoList[forwardMostMove-1].depth = depth;
3050 pvInfoList[forwardMostMove-1].score = 100*score;
3052 OutputKibitz(suppressKibitz, parse);
3055 if(gameMode == IcsObserving) // restore original ICS messages
3056 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3057 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3059 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3060 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3061 SendToPlayer(tmp, strlen(tmp));
3063 next_out = i+1; // [HGM] suppress printing in ICS window
3065 started = STARTED_NONE;
3067 /* Don't match patterns against characters in comment */
3072 if (started == STARTED_CHATTER) {
3073 if (buf[i] != '\n') {
3074 /* Don't match patterns against characters in chatter */
3078 started = STARTED_NONE;
3079 if(suppressKibitz) next_out = i+1;
3082 /* Kludge to deal with rcmd protocol */
3083 if (firstTime && looking_at(buf, &i, "\001*")) {
3084 DisplayFatalError(&buf[1], 0, 1);
3090 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3093 if (appData.debugMode)
3094 fprintf(debugFP, "ics_type %d\n", ics_type);
3097 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3098 ics_type = ICS_FICS;
3100 if (appData.debugMode)
3101 fprintf(debugFP, "ics_type %d\n", ics_type);
3104 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3105 ics_type = ICS_CHESSNET;
3107 if (appData.debugMode)
3108 fprintf(debugFP, "ics_type %d\n", ics_type);
3113 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3114 looking_at(buf, &i, "Logging you in as \"*\"") ||
3115 looking_at(buf, &i, "will be \"*\""))) {
3116 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3120 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3122 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3123 DisplayIcsInteractionTitle(buf);
3124 have_set_title = TRUE;
3127 /* skip finger notes */
3128 if (started == STARTED_NONE &&
3129 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3130 (buf[i] == '1' && buf[i+1] == '0')) &&
3131 buf[i+2] == ':' && buf[i+3] == ' ') {
3132 started = STARTED_CHATTER;
3138 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3139 if(appData.seekGraph) {
3140 if(soughtPending && MatchSoughtLine(buf+i)) {
3141 i = strstr(buf+i, "rated") - buf;
3142 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3143 next_out = leftover_start = i;
3144 started = STARTED_CHATTER;
3145 suppressKibitz = TRUE;
3148 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3149 && looking_at(buf, &i, "* ads displayed")) {
3150 soughtPending = FALSE;
3155 if(appData.autoRefresh) {
3156 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3157 int s = (ics_type == ICS_ICC); // ICC format differs
3159 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3160 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3161 looking_at(buf, &i, "*% "); // eat prompt
3162 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3163 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3164 next_out = i; // suppress
3167 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3168 char *p = star_match[0];
3170 if(seekGraphUp) RemoveSeekAd(atoi(p));
3171 while(*p && *p++ != ' '); // next
3173 looking_at(buf, &i, "*% "); // eat prompt
3174 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3181 /* skip formula vars */
3182 if (started == STARTED_NONE &&
3183 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3184 started = STARTED_CHATTER;
3189 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3190 if (appData.autoKibitz && started == STARTED_NONE &&
3191 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3192 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3193 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3194 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3195 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3196 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3197 suppressKibitz = TRUE;
3198 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3200 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3201 && (gameMode == IcsPlayingWhite)) ||
3202 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3203 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3204 started = STARTED_CHATTER; // own kibitz we simply discard
3206 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3207 parse_pos = 0; parse[0] = NULLCHAR;
3208 savingComment = TRUE;
3209 suppressKibitz = gameMode != IcsObserving ? 2 :
3210 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3214 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3215 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3216 && atoi(star_match[0])) {
3217 // suppress the acknowledgements of our own autoKibitz
3219 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3221 SendToPlayer(star_match[0], strlen(star_match[0]));
3222 if(looking_at(buf, &i, "*% ")) // eat prompt
3223 suppressKibitz = FALSE;
3227 } // [HGM] kibitz: end of patch
3229 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3231 // [HGM] chat: intercept tells by users for which we have an open chat window
3233 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3234 looking_at(buf, &i, "* whispers:") ||
3235 looking_at(buf, &i, "* kibitzes:") ||
3236 looking_at(buf, &i, "* shouts:") ||
3237 looking_at(buf, &i, "* c-shouts:") ||
3238 looking_at(buf, &i, "--> * ") ||
3239 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3240 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3241 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3242 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3244 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3245 chattingPartner = -1;
3247 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3248 for(p=0; p<MAX_CHAT; p++) {
3249 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3250 talker[0] = '['; strcat(talker, "] ");
3251 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3252 chattingPartner = p; break;
3255 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3256 for(p=0; p<MAX_CHAT; p++) {
3257 if(!strcmp("kibitzes", chatPartner[p])) {
3258 talker[0] = '['; strcat(talker, "] ");
3259 chattingPartner = p; break;
3262 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3263 for(p=0; p<MAX_CHAT; p++) {
3264 if(!strcmp("whispers", chatPartner[p])) {
3265 talker[0] = '['; strcat(talker, "] ");
3266 chattingPartner = p; break;
3269 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3270 if(buf[i-8] == '-' && buf[i-3] == 't')
3271 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3272 if(!strcmp("c-shouts", chatPartner[p])) {
3273 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3274 chattingPartner = p; break;
3277 if(chattingPartner < 0)
3278 for(p=0; p<MAX_CHAT; p++) {
3279 if(!strcmp("shouts", chatPartner[p])) {
3280 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3281 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3282 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3283 chattingPartner = p; break;
3287 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3288 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3289 talker[0] = 0; Colorize(ColorTell, FALSE);
3290 chattingPartner = p; break;
3292 if(chattingPartner<0) i = oldi; else {
3293 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3294 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3295 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3296 started = STARTED_COMMENT;
3297 parse_pos = 0; parse[0] = NULLCHAR;
3298 savingComment = 3 + chattingPartner; // counts as TRUE
3299 suppressKibitz = TRUE;
3302 } // [HGM] chat: end of patch
3305 if (appData.zippyTalk || appData.zippyPlay) {
3306 /* [DM] Backup address for color zippy lines */
3308 if (loggedOn == TRUE)
3309 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3310 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3312 } // [DM] 'else { ' deleted
3314 /* Regular tells and says */
3315 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3316 looking_at(buf, &i, "* (your partner) tells you: ") ||
3317 looking_at(buf, &i, "* says: ") ||
3318 /* Don't color "message" or "messages" output */
3319 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3320 looking_at(buf, &i, "*. * at *:*: ") ||
3321 looking_at(buf, &i, "--* (*:*): ") ||
3322 /* Message notifications (same color as tells) */
3323 looking_at(buf, &i, "* has left a message ") ||
3324 looking_at(buf, &i, "* just sent you a message:\n") ||
3325 /* Whispers and kibitzes */
3326 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3327 looking_at(buf, &i, "* kibitzes: ") ||
3329 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3331 if (tkind == 1 && strchr(star_match[0], ':')) {
3332 /* Avoid "tells you:" spoofs in channels */
3335 if (star_match[0][0] == NULLCHAR ||
3336 strchr(star_match[0], ' ') ||
3337 (tkind == 3 && strchr(star_match[1], ' '))) {
3338 /* Reject bogus matches */
3341 if (appData.colorize) {
3342 if (oldi > next_out) {
3343 SendToPlayer(&buf[next_out], oldi - next_out);
3348 Colorize(ColorTell, FALSE);
3349 curColor = ColorTell;
3352 Colorize(ColorKibitz, FALSE);
3353 curColor = ColorKibitz;
3356 p = strrchr(star_match[1], '(');
3363 Colorize(ColorChannel1, FALSE);
3364 curColor = ColorChannel1;
3366 Colorize(ColorChannel, FALSE);
3367 curColor = ColorChannel;
3371 curColor = ColorNormal;
3375 if (started == STARTED_NONE && appData.autoComment &&
3376 (gameMode == IcsObserving ||
3377 gameMode == IcsPlayingWhite ||
3378 gameMode == IcsPlayingBlack)) {
3379 parse_pos = i - oldi;
3380 memcpy(parse, &buf[oldi], parse_pos);
3381 parse[parse_pos] = NULLCHAR;
3382 started = STARTED_COMMENT;
3383 savingComment = TRUE;
3385 started = STARTED_CHATTER;
3386 savingComment = FALSE;
3393 if (looking_at(buf, &i, "* s-shouts: ") ||
3394 looking_at(buf, &i, "* c-shouts: ")) {
3395 if (appData.colorize) {
3396 if (oldi > next_out) {
3397 SendToPlayer(&buf[next_out], oldi - next_out);
3400 Colorize(ColorSShout, FALSE);
3401 curColor = ColorSShout;
3404 started = STARTED_CHATTER;
3408 if (looking_at(buf, &i, "--->")) {
3413 if (looking_at(buf, &i, "* shouts: ") ||
3414 looking_at(buf, &i, "--> ")) {
3415 if (appData.colorize) {
3416 if (oldi > next_out) {
3417 SendToPlayer(&buf[next_out], oldi - next_out);
3420 Colorize(ColorShout, FALSE);
3421 curColor = ColorShout;
3424 started = STARTED_CHATTER;
3428 if (looking_at( buf, &i, "Challenge:")) {
3429 if (appData.colorize) {
3430 if (oldi > next_out) {
3431 SendToPlayer(&buf[next_out], oldi - next_out);
3434 Colorize(ColorChallenge, FALSE);
3435 curColor = ColorChallenge;
3441 if (looking_at(buf, &i, "* offers you") ||
3442 looking_at(buf, &i, "* offers to be") ||
3443 looking_at(buf, &i, "* would like to") ||
3444 looking_at(buf, &i, "* requests to") ||
3445 looking_at(buf, &i, "Your opponent offers") ||
3446 looking_at(buf, &i, "Your opponent requests")) {
3448 if (appData.colorize) {
3449 if (oldi > next_out) {
3450 SendToPlayer(&buf[next_out], oldi - next_out);
3453 Colorize(ColorRequest, FALSE);
3454 curColor = ColorRequest;
3459 if (looking_at(buf, &i, "* (*) seeking")) {
3460 if (appData.colorize) {
3461 if (oldi > next_out) {
3462 SendToPlayer(&buf[next_out], oldi - next_out);
3465 Colorize(ColorSeek, FALSE);
3466 curColor = ColorSeek;
3471 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3473 if (looking_at(buf, &i, "\\ ")) {
3474 if (prevColor != ColorNormal) {
3475 if (oldi > next_out) {
3476 SendToPlayer(&buf[next_out], oldi - next_out);
3479 Colorize(prevColor, TRUE);
3480 curColor = prevColor;
3482 if (savingComment) {
3483 parse_pos = i - oldi;
3484 memcpy(parse, &buf[oldi], parse_pos);
3485 parse[parse_pos] = NULLCHAR;
3486 started = STARTED_COMMENT;
3487 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3488 chattingPartner = savingComment - 3; // kludge to remember the box
3490 started = STARTED_CHATTER;
3495 if (looking_at(buf, &i, "Black Strength :") ||
3496 looking_at(buf, &i, "<<< style 10 board >>>") ||
3497 looking_at(buf, &i, "<10>") ||
3498 looking_at(buf, &i, "#@#")) {
3499 /* Wrong board style */
3501 SendToICS(ics_prefix);
3502 SendToICS("set style 12\n");
3503 SendToICS(ics_prefix);
3504 SendToICS("refresh\n");
3508 if (looking_at(buf, &i, "login:")) {
3509 if (!have_sent_ICS_logon) {
3511 have_sent_ICS_logon = 1;
3512 else // no init script was found
3513 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3514 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3515 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3520 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3521 (looking_at(buf, &i, "\n<12> ") ||
3522 looking_at(buf, &i, "<12> "))) {
3524 if (oldi > next_out) {
3525 SendToPlayer(&buf[next_out], oldi - next_out);
3528 started = STARTED_BOARD;
3533 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3534 looking_at(buf, &i, "<b1> ")) {
3535 if (oldi > next_out) {
3536 SendToPlayer(&buf[next_out], oldi - next_out);
3539 started = STARTED_HOLDINGS;
3544 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3546 /* Header for a move list -- first line */
3548 switch (ics_getting_history) {
3552 case BeginningOfGame:
3553 /* User typed "moves" or "oldmoves" while we
3554 were idle. Pretend we asked for these
3555 moves and soak them up so user can step
3556 through them and/or save them.
3559 gameMode = IcsObserving;
3562 ics_getting_history = H_GOT_UNREQ_HEADER;
3564 case EditGame: /*?*/
3565 case EditPosition: /*?*/
3566 /* Should above feature work in these modes too? */
3567 /* For now it doesn't */
3568 ics_getting_history = H_GOT_UNWANTED_HEADER;
3571 ics_getting_history = H_GOT_UNWANTED_HEADER;
3576 /* Is this the right one? */
3577 if (gameInfo.white && gameInfo.black &&
3578 strcmp(gameInfo.white, star_match[0]) == 0 &&
3579 strcmp(gameInfo.black, star_match[2]) == 0) {
3581 ics_getting_history = H_GOT_REQ_HEADER;
3584 case H_GOT_REQ_HEADER:
3585 case H_GOT_UNREQ_HEADER:
3586 case H_GOT_UNWANTED_HEADER:
3587 case H_GETTING_MOVES:
3588 /* Should not happen */
3589 DisplayError(_("Error gathering move list: two headers"), 0);
3590 ics_getting_history = H_FALSE;
3594 /* Save player ratings into gameInfo if needed */
3595 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3596 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3597 (gameInfo.whiteRating == -1 ||
3598 gameInfo.blackRating == -1)) {
3600 gameInfo.whiteRating = string_to_rating(star_match[1]);
3601 gameInfo.blackRating = string_to_rating(star_match[3]);
3602 if (appData.debugMode)
3603 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3604 gameInfo.whiteRating, gameInfo.blackRating);
3609 if (looking_at(buf, &i,
3610 "* * match, initial time: * minute*, increment: * second")) {
3611 /* Header for a move list -- second line */
3612 /* Initial board will follow if this is a wild game */
3613 if (gameInfo.event != NULL) free(gameInfo.event);
3614 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3615 gameInfo.event = StrSave(str);
3616 /* [HGM] we switched variant. Translate boards if needed. */
3617 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3621 if (looking_at(buf, &i, "Move ")) {
3622 /* Beginning of a move list */
3623 switch (ics_getting_history) {
3625 /* Normally should not happen */
3626 /* Maybe user hit reset while we were parsing */
3629 /* Happens if we are ignoring a move list that is not
3630 * the one we just requested. Common if the user
3631 * tries to observe two games without turning off
3634 case H_GETTING_MOVES:
3635 /* Should not happen */
3636 DisplayError(_("Error gathering move list: nested"), 0);
3637 ics_getting_history = H_FALSE;
3639 case H_GOT_REQ_HEADER:
3640 ics_getting_history = H_GETTING_MOVES;
3641 started = STARTED_MOVES;
3643 if (oldi > next_out) {
3644 SendToPlayer(&buf[next_out], oldi - next_out);
3647 case H_GOT_UNREQ_HEADER:
3648 ics_getting_history = H_GETTING_MOVES;
3649 started = STARTED_MOVES_NOHIDE;
3652 case H_GOT_UNWANTED_HEADER:
3653 ics_getting_history = H_FALSE;
3659 if (looking_at(buf, &i, "% ") ||
3660 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3661 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3662 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3663 soughtPending = FALSE;
3667 if(suppressKibitz) next_out = i;
3668 savingComment = FALSE;
3672 case STARTED_MOVES_NOHIDE:
3673 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3674 parse[parse_pos + i - oldi] = NULLCHAR;
3675 ParseGameHistory(parse);
3677 if (appData.zippyPlay && first.initDone) {
3678 FeedMovesToProgram(&first, forwardMostMove);
3679 if (gameMode == IcsPlayingWhite) {
3680 if (WhiteOnMove(forwardMostMove)) {
3681 if (first.sendTime) {
3682 if (first.useColors) {
3683 SendToProgram("black\n", &first);
3685 SendTimeRemaining(&first, TRUE);
3687 if (first.useColors) {
3688 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3690 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3691 first.maybeThinking = TRUE;
3693 if (first.usePlayother) {
3694 if (first.sendTime) {
3695 SendTimeRemaining(&first, TRUE);
3697 SendToProgram("playother\n", &first);
3703 } else if (gameMode == IcsPlayingBlack) {
3704 if (!WhiteOnMove(forwardMostMove)) {
3705 if (first.sendTime) {
3706 if (first.useColors) {
3707 SendToProgram("white\n", &first);
3709 SendTimeRemaining(&first, FALSE);
3711 if (first.useColors) {
3712 SendToProgram("black\n", &first);
3714 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3715 first.maybeThinking = TRUE;
3717 if (first.usePlayother) {
3718 if (first.sendTime) {
3719 SendTimeRemaining(&first, FALSE);
3721 SendToProgram("playother\n", &first);
3730 if (gameMode == IcsObserving && ics_gamenum == -1) {
3731 /* Moves came from oldmoves or moves command
3732 while we weren't doing anything else.
3734 currentMove = forwardMostMove;
3735 ClearHighlights();/*!!could figure this out*/
3736 flipView = appData.flipView;
3737 DrawPosition(TRUE, boards[currentMove]);
3738 DisplayBothClocks();
3739 snprintf(str, MSG_SIZ, "%s %s %s",
3740 gameInfo.white, _("vs."), gameInfo.black);
3744 /* Moves were history of an active game */
3745 if (gameInfo.resultDetails != NULL) {
3746 free(gameInfo.resultDetails);
3747 gameInfo.resultDetails = NULL;
3750 HistorySet(parseList, backwardMostMove,
3751 forwardMostMove, currentMove-1);
3752 DisplayMove(currentMove - 1);
3753 if (started == STARTED_MOVES) next_out = i;
3754 started = STARTED_NONE;
3755 ics_getting_history = H_FALSE;
3758 case STARTED_OBSERVE:
3759 started = STARTED_NONE;
3760 SendToICS(ics_prefix);
3761 SendToICS("refresh\n");
3767 if(bookHit) { // [HGM] book: simulate book reply
3768 static char bookMove[MSG_SIZ]; // a bit generous?
3770 programStats.nodes = programStats.depth = programStats.time =
3771 programStats.score = programStats.got_only_move = 0;
3772 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3774 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3775 strcat(bookMove, bookHit);
3776 HandleMachineMove(bookMove, &first);
3781 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3782 started == STARTED_HOLDINGS ||
3783 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3784 /* Accumulate characters in move list or board */
3785 parse[parse_pos++] = buf[i];
3788 /* Start of game messages. Mostly we detect start of game
3789 when the first board image arrives. On some versions
3790 of the ICS, though, we need to do a "refresh" after starting
3791 to observe in order to get the current board right away. */
3792 if (looking_at(buf, &i, "Adding game * to observation list")) {
3793 started = STARTED_OBSERVE;
3797 /* Handle auto-observe */
3798 if (appData.autoObserve &&
3799 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3800 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3802 /* Choose the player that was highlighted, if any. */
3803 if (star_match[0][0] == '\033' ||
3804 star_match[1][0] != '\033') {
3805 player = star_match[0];
3807 player = star_match[2];
3809 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3810 ics_prefix, StripHighlightAndTitle(player));
3813 /* Save ratings from notify string */
3814 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3815 player1Rating = string_to_rating(star_match[1]);
3816 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3817 player2Rating = string_to_rating(star_match[3]);
3819 if (appData.debugMode)
3821 "Ratings from 'Game notification:' %s %d, %s %d\n",
3822 player1Name, player1Rating,
3823 player2Name, player2Rating);
3828 /* Deal with automatic examine mode after a game,
3829 and with IcsObserving -> IcsExamining transition */
3830 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3831 looking_at(buf, &i, "has made you an examiner of game *")) {
3833 int gamenum = atoi(star_match[0]);
3834 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3835 gamenum == ics_gamenum) {
3836 /* We were already playing or observing this game;
3837 no need to refetch history */
3838 gameMode = IcsExamining;
3840 pauseExamForwardMostMove = forwardMostMove;
3841 } else if (currentMove < forwardMostMove) {
3842 ForwardInner(forwardMostMove);
3845 /* I don't think this case really can happen */
3846 SendToICS(ics_prefix);
3847 SendToICS("refresh\n");
3852 /* Error messages */
3853 // if (ics_user_moved) {
3854 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3855 if (looking_at(buf, &i, "Illegal move") ||
3856 looking_at(buf, &i, "Not a legal move") ||
3857 looking_at(buf, &i, "Your king is in check") ||
3858 looking_at(buf, &i, "It isn't your turn") ||
3859 looking_at(buf, &i, "It is not your move")) {
3861 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3862 currentMove = forwardMostMove-1;
3863 DisplayMove(currentMove - 1); /* before DMError */
3864 DrawPosition(FALSE, boards[currentMove]);
3865 SwitchClocks(forwardMostMove-1); // [HGM] race
3866 DisplayBothClocks();
3868 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3874 if (looking_at(buf, &i, "still have time") ||
3875 looking_at(buf, &i, "not out of time") ||
3876 looking_at(buf, &i, "either player is out of time") ||
3877 looking_at(buf, &i, "has timeseal; checking")) {
3878 /* We must have called his flag a little too soon */
3879 whiteFlag = blackFlag = FALSE;
3883 if (looking_at(buf, &i, "added * seconds to") ||
3884 looking_at(buf, &i, "seconds were added to")) {
3885 /* Update the clocks */
3886 SendToICS(ics_prefix);
3887 SendToICS("refresh\n");
3891 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3892 ics_clock_paused = TRUE;
3897 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3898 ics_clock_paused = FALSE;
3903 /* Grab player ratings from the Creating: message.
3904 Note we have to check for the special case when
3905 the ICS inserts things like [white] or [black]. */
3906 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3907 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3909 0 player 1 name (not necessarily white)
3911 2 empty, white, or black (IGNORED)
3912 3 player 2 name (not necessarily black)
3915 The names/ratings are sorted out when the game
3916 actually starts (below).
3918 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3919 player1Rating = string_to_rating(star_match[1]);
3920 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3921 player2Rating = string_to_rating(star_match[4]);
3923 if (appData.debugMode)
3925 "Ratings from 'Creating:' %s %d, %s %d\n",
3926 player1Name, player1Rating,
3927 player2Name, player2Rating);
3932 /* Improved generic start/end-of-game messages */
3933 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3934 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3935 /* If tkind == 0: */
3936 /* star_match[0] is the game number */
3937 /* [1] is the white player's name */
3938 /* [2] is the black player's name */
3939 /* For end-of-game: */
3940 /* [3] is the reason for the game end */
3941 /* [4] is a PGN end game-token, preceded by " " */
3942 /* For start-of-game: */
3943 /* [3] begins with "Creating" or "Continuing" */
3944 /* [4] is " *" or empty (don't care). */
3945 int gamenum = atoi(star_match[0]);
3946 char *whitename, *blackname, *why, *endtoken;
3947 ChessMove endtype = EndOfFile;
3950 whitename = star_match[1];
3951 blackname = star_match[2];
3952 why = star_match[3];
3953 endtoken = star_match[4];
3955 whitename = star_match[1];
3956 blackname = star_match[3];
3957 why = star_match[5];
3958 endtoken = star_match[6];
3961 /* Game start messages */
3962 if (strncmp(why, "Creating ", 9) == 0 ||
3963 strncmp(why, "Continuing ", 11) == 0) {
3964 gs_gamenum = gamenum;
3965 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3966 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3967 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3969 if (appData.zippyPlay) {
3970 ZippyGameStart(whitename, blackname);
3973 partnerBoardValid = FALSE; // [HGM] bughouse
3977 /* Game end messages */
3978 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3979 ics_gamenum != gamenum) {
3982 while (endtoken[0] == ' ') endtoken++;
3983 switch (endtoken[0]) {
3986 endtype = GameUnfinished;
3989 endtype = BlackWins;
3992 if (endtoken[1] == '/')
3993 endtype = GameIsDrawn;
3995 endtype = WhiteWins;
3998 GameEnds(endtype, why, GE_ICS);
4000 if (appData.zippyPlay && first.initDone) {
4001 ZippyGameEnd(endtype, why);
4002 if (first.pr == NoProc) {
4003 /* Start the next process early so that we'll
4004 be ready for the next challenge */
4005 StartChessProgram(&first);
4007 /* Send "new" early, in case this command takes
4008 a long time to finish, so that we'll be ready
4009 for the next challenge. */
4010 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4014 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4018 if (looking_at(buf, &i, "Removing game * from observation") ||
4019 looking_at(buf, &i, "no longer observing game *") ||
4020 looking_at(buf, &i, "Game * (*) has no examiners")) {
4021 if (gameMode == IcsObserving &&
4022 atoi(star_match[0]) == ics_gamenum)
4024 /* icsEngineAnalyze */
4025 if (appData.icsEngineAnalyze) {
4032 ics_user_moved = FALSE;
4037 if (looking_at(buf, &i, "no longer examining game *")) {
4038 if (gameMode == IcsExamining &&
4039 atoi(star_match[0]) == ics_gamenum)
4043 ics_user_moved = FALSE;
4048 /* Advance leftover_start past any newlines we find,
4049 so only partial lines can get reparsed */
4050 if (looking_at(buf, &i, "\n")) {
4051 prevColor = curColor;
4052 if (curColor != ColorNormal) {
4053 if (oldi > next_out) {
4054 SendToPlayer(&buf[next_out], oldi - next_out);
4057 Colorize(ColorNormal, FALSE);
4058 curColor = ColorNormal;
4060 if (started == STARTED_BOARD) {
4061 started = STARTED_NONE;
4062 parse[parse_pos] = NULLCHAR;
4063 ParseBoard12(parse);
4066 /* Send premove here */
4067 if (appData.premove) {
4069 if (currentMove == 0 &&
4070 gameMode == IcsPlayingWhite &&
4071 appData.premoveWhite) {
4072 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4073 if (appData.debugMode)
4074 fprintf(debugFP, "Sending premove:\n");
4076 } else if (currentMove == 1 &&
4077 gameMode == IcsPlayingBlack &&
4078 appData.premoveBlack) {
4079 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4080 if (appData.debugMode)
4081 fprintf(debugFP, "Sending premove:\n");
4083 } else if (gotPremove) {
4085 ClearPremoveHighlights();
4086 if (appData.debugMode)
4087 fprintf(debugFP, "Sending premove:\n");
4088 UserMoveEvent(premoveFromX, premoveFromY,
4089 premoveToX, premoveToY,
4094 /* Usually suppress following prompt */
4095 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4096 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4097 if (looking_at(buf, &i, "*% ")) {
4098 savingComment = FALSE;
4103 } else if (started == STARTED_HOLDINGS) {
4105 char new_piece[MSG_SIZ];
4106 started = STARTED_NONE;
4107 parse[parse_pos] = NULLCHAR;
4108 if (appData.debugMode)
4109 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4110 parse, currentMove);
4111 if (sscanf(parse, " game %d", &gamenum) == 1) {
4112 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4113 if (gameInfo.variant == VariantNormal) {
4114 /* [HGM] We seem to switch variant during a game!
4115 * Presumably no holdings were displayed, so we have
4116 * to move the position two files to the right to
4117 * create room for them!
4119 VariantClass newVariant;
4120 switch(gameInfo.boardWidth) { // base guess on board width
4121 case 9: newVariant = VariantShogi; break;
4122 case 10: newVariant = VariantGreat; break;
4123 default: newVariant = VariantCrazyhouse; break;
4125 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4126 /* Get a move list just to see the header, which
4127 will tell us whether this is really bug or zh */
4128 if (ics_getting_history == H_FALSE) {
4129 ics_getting_history = H_REQUESTED;
4130 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4134 new_piece[0] = NULLCHAR;
4135 sscanf(parse, "game %d white [%s black [%s <- %s",
4136 &gamenum, white_holding, black_holding,
4138 white_holding[strlen(white_holding)-1] = NULLCHAR;
4139 black_holding[strlen(black_holding)-1] = NULLCHAR;
4140 /* [HGM] copy holdings to board holdings area */
4141 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4142 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4143 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4145 if (appData.zippyPlay && first.initDone) {
4146 ZippyHoldings(white_holding, black_holding,
4150 if (tinyLayout || smallLayout) {
4151 char wh[16], bh[16];
4152 PackHolding(wh, white_holding);
4153 PackHolding(bh, black_holding);
4154 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4155 gameInfo.white, gameInfo.black);
4157 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4158 gameInfo.white, white_holding, _("vs."),
4159 gameInfo.black, black_holding);
4161 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4162 DrawPosition(FALSE, boards[currentMove]);
4164 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4165 sscanf(parse, "game %d white [%s black [%s <- %s",
4166 &gamenum, white_holding, black_holding,
4168 white_holding[strlen(white_holding)-1] = NULLCHAR;
4169 black_holding[strlen(black_holding)-1] = NULLCHAR;
4170 /* [HGM] copy holdings to partner-board holdings area */
4171 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4172 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4173 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4174 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4175 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4178 /* Suppress following prompt */
4179 if (looking_at(buf, &i, "*% ")) {
4180 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4181 savingComment = FALSE;
4189 i++; /* skip unparsed character and loop back */
4192 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4193 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4194 // SendToPlayer(&buf[next_out], i - next_out);
4195 started != STARTED_HOLDINGS && leftover_start > next_out) {
4196 SendToPlayer(&buf[next_out], leftover_start - next_out);
4200 leftover_len = buf_len - leftover_start;
4201 /* if buffer ends with something we couldn't parse,
4202 reparse it after appending the next read */
4204 } else if (count == 0) {
4205 RemoveInputSource(isr);
4206 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4208 DisplayFatalError(_("Error reading from ICS"), error, 1);
4213 /* Board style 12 looks like this:
4215 <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
4217 * The "<12> " is stripped before it gets to this routine. The two
4218 * trailing 0's (flip state and clock ticking) are later addition, and
4219 * some chess servers may not have them, or may have only the first.
4220 * Additional trailing fields may be added in the future.
4223 #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"
4225 #define RELATION_OBSERVING_PLAYED 0
4226 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4227 #define RELATION_PLAYING_MYMOVE 1
4228 #define RELATION_PLAYING_NOTMYMOVE -1
4229 #define RELATION_EXAMINING 2
4230 #define RELATION_ISOLATED_BOARD -3
4231 #define RELATION_STARTING_POSITION -4 /* FICS only */
4234 ParseBoard12 (char *string)
4238 char *bookHit = NULL; // [HGM] book
4240 GameMode newGameMode;
4241 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4242 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4243 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4244 char to_play, board_chars[200];
4245 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4246 char black[32], white[32];
4248 int prevMove = currentMove;
4251 int fromX, fromY, toX, toY;
4253 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4254 Boolean weird = FALSE, reqFlag = FALSE;
4256 fromX = fromY = toX = toY = -1;
4260 if (appData.debugMode)
4261 fprintf(debugFP, "Parsing board: %s\n", string);
4263 move_str[0] = NULLCHAR;
4264 elapsed_time[0] = NULLCHAR;
4265 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4267 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4268 if(string[i] == ' ') { ranks++; files = 0; }
4270 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4273 for(j = 0; j <i; j++) board_chars[j] = string[j];
4274 board_chars[i] = '\0';
4277 n = sscanf(string, PATTERN, &to_play, &double_push,
4278 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4279 &gamenum, white, black, &relation, &basetime, &increment,
4280 &white_stren, &black_stren, &white_time, &black_time,
4281 &moveNum, str, elapsed_time, move_str, &ics_flip,
4285 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4286 DisplayError(str, 0);
4290 /* Convert the move number to internal form */
4291 moveNum = (moveNum - 1) * 2;
4292 if (to_play == 'B') moveNum++;
4293 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4294 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4300 case RELATION_OBSERVING_PLAYED:
4301 case RELATION_OBSERVING_STATIC:
4302 if (gamenum == -1) {
4303 /* Old ICC buglet */
4304 relation = RELATION_OBSERVING_STATIC;
4306 newGameMode = IcsObserving;
4308 case RELATION_PLAYING_MYMOVE:
4309 case RELATION_PLAYING_NOTMYMOVE:
4311 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4312 IcsPlayingWhite : IcsPlayingBlack;
4313 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4315 case RELATION_EXAMINING:
4316 newGameMode = IcsExamining;
4318 case RELATION_ISOLATED_BOARD:
4320 /* Just display this board. If user was doing something else,
4321 we will forget about it until the next board comes. */
4322 newGameMode = IcsIdle;
4324 case RELATION_STARTING_POSITION:
4325 newGameMode = gameMode;
4329 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4330 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4331 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4332 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4333 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4334 static int lastBgGame = -1;
4336 for (k = 0; k < ranks; k++) {
4337 for (j = 0; j < files; j++)
4338 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4339 if(gameInfo.holdingsWidth > 1) {
4340 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4341 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4344 CopyBoard(partnerBoard, board);
4345 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4346 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4347 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4348 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4349 if(toSqr = strchr(str, '-')) {
4350 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4351 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4352 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4353 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4354 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4355 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4357 DisplayWhiteClock(white_time*fac, to_play == 'W');
4358 DisplayBlackClock(black_time*fac, to_play != 'W');
4359 activePartner = to_play;
4360 if(gamenum != lastBgGame) {
4362 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4365 lastBgGame = gamenum;
4366 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4367 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4368 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4369 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4370 if(!twoBoards) DisplayMessage(partnerStatus, "");
4371 partnerBoardValid = TRUE;
4375 if(appData.dualBoard && appData.bgObserve) {
4376 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4377 SendToICS(ics_prefix), SendToICS("pobserve\n");
4378 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4380 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4385 /* Modify behavior for initial board display on move listing
4388 switch (ics_getting_history) {
4392 case H_GOT_REQ_HEADER:
4393 case H_GOT_UNREQ_HEADER:
4394 /* This is the initial position of the current game */
4395 gamenum = ics_gamenum;
4396 moveNum = 0; /* old ICS bug workaround */
4397 if (to_play == 'B') {
4398 startedFromSetupPosition = TRUE;
4399 blackPlaysFirst = TRUE;
4401 if (forwardMostMove == 0) forwardMostMove = 1;
4402 if (backwardMostMove == 0) backwardMostMove = 1;
4403 if (currentMove == 0) currentMove = 1;
4405 newGameMode = gameMode;
4406 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4408 case H_GOT_UNWANTED_HEADER:
4409 /* This is an initial board that we don't want */
4411 case H_GETTING_MOVES:
4412 /* Should not happen */
4413 DisplayError(_("Error gathering move list: extra board"), 0);
4414 ics_getting_history = H_FALSE;
4418 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4419 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4420 weird && (int)gameInfo.variant < (int)VariantShogi) {
4421 /* [HGM] We seem to have switched variant unexpectedly
4422 * Try to guess new variant from board size
4424 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4425 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4426 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4427 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4428 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4429 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4430 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4431 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4432 /* Get a move list just to see the header, which
4433 will tell us whether this is really bug or zh */
4434 if (ics_getting_history == H_FALSE) {
4435 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4436 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4441 /* Take action if this is the first board of a new game, or of a
4442 different game than is currently being displayed. */
4443 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4444 relation == RELATION_ISOLATED_BOARD) {
4446 /* Forget the old game and get the history (if any) of the new one */
4447 if (gameMode != BeginningOfGame) {
4451 if (appData.autoRaiseBoard) BoardToTop();
4453 if (gamenum == -1) {
4454 newGameMode = IcsIdle;
4455 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4456 appData.getMoveList && !reqFlag) {
4457 /* Need to get game history */
4458 ics_getting_history = H_REQUESTED;
4459 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4463 /* Initially flip the board to have black on the bottom if playing
4464 black or if the ICS flip flag is set, but let the user change
4465 it with the Flip View button. */
4466 flipView = appData.autoFlipView ?
4467 (newGameMode == IcsPlayingBlack) || ics_flip :
4470 /* Done with values from previous mode; copy in new ones */
4471 gameMode = newGameMode;
4473 ics_gamenum = gamenum;
4474 if (gamenum == gs_gamenum) {
4475 int klen = strlen(gs_kind);
4476 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4477 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4478 gameInfo.event = StrSave(str);
4480 gameInfo.event = StrSave("ICS game");
4482 gameInfo.site = StrSave(appData.icsHost);
4483 gameInfo.date = PGNDate();
4484 gameInfo.round = StrSave("-");
4485 gameInfo.white = StrSave(white);
4486 gameInfo.black = StrSave(black);
4487 timeControl = basetime * 60 * 1000;
4489 timeIncrement = increment * 1000;
4490 movesPerSession = 0;
4491 gameInfo.timeControl = TimeControlTagValue();
4492 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4493 if (appData.debugMode) {
4494 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4495 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4496 setbuf(debugFP, NULL);
4499 gameInfo.outOfBook = NULL;
4501 /* Do we have the ratings? */
4502 if (strcmp(player1Name, white) == 0 &&
4503 strcmp(player2Name, black) == 0) {
4504 if (appData.debugMode)
4505 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4506 player1Rating, player2Rating);
4507 gameInfo.whiteRating = player1Rating;
4508 gameInfo.blackRating = player2Rating;
4509 } else if (strcmp(player2Name, white) == 0 &&
4510 strcmp(player1Name, black) == 0) {
4511 if (appData.debugMode)
4512 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4513 player2Rating, player1Rating);
4514 gameInfo.whiteRating = player2Rating;
4515 gameInfo.blackRating = player1Rating;
4517 player1Name[0] = player2Name[0] = NULLCHAR;
4519 /* Silence shouts if requested */
4520 if (appData.quietPlay &&
4521 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4522 SendToICS(ics_prefix);
4523 SendToICS("set shout 0\n");
4527 /* Deal with midgame name changes */
4529 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4530 if (gameInfo.white) free(gameInfo.white);
4531 gameInfo.white = StrSave(white);
4533 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4534 if (gameInfo.black) free(gameInfo.black);
4535 gameInfo.black = StrSave(black);
4539 /* Throw away game result if anything actually changes in examine mode */
4540 if (gameMode == IcsExamining && !newGame) {
4541 gameInfo.result = GameUnfinished;
4542 if (gameInfo.resultDetails != NULL) {
4543 free(gameInfo.resultDetails);
4544 gameInfo.resultDetails = NULL;
4548 /* In pausing && IcsExamining mode, we ignore boards coming
4549 in if they are in a different variation than we are. */
4550 if (pauseExamInvalid) return;
4551 if (pausing && gameMode == IcsExamining) {
4552 if (moveNum <= pauseExamForwardMostMove) {
4553 pauseExamInvalid = TRUE;
4554 forwardMostMove = pauseExamForwardMostMove;
4559 if (appData.debugMode) {
4560 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4562 /* Parse the board */
4563 for (k = 0; k < ranks; k++) {
4564 for (j = 0; j < files; j++)
4565 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4566 if(gameInfo.holdingsWidth > 1) {
4567 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4568 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4571 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4572 board[5][BOARD_RGHT+1] = WhiteAngel;
4573 board[6][BOARD_RGHT+1] = WhiteMarshall;
4574 board[1][0] = BlackMarshall;
4575 board[2][0] = BlackAngel;
4576 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4578 CopyBoard(boards[moveNum], board);
4579 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4581 startedFromSetupPosition =
4582 !CompareBoards(board, initialPosition);
4583 if(startedFromSetupPosition)
4584 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4587 /* [HGM] Set castling rights. Take the outermost Rooks,
4588 to make it also work for FRC opening positions. Note that board12
4589 is really defective for later FRC positions, as it has no way to
4590 indicate which Rook can castle if they are on the same side of King.
4591 For the initial position we grant rights to the outermost Rooks,
4592 and remember thos rights, and we then copy them on positions
4593 later in an FRC game. This means WB might not recognize castlings with
4594 Rooks that have moved back to their original position as illegal,
4595 but in ICS mode that is not its job anyway.
4597 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4598 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4600 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4601 if(board[0][i] == WhiteRook) j = i;
4602 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4603 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4604 if(board[0][i] == WhiteRook) j = i;
4605 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4606 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4607 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4608 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4609 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4610 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4611 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4613 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4614 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4615 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4616 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4617 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4618 if(board[BOARD_HEIGHT-1][k] == bKing)
4619 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4620 if(gameInfo.variant == VariantTwoKings) {
4621 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4622 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4623 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4626 r = boards[moveNum][CASTLING][0] = initialRights[0];
4627 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4628 r = boards[moveNum][CASTLING][1] = initialRights[1];
4629 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4630 r = boards[moveNum][CASTLING][3] = initialRights[3];
4631 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4632 r = boards[moveNum][CASTLING][4] = initialRights[4];
4633 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4634 /* wildcastle kludge: always assume King has rights */
4635 r = boards[moveNum][CASTLING][2] = initialRights[2];
4636 r = boards[moveNum][CASTLING][5] = initialRights[5];
4638 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4639 boards[moveNum][EP_STATUS] = EP_NONE;
4640 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4641 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4642 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4645 if (ics_getting_history == H_GOT_REQ_HEADER ||
4646 ics_getting_history == H_GOT_UNREQ_HEADER) {
4647 /* This was an initial position from a move list, not
4648 the current position */
4652 /* Update currentMove and known move number limits */
4653 newMove = newGame || moveNum > forwardMostMove;
4656 forwardMostMove = backwardMostMove = currentMove = moveNum;
4657 if (gameMode == IcsExamining && moveNum == 0) {
4658 /* Workaround for ICS limitation: we are not told the wild
4659 type when starting to examine a game. But if we ask for
4660 the move list, the move list header will tell us */
4661 ics_getting_history = H_REQUESTED;
4662 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4665 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4666 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4668 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4669 /* [HGM] applied this also to an engine that is silently watching */
4670 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4671 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4672 gameInfo.variant == currentlyInitializedVariant) {
4673 takeback = forwardMostMove - moveNum;
4674 for (i = 0; i < takeback; i++) {
4675 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4676 SendToProgram("undo\n", &first);
4681 forwardMostMove = moveNum;
4682 if (!pausing || currentMove > forwardMostMove)
4683 currentMove = forwardMostMove;
4685 /* New part of history that is not contiguous with old part */
4686 if (pausing && gameMode == IcsExamining) {
4687 pauseExamInvalid = TRUE;
4688 forwardMostMove = pauseExamForwardMostMove;
4691 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4693 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4694 // [HGM] when we will receive the move list we now request, it will be
4695 // fed to the engine from the first move on. So if the engine is not
4696 // in the initial position now, bring it there.
4697 InitChessProgram(&first, 0);
4700 ics_getting_history = H_REQUESTED;
4701 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4704 forwardMostMove = backwardMostMove = currentMove = moveNum;
4707 /* Update the clocks */
4708 if (strchr(elapsed_time, '.')) {
4710 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4711 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4713 /* Time is in seconds */
4714 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4715 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4720 if (appData.zippyPlay && newGame &&
4721 gameMode != IcsObserving && gameMode != IcsIdle &&
4722 gameMode != IcsExamining)
4723 ZippyFirstBoard(moveNum, basetime, increment);
4726 /* Put the move on the move list, first converting
4727 to canonical algebraic form. */
4729 if (appData.debugMode) {
4730 int f = forwardMostMove;
4731 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4732 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4733 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4734 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4735 fprintf(debugFP, "moveNum = %d\n", moveNum);
4736 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4737 setbuf(debugFP, NULL);
4739 if (moveNum <= backwardMostMove) {
4740 /* We don't know what the board looked like before
4742 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4743 strcat(parseList[moveNum - 1], " ");
4744 strcat(parseList[moveNum - 1], elapsed_time);
4745 moveList[moveNum - 1][0] = NULLCHAR;
4746 } else if (strcmp(move_str, "none") == 0) {
4747 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4748 /* Again, we don't know what the board looked like;
4749 this is really the start of the game. */
4750 parseList[moveNum - 1][0] = NULLCHAR;
4751 moveList[moveNum - 1][0] = NULLCHAR;
4752 backwardMostMove = moveNum;
4753 startedFromSetupPosition = TRUE;
4754 fromX = fromY = toX = toY = -1;
4756 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4757 // So we parse the long-algebraic move string in stead of the SAN move
4758 int valid; char buf[MSG_SIZ], *prom;
4760 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4761 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4762 // str looks something like "Q/a1-a2"; kill the slash
4764 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4765 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4766 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4767 strcat(buf, prom); // long move lacks promo specification!
4768 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4769 if(appData.debugMode)
4770 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4771 safeStrCpy(move_str, buf, MSG_SIZ);
4773 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4774 &fromX, &fromY, &toX, &toY, &promoChar)
4775 || ParseOneMove(buf, moveNum - 1, &moveType,
4776 &fromX, &fromY, &toX, &toY, &promoChar);
4777 // end of long SAN patch
4779 (void) CoordsToAlgebraic(boards[moveNum - 1],
4780 PosFlags(moveNum - 1),
4781 fromY, fromX, toY, toX, promoChar,
4782 parseList[moveNum-1]);
4783 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4789 if(gameInfo.variant != VariantShogi)
4790 strcat(parseList[moveNum - 1], "+");
4793 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4794 strcat(parseList[moveNum - 1], "#");
4797 strcat(parseList[moveNum - 1], " ");
4798 strcat(parseList[moveNum - 1], elapsed_time);
4799 /* currentMoveString is set as a side-effect of ParseOneMove */
4800 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4801 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4802 strcat(moveList[moveNum - 1], "\n");
4804 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4805 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4806 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4807 ChessSquare old, new = boards[moveNum][k][j];
4808 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4809 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4810 if(old == new) continue;
4811 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4812 else if(new == WhiteWazir || new == BlackWazir) {
4813 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4814 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4815 else boards[moveNum][k][j] = old; // preserve type of Gold
4816 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4817 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4820 /* Move from ICS was illegal!? Punt. */
4821 if (appData.debugMode) {
4822 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4823 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4825 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4826 strcat(parseList[moveNum - 1], " ");
4827 strcat(parseList[moveNum - 1], elapsed_time);
4828 moveList[moveNum - 1][0] = NULLCHAR;
4829 fromX = fromY = toX = toY = -1;
4832 if (appData.debugMode) {
4833 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4834 setbuf(debugFP, NULL);
4838 /* Send move to chess program (BEFORE animating it). */
4839 if (appData.zippyPlay && !newGame && newMove &&
4840 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4842 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4843 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4844 if (moveList[moveNum - 1][0] == NULLCHAR) {
4845 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4847 DisplayError(str, 0);
4849 if (first.sendTime) {
4850 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4852 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4853 if (firstMove && !bookHit) {
4855 if (first.useColors) {
4856 SendToProgram(gameMode == IcsPlayingWhite ?
4858 "black\ngo\n", &first);
4860 SendToProgram("go\n", &first);
4862 first.maybeThinking = TRUE;
4865 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4866 if (moveList[moveNum - 1][0] == NULLCHAR) {
4867 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4868 DisplayError(str, 0);
4870 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4871 SendMoveToProgram(moveNum - 1, &first);
4878 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4879 /* If move comes from a remote source, animate it. If it
4880 isn't remote, it will have already been animated. */
4881 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4882 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4884 if (!pausing && appData.highlightLastMove) {
4885 SetHighlights(fromX, fromY, toX, toY);
4889 /* Start the clocks */
4890 whiteFlag = blackFlag = FALSE;
4891 appData.clockMode = !(basetime == 0 && increment == 0);
4893 ics_clock_paused = TRUE;
4895 } else if (ticking == 1) {
4896 ics_clock_paused = FALSE;
4898 if (gameMode == IcsIdle ||
4899 relation == RELATION_OBSERVING_STATIC ||
4900 relation == RELATION_EXAMINING ||
4902 DisplayBothClocks();
4906 /* Display opponents and material strengths */
4907 if (gameInfo.variant != VariantBughouse &&
4908 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4909 if (tinyLayout || smallLayout) {
4910 if(gameInfo.variant == VariantNormal)
4911 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4912 gameInfo.white, white_stren, gameInfo.black, black_stren,
4913 basetime, increment);
4915 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4916 gameInfo.white, white_stren, gameInfo.black, black_stren,
4917 basetime, increment, (int) gameInfo.variant);
4919 if(gameInfo.variant == VariantNormal)
4920 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4921 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4922 basetime, increment);
4924 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4925 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4926 basetime, increment, VariantName(gameInfo.variant));
4929 if (appData.debugMode) {
4930 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4935 /* Display the board */
4936 if (!pausing && !appData.noGUI) {
4938 if (appData.premove)
4940 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4941 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4942 ClearPremoveHighlights();
4944 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4945 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4946 DrawPosition(j, boards[currentMove]);
4948 DisplayMove(moveNum - 1);
4949 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4950 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4951 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4952 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4956 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4958 if(bookHit) { // [HGM] book: simulate book reply
4959 static char bookMove[MSG_SIZ]; // a bit generous?
4961 programStats.nodes = programStats.depth = programStats.time =
4962 programStats.score = programStats.got_only_move = 0;
4963 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4965 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4966 strcat(bookMove, bookHit);
4967 HandleMachineMove(bookMove, &first);
4976 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4977 ics_getting_history = H_REQUESTED;
4978 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4984 SendToBoth (char *msg)
4985 { // to make it easy to keep two engines in step in dual analysis
4986 SendToProgram(msg, &first);
4987 if(second.analyzing) SendToProgram(msg, &second);
4991 AnalysisPeriodicEvent (int force)
4993 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4994 && !force) || !appData.periodicUpdates)
4997 /* Send . command to Crafty to collect stats */
5000 /* Don't send another until we get a response (this makes
5001 us stop sending to old Crafty's which don't understand
5002 the "." command (sending illegal cmds resets node count & time,
5003 which looks bad)) */
5004 programStats.ok_to_send = 0;
5008 ics_update_width (int new_width)
5010 ics_printf("set width %d\n", new_width);
5014 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5018 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5019 if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChu) {
5020 sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5021 SendToProgram(buf, cps);
5024 // null move in variant where engine does not understand it (for analysis purposes)
5025 SendBoard(cps, moveNum + 1); // send position after move in stead.
5028 if (cps->useUsermove) {
5029 SendToProgram("usermove ", cps);
5033 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5034 int len = space - parseList[moveNum];
5035 memcpy(buf, parseList[moveNum], len);
5037 buf[len] = NULLCHAR;
5039 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5041 SendToProgram(buf, cps);
5043 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5044 AlphaRank(moveList[moveNum], 4);
5045 SendToProgram(moveList[moveNum], cps);
5046 AlphaRank(moveList[moveNum], 4); // and back
5048 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5049 * the engine. It would be nice to have a better way to identify castle
5051 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5052 && cps->useOOCastle) {
5053 int fromX = moveList[moveNum][0] - AAA;
5054 int fromY = moveList[moveNum][1] - ONE;
5055 int toX = moveList[moveNum][2] - AAA;
5056 int toY = moveList[moveNum][3] - ONE;
5057 if((boards[moveNum][fromY][fromX] == WhiteKing
5058 && boards[moveNum][toY][toX] == WhiteRook)
5059 || (boards[moveNum][fromY][fromX] == BlackKing
5060 && boards[moveNum][toY][toX] == BlackRook)) {
5061 if(toX > fromX) SendToProgram("O-O\n", cps);
5062 else SendToProgram("O-O-O\n", cps);
5064 else SendToProgram(moveList[moveNum], cps);
5066 if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5067 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5068 moveList[moveNum][5], moveList[moveNum][6] - '0',
5069 moveList[moveNum][5], moveList[moveNum][6] - '0',
5070 moveList[moveNum][2], moveList[moveNum][3] - '0');
5071 SendToProgram(buf, cps);
5073 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5074 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5075 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5076 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5077 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5079 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5080 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5081 SendToProgram(buf, cps);
5083 else SendToProgram(moveList[moveNum], cps);
5084 /* End of additions by Tord */
5087 /* [HGM] setting up the opening has brought engine in force mode! */
5088 /* Send 'go' if we are in a mode where machine should play. */
5089 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5090 (gameMode == TwoMachinesPlay ||
5092 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5094 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5095 SendToProgram("go\n", cps);
5096 if (appData.debugMode) {
5097 fprintf(debugFP, "(extra)\n");
5100 setboardSpoiledMachineBlack = 0;
5104 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5106 char user_move[MSG_SIZ];
5109 if(gameInfo.variant == VariantSChess && promoChar) {
5110 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5111 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5112 } else suffix[0] = NULLCHAR;
5116 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5117 (int)moveType, fromX, fromY, toX, toY);
5118 DisplayError(user_move + strlen("say "), 0);
5120 case WhiteKingSideCastle:
5121 case BlackKingSideCastle:
5122 case WhiteQueenSideCastleWild:
5123 case BlackQueenSideCastleWild:
5125 case WhiteHSideCastleFR:
5126 case BlackHSideCastleFR:
5128 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5130 case WhiteQueenSideCastle:
5131 case BlackQueenSideCastle:
5132 case WhiteKingSideCastleWild:
5133 case BlackKingSideCastleWild:
5135 case WhiteASideCastleFR:
5136 case BlackASideCastleFR:
5138 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5140 case WhiteNonPromotion:
5141 case BlackNonPromotion:
5142 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5144 case WhitePromotion:
5145 case BlackPromotion:
5146 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5147 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5148 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5149 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5150 PieceToChar(WhiteFerz));
5151 else if(gameInfo.variant == VariantGreat)
5152 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5153 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5154 PieceToChar(WhiteMan));
5156 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5157 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5163 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5164 ToUpper(PieceToChar((ChessSquare) fromX)),
5165 AAA + toX, ONE + toY);
5167 case IllegalMove: /* could be a variant we don't quite understand */
5168 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5170 case WhiteCapturesEnPassant:
5171 case BlackCapturesEnPassant:
5172 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5173 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5176 SendToICS(user_move);
5177 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5178 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5183 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5184 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5185 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5186 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5187 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5190 if(gameMode != IcsExamining) { // is this ever not the case?
5191 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5193 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5194 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5195 } else { // on FICS we must first go to general examine mode
5196 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5198 if(gameInfo.variant != VariantNormal) {
5199 // try figure out wild number, as xboard names are not always valid on ICS
5200 for(i=1; i<=36; i++) {
5201 snprintf(buf, MSG_SIZ, "wild/%d", i);
5202 if(StringToVariant(buf) == gameInfo.variant) break;
5204 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5205 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5206 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5207 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5208 SendToICS(ics_prefix);
5210 if(startedFromSetupPosition || backwardMostMove != 0) {
5211 fen = PositionToFEN(backwardMostMove, NULL, 1);
5212 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5213 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5215 } else { // FICS: everything has to set by separate bsetup commands
5216 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5217 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5219 if(!WhiteOnMove(backwardMostMove)) {
5220 SendToICS("bsetup tomove black\n");
5222 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5223 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5225 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5226 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5228 i = boards[backwardMostMove][EP_STATUS];
5229 if(i >= 0) { // set e.p.
5230 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5236 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5237 SendToICS("bsetup done\n"); // switch to normal examining.
5239 for(i = backwardMostMove; i<last; i++) {
5241 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5242 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5243 int len = strlen(moveList[i]);
5244 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5245 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5249 SendToICS(ics_prefix);
5250 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5253 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5256 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5258 if (rf == DROP_RANK) {
5259 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5260 sprintf(move, "%c@%c%c\n",
5261 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5263 if (promoChar == 'x' || promoChar == NULLCHAR) {
5264 sprintf(move, "%c%c%c%c\n",
5265 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5266 if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5268 sprintf(move, "%c%c%c%c%c\n",
5269 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5275 ProcessICSInitScript (FILE *f)
5279 while (fgets(buf, MSG_SIZ, f)) {
5280 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5287 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5289 static ClickType lastClickType;
5294 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5295 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5296 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5297 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5298 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5299 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5302 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5303 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5304 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5305 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5306 if(!step) step = -1;
5307 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5308 appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion) ||
5309 IS_SHOGI(gameInfo.variant) && promoSweep != CHUPROMOTED last && last != CHUPROMOTED promoSweep && last != promoSweep);
5311 int victim = boards[currentMove][toY][toX];
5312 boards[currentMove][toY][toX] = promoSweep;
5313 DrawPosition(FALSE, boards[currentMove]);
5314 boards[currentMove][toY][toX] = victim;
5316 ChangeDragPiece(promoSweep);
5320 PromoScroll (int x, int y)
5324 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5325 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5326 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5327 if(!step) return FALSE;
5328 lastX = x; lastY = y;
5329 if((promoSweep < BlackPawn) == flipView) step = -step;
5330 if(step > 0) selectFlag = 1;
5331 if(!selectFlag) Sweep(step);
5336 NextPiece (int step)
5338 ChessSquare piece = boards[currentMove][toY][toX];
5341 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5342 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5343 if(!step) step = -1;
5344 } while(PieceToChar(pieceSweep) == '.');
5345 boards[currentMove][toY][toX] = pieceSweep;
5346 DrawPosition(FALSE, boards[currentMove]);
5347 boards[currentMove][toY][toX] = piece;
5349 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5351 AlphaRank (char *move, int n)
5353 // char *p = move, c; int x, y;
5355 if (appData.debugMode) {
5356 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5360 move[2]>='0' && move[2]<='9' &&
5361 move[3]>='a' && move[3]<='x' ) {
5363 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5364 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5366 if(move[0]>='0' && move[0]<='9' &&
5367 move[1]>='a' && move[1]<='x' &&
5368 move[2]>='0' && move[2]<='9' &&
5369 move[3]>='a' && move[3]<='x' ) {
5370 /* input move, Shogi -> normal */
5371 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5372 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5373 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5374 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5377 move[3]>='0' && move[3]<='9' &&
5378 move[2]>='a' && move[2]<='x' ) {
5380 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5381 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5384 move[0]>='a' && move[0]<='x' &&
5385 move[3]>='0' && move[3]<='9' &&
5386 move[2]>='a' && move[2]<='x' ) {
5387 /* output move, normal -> Shogi */
5388 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5389 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5390 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5391 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5392 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5394 if (appData.debugMode) {
5395 fprintf(debugFP, " out = '%s'\n", move);
5399 char yy_textstr[8000];
5401 /* Parser for moves from gnuchess, ICS, or user typein box */
5403 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5405 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5407 switch (*moveType) {
5408 case WhitePromotion:
5409 case BlackPromotion:
5410 case WhiteNonPromotion:
5411 case BlackNonPromotion:
5414 case WhiteCapturesEnPassant:
5415 case BlackCapturesEnPassant:
5416 case WhiteKingSideCastle:
5417 case WhiteQueenSideCastle:
5418 case BlackKingSideCastle:
5419 case BlackQueenSideCastle:
5420 case WhiteKingSideCastleWild:
5421 case WhiteQueenSideCastleWild:
5422 case BlackKingSideCastleWild:
5423 case BlackQueenSideCastleWild:
5424 /* Code added by Tord: */
5425 case WhiteHSideCastleFR:
5426 case WhiteASideCastleFR:
5427 case BlackHSideCastleFR:
5428 case BlackASideCastleFR:
5429 /* End of code added by Tord */
5430 case IllegalMove: /* bug or odd chess variant */
5431 *fromX = currentMoveString[0] - AAA;
5432 *fromY = currentMoveString[1] - ONE;
5433 *toX = currentMoveString[2] - AAA;
5434 *toY = currentMoveString[3] - ONE;
5435 *promoChar = currentMoveString[4];
5436 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5437 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5438 if (appData.debugMode) {
5439 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5441 *fromX = *fromY = *toX = *toY = 0;
5444 if (appData.testLegality) {
5445 return (*moveType != IllegalMove);
5447 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5448 // [HGM] lion: if this is a double move we are less critical
5449 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5454 *fromX = *moveType == WhiteDrop ?
5455 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5456 (int) CharToPiece(ToLower(currentMoveString[0]));
5458 *toX = currentMoveString[2] - AAA;
5459 *toY = currentMoveString[3] - ONE;
5460 *promoChar = NULLCHAR;
5464 case ImpossibleMove:
5474 if (appData.debugMode) {
5475 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5478 *fromX = *fromY = *toX = *toY = 0;
5479 *promoChar = NULLCHAR;
5484 Boolean pushed = FALSE;
5485 char *lastParseAttempt;
5488 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5489 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5490 int fromX, fromY, toX, toY; char promoChar;
5495 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5496 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5497 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5500 endPV = forwardMostMove;
5502 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5503 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5504 lastParseAttempt = pv;
5505 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5506 if(!valid && nr == 0 &&
5507 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5508 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5509 // Hande case where played move is different from leading PV move
5510 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5511 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5512 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5513 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5514 endPV += 2; // if position different, keep this
5515 moveList[endPV-1][0] = fromX + AAA;
5516 moveList[endPV-1][1] = fromY + ONE;
5517 moveList[endPV-1][2] = toX + AAA;
5518 moveList[endPV-1][3] = toY + ONE;
5519 parseList[endPV-1][0] = NULLCHAR;
5520 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5523 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5524 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5525 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5526 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5527 valid++; // allow comments in PV
5531 if(endPV+1 > framePtr) break; // no space, truncate
5534 CopyBoard(boards[endPV], boards[endPV-1]);
5535 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5536 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5537 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5538 CoordsToAlgebraic(boards[endPV - 1],
5539 PosFlags(endPV - 1),
5540 fromY, fromX, toY, toX, promoChar,
5541 parseList[endPV - 1]);
5543 if(atEnd == 2) return; // used hidden, for PV conversion
5544 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5545 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5546 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5547 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5548 DrawPosition(TRUE, boards[currentMove]);
5552 MultiPV (ChessProgramState *cps)
5553 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5555 for(i=0; i<cps->nrOptions; i++)
5556 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5561 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5564 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5566 int startPV, multi, lineStart, origIndex = index;
5567 char *p, buf2[MSG_SIZ];
5568 ChessProgramState *cps = (pane ? &second : &first);
5570 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5571 lastX = x; lastY = y;
5572 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5573 lineStart = startPV = index;
5574 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5575 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5577 do{ while(buf[index] && buf[index] != '\n') index++;
5578 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5580 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5581 int n = cps->option[multi].value;
5582 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5583 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5584 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5585 cps->option[multi].value = n;
5588 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5589 ExcludeClick(origIndex - lineStart);
5592 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5593 *start = startPV; *end = index-1;
5594 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5601 static char buf[10*MSG_SIZ];
5602 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5604 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5605 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5606 for(i = forwardMostMove; i<endPV; i++){
5607 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5608 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5611 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5612 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5613 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5619 LoadPV (int x, int y)
5620 { // called on right mouse click to load PV
5621 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5622 lastX = x; lastY = y;
5623 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5631 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5632 if(endPV < 0) return;
5633 if(appData.autoCopyPV) CopyFENToClipboard();
5635 if(extendGame && currentMove > forwardMostMove) {
5636 Boolean saveAnimate = appData.animate;
5638 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5639 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5640 } else storedGames--; // abandon shelved tail of original game
5643 forwardMostMove = currentMove;
5644 currentMove = oldFMM;
5645 appData.animate = FALSE;
5646 ToNrEvent(forwardMostMove);
5647 appData.animate = saveAnimate;
5649 currentMove = forwardMostMove;
5650 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5651 ClearPremoveHighlights();
5652 DrawPosition(TRUE, boards[currentMove]);
5656 MovePV (int x, int y, int h)
5657 { // step through PV based on mouse coordinates (called on mouse move)
5658 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5660 // we must somehow check if right button is still down (might be released off board!)
5661 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5662 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5663 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5665 lastX = x; lastY = y;
5667 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5668 if(endPV < 0) return;
5669 if(y < margin) step = 1; else
5670 if(y > h - margin) step = -1;
5671 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5672 currentMove += step;
5673 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5674 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5675 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5676 DrawPosition(FALSE, boards[currentMove]);
5680 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5681 // All positions will have equal probability, but the current method will not provide a unique
5682 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5688 int piecesLeft[(int)BlackPawn];
5689 int seed, nrOfShuffles;
5692 GetPositionNumber ()
5693 { // sets global variable seed
5696 seed = appData.defaultFrcPosition;
5697 if(seed < 0) { // randomize based on time for negative FRC position numbers
5698 for(i=0; i<50; i++) seed += random();
5699 seed = random() ^ random() >> 8 ^ random() << 8;
5700 if(seed<0) seed = -seed;
5705 put (Board board, int pieceType, int rank, int n, int shade)
5706 // put the piece on the (n-1)-th empty squares of the given shade
5710 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5711 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5712 board[rank][i] = (ChessSquare) pieceType;
5713 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5715 piecesLeft[pieceType]--;
5724 AddOnePiece (Board board, int pieceType, int rank, int shade)
5725 // calculate where the next piece goes, (any empty square), and put it there
5729 i = seed % squaresLeft[shade];
5730 nrOfShuffles *= squaresLeft[shade];
5731 seed /= squaresLeft[shade];
5732 put(board, pieceType, rank, i, shade);
5736 AddTwoPieces (Board board, int pieceType, int rank)
5737 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5739 int i, n=squaresLeft[ANY], j=n-1, k;
5741 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5742 i = seed % k; // pick one
5745 while(i >= j) i -= j--;
5746 j = n - 1 - j; i += j;
5747 put(board, pieceType, rank, j, ANY);
5748 put(board, pieceType, rank, i, ANY);
5752 SetUpShuffle (Board board, int number)
5756 GetPositionNumber(); nrOfShuffles = 1;
5758 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5759 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5760 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5762 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5764 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5765 p = (int) board[0][i];
5766 if(p < (int) BlackPawn) piecesLeft[p] ++;
5767 board[0][i] = EmptySquare;
5770 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5771 // shuffles restricted to allow normal castling put KRR first
5772 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5773 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5774 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5775 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5776 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5777 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5778 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5779 put(board, WhiteRook, 0, 0, ANY);
5780 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5783 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5784 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5785 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5786 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5787 while(piecesLeft[p] >= 2) {
5788 AddOnePiece(board, p, 0, LITE);
5789 AddOnePiece(board, p, 0, DARK);
5791 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5794 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5795 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5796 // but we leave King and Rooks for last, to possibly obey FRC restriction
5797 if(p == (int)WhiteRook) continue;
5798 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5799 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5802 // now everything is placed, except perhaps King (Unicorn) and Rooks
5804 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5805 // Last King gets castling rights
5806 while(piecesLeft[(int)WhiteUnicorn]) {
5807 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5808 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5811 while(piecesLeft[(int)WhiteKing]) {
5812 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5813 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5818 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5819 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5822 // Only Rooks can be left; simply place them all
5823 while(piecesLeft[(int)WhiteRook]) {
5824 i = put(board, WhiteRook, 0, 0, ANY);
5825 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5828 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5830 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5833 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5834 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5837 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5841 SetCharTable (char *table, const char * map)
5842 /* [HGM] moved here from winboard.c because of its general usefulness */
5843 /* Basically a safe strcpy that uses the last character as King */
5845 int result = FALSE; int NrPieces;
5847 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5848 && NrPieces >= 12 && !(NrPieces&1)) {
5849 int i; /* [HGM] Accept even length from 12 to 34 */
5851 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5852 for( i=0; i<NrPieces/2-1; i++ ) {
5854 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5856 table[(int) WhiteKing] = map[NrPieces/2-1];
5857 table[(int) BlackKing] = map[NrPieces-1];
5866 Prelude (Board board)
5867 { // [HGM] superchess: random selection of exo-pieces
5868 int i, j, k; ChessSquare p;
5869 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5871 GetPositionNumber(); // use FRC position number
5873 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5874 SetCharTable(pieceToChar, appData.pieceToCharTable);
5875 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5876 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5879 j = seed%4; seed /= 4;
5880 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5881 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5882 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5883 j = seed%3 + (seed%3 >= j); seed /= 3;
5884 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5885 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5886 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5887 j = seed%3; seed /= 3;
5888 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5889 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5890 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5891 j = seed%2 + (seed%2 >= j); seed /= 2;
5892 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5893 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5894 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5895 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5896 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5897 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5898 put(board, exoPieces[0], 0, 0, ANY);
5899 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5903 InitPosition (int redraw)
5905 ChessSquare (* pieces)[BOARD_FILES];
5906 int i, j, pawnRow=1, pieceRows=1, overrule,
5907 oldx = gameInfo.boardWidth,
5908 oldy = gameInfo.boardHeight,
5909 oldh = gameInfo.holdingsWidth;
5912 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5914 /* [AS] Initialize pv info list [HGM] and game status */
5916 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5917 pvInfoList[i].depth = 0;
5918 boards[i][EP_STATUS] = EP_NONE;
5919 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5922 initialRulePlies = 0; /* 50-move counter start */
5924 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5925 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5929 /* [HGM] logic here is completely changed. In stead of full positions */
5930 /* the initialized data only consist of the two backranks. The switch */
5931 /* selects which one we will use, which is than copied to the Board */
5932 /* initialPosition, which for the rest is initialized by Pawns and */
5933 /* empty squares. This initial position is then copied to boards[0], */
5934 /* possibly after shuffling, so that it remains available. */
5936 gameInfo.holdingsWidth = 0; /* default board sizes */
5937 gameInfo.boardWidth = 8;
5938 gameInfo.boardHeight = 8;
5939 gameInfo.holdingsSize = 0;
5940 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5941 for(i=0; i<BOARD_FILES-2; i++)
5942 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5943 initialPosition[EP_STATUS] = EP_NONE;
5944 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5945 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5946 SetCharTable(pieceNickName, appData.pieceNickNames);
5947 else SetCharTable(pieceNickName, "............");
5950 switch (gameInfo.variant) {
5951 case VariantFischeRandom:
5952 shuffleOpenings = TRUE;
5955 case VariantShatranj:
5956 pieces = ShatranjArray;
5957 nrCastlingRights = 0;
5958 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5961 pieces = makrukArray;
5962 nrCastlingRights = 0;
5963 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5966 pieces = aseanArray;
5967 nrCastlingRights = 0;
5968 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5970 case VariantTwoKings:
5971 pieces = twoKingsArray;
5974 pieces = GrandArray;
5975 nrCastlingRights = 0;
5976 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5977 gameInfo.boardWidth = 10;
5978 gameInfo.boardHeight = 10;
5979 gameInfo.holdingsSize = 7;
5981 case VariantCapaRandom:
5982 shuffleOpenings = TRUE;
5983 case VariantCapablanca:
5984 pieces = CapablancaArray;
5985 gameInfo.boardWidth = 10;
5986 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5989 pieces = GothicArray;
5990 gameInfo.boardWidth = 10;
5991 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5994 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5995 gameInfo.holdingsSize = 7;
5996 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5999 pieces = JanusArray;
6000 gameInfo.boardWidth = 10;
6001 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6002 nrCastlingRights = 6;
6003 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6004 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6005 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6006 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6007 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6008 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6011 pieces = FalconArray;
6012 gameInfo.boardWidth = 10;
6013 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6015 case VariantXiangqi:
6016 pieces = XiangqiArray;
6017 gameInfo.boardWidth = 9;
6018 gameInfo.boardHeight = 10;
6019 nrCastlingRights = 0;
6020 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6023 pieces = ShogiArray;
6024 gameInfo.boardWidth = 9;
6025 gameInfo.boardHeight = 9;
6026 gameInfo.holdingsSize = 7;
6027 nrCastlingRights = 0;
6028 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6031 pieces = ChuArray; pieceRows = 3;
6032 gameInfo.boardWidth = 12;
6033 gameInfo.boardHeight = 12;
6034 nrCastlingRights = 0;
6035 SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6036 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6038 case VariantCourier:
6039 pieces = CourierArray;
6040 gameInfo.boardWidth = 12;
6041 nrCastlingRights = 0;
6042 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6044 case VariantKnightmate:
6045 pieces = KnightmateArray;
6046 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6048 case VariantSpartan:
6049 pieces = SpartanArray;
6050 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6054 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6057 pieces = fairyArray;
6058 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6061 pieces = GreatArray;
6062 gameInfo.boardWidth = 10;
6063 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6064 gameInfo.holdingsSize = 8;
6068 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6069 gameInfo.holdingsSize = 8;
6070 startedFromSetupPosition = TRUE;
6072 case VariantCrazyhouse:
6073 case VariantBughouse:
6075 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6076 gameInfo.holdingsSize = 5;
6078 case VariantWildCastle:
6080 /* !!?shuffle with kings guaranteed to be on d or e file */
6081 shuffleOpenings = 1;
6083 case VariantNoCastle:
6085 nrCastlingRights = 0;
6086 /* !!?unconstrained back-rank shuffle */
6087 shuffleOpenings = 1;
6092 if(appData.NrFiles >= 0) {
6093 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6094 gameInfo.boardWidth = appData.NrFiles;
6096 if(appData.NrRanks >= 0) {
6097 gameInfo.boardHeight = appData.NrRanks;
6099 if(appData.holdingsSize >= 0) {
6100 i = appData.holdingsSize;
6101 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6102 gameInfo.holdingsSize = i;
6104 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6105 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6106 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6108 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6109 if(pawnRow < 1) pawnRow = 1;
6110 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6111 if(gameInfo.variant == VariantChu) pawnRow = 3;
6113 /* User pieceToChar list overrules defaults */
6114 if(appData.pieceToCharTable != NULL)
6115 SetCharTable(pieceToChar, appData.pieceToCharTable);
6117 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6119 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6120 s = (ChessSquare) 0; /* account holding counts in guard band */
6121 for( i=0; i<BOARD_HEIGHT; i++ )
6122 initialPosition[i][j] = s;
6124 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6125 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6126 initialPosition[pawnRow][j] = WhitePawn;
6127 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6128 if(gameInfo.variant == VariantXiangqi) {
6130 initialPosition[pawnRow][j] =
6131 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6132 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6133 initialPosition[2][j] = WhiteCannon;
6134 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6138 if(gameInfo.variant == VariantChu) {
6139 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6140 initialPosition[pawnRow+1][j] = WhiteCobra,
6141 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6142 for(i=1; i<pieceRows; i++) {
6143 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6144 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6147 if(gameInfo.variant == VariantGrand) {
6148 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6149 initialPosition[0][j] = WhiteRook;
6150 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6153 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
6155 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6158 initialPosition[1][j] = WhiteBishop;
6159 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6161 initialPosition[1][j] = WhiteRook;
6162 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6165 if( nrCastlingRights == -1) {
6166 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6167 /* This sets default castling rights from none to normal corners */
6168 /* Variants with other castling rights must set them themselves above */
6169 nrCastlingRights = 6;
6171 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6172 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6173 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6174 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6175 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6176 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6179 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6180 if(gameInfo.variant == VariantGreat) { // promotion commoners
6181 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6182 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6183 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6184 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6186 if( gameInfo.variant == VariantSChess ) {
6187 initialPosition[1][0] = BlackMarshall;
6188 initialPosition[2][0] = BlackAngel;
6189 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6190 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6191 initialPosition[1][1] = initialPosition[2][1] =
6192 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6194 if (appData.debugMode) {
6195 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6197 if(shuffleOpenings) {
6198 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6199 startedFromSetupPosition = TRUE;
6201 if(startedFromPositionFile) {
6202 /* [HGM] loadPos: use PositionFile for every new game */
6203 CopyBoard(initialPosition, filePosition);
6204 for(i=0; i<nrCastlingRights; i++)
6205 initialRights[i] = filePosition[CASTLING][i];
6206 startedFromSetupPosition = TRUE;
6209 CopyBoard(boards[0], initialPosition);
6211 if(oldx != gameInfo.boardWidth ||
6212 oldy != gameInfo.boardHeight ||
6213 oldv != gameInfo.variant ||
6214 oldh != gameInfo.holdingsWidth
6216 InitDrawingSizes(-2 ,0);
6218 oldv = gameInfo.variant;
6220 DrawPosition(TRUE, boards[currentMove]);
6224 SendBoard (ChessProgramState *cps, int moveNum)
6226 char message[MSG_SIZ];
6228 if (cps->useSetboard) {
6229 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6230 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6231 SendToProgram(message, cps);
6236 int i, j, left=0, right=BOARD_WIDTH;
6237 /* Kludge to set black to move, avoiding the troublesome and now
6238 * deprecated "black" command.
6240 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6241 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6243 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6245 SendToProgram("edit\n", cps);
6246 SendToProgram("#\n", cps);
6247 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6248 bp = &boards[moveNum][i][left];
6249 for (j = left; j < right; j++, bp++) {
6250 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6251 if ((int) *bp < (int) BlackPawn) {
6252 if(j == BOARD_RGHT+1)
6253 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6254 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6255 if(message[0] == '+' || message[0] == '~') {
6256 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6257 PieceToChar((ChessSquare)(DEMOTED *bp)),
6260 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6261 message[1] = BOARD_RGHT - 1 - j + '1';
6262 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6264 SendToProgram(message, cps);
6269 SendToProgram("c\n", cps);
6270 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6271 bp = &boards[moveNum][i][left];
6272 for (j = left; j < right; j++, bp++) {
6273 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6274 if (((int) *bp != (int) EmptySquare)
6275 && ((int) *bp >= (int) BlackPawn)) {
6276 if(j == BOARD_LEFT-2)
6277 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6278 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6280 if(message[0] == '+' || message[0] == '~') {
6281 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6282 PieceToChar((ChessSquare)(DEMOTED *bp)),
6285 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6286 message[1] = BOARD_RGHT - 1 - j + '1';
6287 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6289 SendToProgram(message, cps);
6294 SendToProgram(".\n", cps);
6296 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6299 char exclusionHeader[MSG_SIZ];
6300 int exCnt, excludePtr;
6301 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6302 static Exclusion excluTab[200];
6303 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6309 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6310 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6316 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6317 excludePtr = 24; exCnt = 0;
6322 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6323 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6324 char buf[2*MOVE_LEN], *p;
6325 Exclusion *e = excluTab;
6327 for(i=0; i<exCnt; i++)
6328 if(e[i].ff == fromX && e[i].fr == fromY &&
6329 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6330 if(i == exCnt) { // was not in exclude list; add it
6331 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6332 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6333 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6336 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6337 excludePtr++; e[i].mark = excludePtr++;
6338 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6341 exclusionHeader[e[i].mark] = state;
6345 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6346 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6350 if((signed char)promoChar == -1) { // kludge to indicate best move
6351 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6352 return 1; // if unparsable, abort
6354 // update exclusion map (resolving toggle by consulting existing state)
6355 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6357 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6358 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6359 excludeMap[k] |= 1<<j;
6360 else excludeMap[k] &= ~(1<<j);
6362 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6364 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6365 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6367 return (state == '+');
6371 ExcludeClick (int index)
6374 Exclusion *e = excluTab;
6375 if(index < 25) { // none, best or tail clicked
6376 if(index < 13) { // none: include all
6377 WriteMap(0); // clear map
6378 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6379 SendToBoth("include all\n"); // and inform engine
6380 } else if(index > 18) { // tail
6381 if(exclusionHeader[19] == '-') { // tail was excluded
6382 SendToBoth("include all\n");
6383 WriteMap(0); // clear map completely
6384 // now re-exclude selected moves
6385 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6386 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6387 } else { // tail was included or in mixed state
6388 SendToBoth("exclude all\n");
6389 WriteMap(0xFF); // fill map completely
6390 // now re-include selected moves
6391 j = 0; // count them
6392 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6393 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6394 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6397 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6400 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6401 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6402 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6409 DefaultPromoChoice (int white)
6412 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6413 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6414 result = WhiteFerz; // no choice
6415 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6416 result= WhiteKing; // in Suicide Q is the last thing we want
6417 else if(gameInfo.variant == VariantSpartan)
6418 result = white ? WhiteQueen : WhiteAngel;
6419 else result = WhiteQueen;
6420 if(!white) result = WHITE_TO_BLACK result;
6424 static int autoQueen; // [HGM] oneclick
6427 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6429 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6430 /* [HGM] add Shogi promotions */
6431 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6436 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6437 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6439 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6440 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6443 piece = boards[currentMove][fromY][fromX];
6444 if(gameInfo.variant == VariantChu) {
6445 int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6446 promotionZoneSize = BOARD_HEIGHT/3;
6447 highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6448 } else if(gameInfo.variant == VariantShogi) {
6449 promotionZoneSize = BOARD_HEIGHT/3;
6450 highestPromotingPiece = (int)WhiteAlfil;
6451 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6452 promotionZoneSize = 3;
6455 // Treat Lance as Pawn when it is not representing Amazon
6456 if(gameInfo.variant != VariantSuper) {
6457 if(piece == WhiteLance) piece = WhitePawn; else
6458 if(piece == BlackLance) piece = BlackPawn;
6461 // next weed out all moves that do not touch the promotion zone at all
6462 if((int)piece >= BlackPawn) {
6463 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6465 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6467 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6468 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6471 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6473 // weed out mandatory Shogi promotions
6474 if(gameInfo.variant == VariantShogi) {
6475 if(piece >= BlackPawn) {
6476 if(toY == 0 && piece == BlackPawn ||
6477 toY == 0 && piece == BlackQueen ||
6478 toY <= 1 && piece == BlackKnight) {
6483 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6484 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6485 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6492 // weed out obviously illegal Pawn moves
6493 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6494 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6495 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6496 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6497 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6498 // note we are not allowed to test for valid (non-)capture, due to premove
6501 // we either have a choice what to promote to, or (in Shogi) whether to promote
6502 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6503 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6504 *promoChoice = PieceToChar(BlackFerz); // no choice
6507 // no sense asking what we must promote to if it is going to explode...
6508 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6509 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6512 // give caller the default choice even if we will not make it
6513 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6514 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6515 if( sweepSelect && gameInfo.variant != VariantGreat
6516 && gameInfo.variant != VariantGrand
6517 && gameInfo.variant != VariantSuper) return FALSE;
6518 if(autoQueen) return FALSE; // predetermined
6520 // suppress promotion popup on illegal moves that are not premoves
6521 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6522 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6523 if(appData.testLegality && !premove) {
6524 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6525 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) ? '+' : NULLCHAR);
6526 if(moveType != WhitePromotion && moveType != BlackPromotion)
6534 InPalace (int row, int column)
6535 { /* [HGM] for Xiangqi */
6536 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6537 column < (BOARD_WIDTH + 4)/2 &&
6538 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6543 PieceForSquare (int x, int y)
6545 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6548 return boards[currentMove][y][x];
6552 OKToStartUserMove (int x, int y)
6554 ChessSquare from_piece;
6557 if (matchMode) return FALSE;
6558 if (gameMode == EditPosition) return TRUE;
6560 if (x >= 0 && y >= 0)
6561 from_piece = boards[currentMove][y][x];
6563 from_piece = EmptySquare;
6565 if (from_piece == EmptySquare) return FALSE;
6567 white_piece = (int)from_piece >= (int)WhitePawn &&
6568 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6572 case TwoMachinesPlay:
6580 case MachinePlaysWhite:
6581 case IcsPlayingBlack:
6582 if (appData.zippyPlay) return FALSE;
6584 DisplayMoveError(_("You are playing Black"));
6589 case MachinePlaysBlack:
6590 case IcsPlayingWhite:
6591 if (appData.zippyPlay) return FALSE;
6593 DisplayMoveError(_("You are playing White"));
6598 case PlayFromGameFile:
6599 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6601 if (!white_piece && WhiteOnMove(currentMove)) {
6602 DisplayMoveError(_("It is White's turn"));
6605 if (white_piece && !WhiteOnMove(currentMove)) {
6606 DisplayMoveError(_("It is Black's turn"));
6609 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6610 /* Editing correspondence game history */
6611 /* Could disallow this or prompt for confirmation */
6616 case BeginningOfGame:
6617 if (appData.icsActive) return FALSE;
6618 if (!appData.noChessProgram) {
6620 DisplayMoveError(_("You are playing White"));
6627 if (!white_piece && WhiteOnMove(currentMove)) {
6628 DisplayMoveError(_("It is White's turn"));
6631 if (white_piece && !WhiteOnMove(currentMove)) {
6632 DisplayMoveError(_("It is Black's turn"));
6641 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6642 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6643 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6644 && gameMode != AnalyzeFile && gameMode != Training) {
6645 DisplayMoveError(_("Displayed position is not current"));
6652 OnlyMove (int *x, int *y, Boolean captures)
6654 DisambiguateClosure cl;
6655 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6657 case MachinePlaysBlack:
6658 case IcsPlayingWhite:
6659 case BeginningOfGame:
6660 if(!WhiteOnMove(currentMove)) return FALSE;
6662 case MachinePlaysWhite:
6663 case IcsPlayingBlack:
6664 if(WhiteOnMove(currentMove)) return FALSE;
6671 cl.pieceIn = EmptySquare;
6676 cl.promoCharIn = NULLCHAR;
6677 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6678 if( cl.kind == NormalMove ||
6679 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6680 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6681 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6688 if(cl.kind != ImpossibleMove) return FALSE;
6689 cl.pieceIn = EmptySquare;
6694 cl.promoCharIn = NULLCHAR;
6695 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6696 if( cl.kind == NormalMove ||
6697 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6698 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6699 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6704 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6710 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6711 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6712 int lastLoadGameUseList = FALSE;
6713 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6714 ChessMove lastLoadGameStart = EndOfFile;
6718 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6722 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6724 /* Check if the user is playing in turn. This is complicated because we
6725 let the user "pick up" a piece before it is his turn. So the piece he
6726 tried to pick up may have been captured by the time he puts it down!
6727 Therefore we use the color the user is supposed to be playing in this
6728 test, not the color of the piece that is currently on the starting
6729 square---except in EditGame mode, where the user is playing both
6730 sides; fortunately there the capture race can't happen. (It can
6731 now happen in IcsExamining mode, but that's just too bad. The user
6732 will get a somewhat confusing message in that case.)
6737 case TwoMachinesPlay:
6741 /* We switched into a game mode where moves are not accepted,
6742 perhaps while the mouse button was down. */
6745 case MachinePlaysWhite:
6746 /* User is moving for Black */
6747 if (WhiteOnMove(currentMove)) {
6748 DisplayMoveError(_("It is White's turn"));
6753 case MachinePlaysBlack:
6754 /* User is moving for White */
6755 if (!WhiteOnMove(currentMove)) {
6756 DisplayMoveError(_("It is Black's turn"));
6761 case PlayFromGameFile:
6762 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6765 case BeginningOfGame:
6768 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6769 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6770 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6771 /* User is moving for Black */
6772 if (WhiteOnMove(currentMove)) {
6773 DisplayMoveError(_("It is White's turn"));
6777 /* User is moving for White */
6778 if (!WhiteOnMove(currentMove)) {
6779 DisplayMoveError(_("It is Black's turn"));
6785 case IcsPlayingBlack:
6786 /* User is moving for Black */
6787 if (WhiteOnMove(currentMove)) {
6788 if (!appData.premove) {
6789 DisplayMoveError(_("It is White's turn"));
6790 } else if (toX >= 0 && toY >= 0) {
6793 premoveFromX = fromX;
6794 premoveFromY = fromY;
6795 premovePromoChar = promoChar;
6797 if (appData.debugMode)
6798 fprintf(debugFP, "Got premove: fromX %d,"
6799 "fromY %d, toX %d, toY %d\n",
6800 fromX, fromY, toX, toY);
6806 case IcsPlayingWhite:
6807 /* User is moving for White */
6808 if (!WhiteOnMove(currentMove)) {
6809 if (!appData.premove) {
6810 DisplayMoveError(_("It is Black's turn"));
6811 } else if (toX >= 0 && toY >= 0) {
6814 premoveFromX = fromX;
6815 premoveFromY = fromY;
6816 premovePromoChar = promoChar;
6818 if (appData.debugMode)
6819 fprintf(debugFP, "Got premove: fromX %d,"
6820 "fromY %d, toX %d, toY %d\n",
6821 fromX, fromY, toX, toY);
6831 /* EditPosition, empty square, or different color piece;
6832 click-click move is possible */
6833 if (toX == -2 || toY == -2) {
6834 boards[0][fromY][fromX] = EmptySquare;
6835 DrawPosition(FALSE, boards[currentMove]);
6837 } else if (toX >= 0 && toY >= 0) {
6838 boards[0][toY][toX] = boards[0][fromY][fromX];
6839 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6840 if(boards[0][fromY][0] != EmptySquare) {
6841 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6842 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6845 if(fromX == BOARD_RGHT+1) {
6846 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6847 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6848 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6851 boards[0][fromY][fromX] = gatingPiece;
6852 DrawPosition(FALSE, boards[currentMove]);
6858 if(toX < 0 || toY < 0) return;
6859 pup = boards[currentMove][toY][toX];
6861 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6862 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6863 if( pup != EmptySquare ) return;
6864 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6865 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6866 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6867 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6868 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6869 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6870 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6874 /* [HGM] always test for legality, to get promotion info */
6875 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6876 fromY, fromX, toY, toX, promoChar);
6878 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6880 /* [HGM] but possibly ignore an IllegalMove result */
6881 if (appData.testLegality) {
6882 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6883 DisplayMoveError(_("Illegal move"));
6888 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6889 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6890 ClearPremoveHighlights(); // was included
6891 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6895 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6898 /* Common tail of UserMoveEvent and DropMenuEvent */
6900 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6904 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6905 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6906 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6907 if(WhiteOnMove(currentMove)) {
6908 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6910 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6914 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6915 move type in caller when we know the move is a legal promotion */
6916 if(moveType == NormalMove && promoChar)
6917 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6919 /* [HGM] <popupFix> The following if has been moved here from
6920 UserMoveEvent(). Because it seemed to belong here (why not allow
6921 piece drops in training games?), and because it can only be
6922 performed after it is known to what we promote. */
6923 if (gameMode == Training) {
6924 /* compare the move played on the board to the next move in the
6925 * game. If they match, display the move and the opponent's response.
6926 * If they don't match, display an error message.
6930 CopyBoard(testBoard, boards[currentMove]);
6931 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6933 if (CompareBoards(testBoard, boards[currentMove+1])) {
6934 ForwardInner(currentMove+1);
6936 /* Autoplay the opponent's response.
6937 * if appData.animate was TRUE when Training mode was entered,
6938 * the response will be animated.
6940 saveAnimate = appData.animate;
6941 appData.animate = animateTraining;
6942 ForwardInner(currentMove+1);
6943 appData.animate = saveAnimate;
6945 /* check for the end of the game */
6946 if (currentMove >= forwardMostMove) {
6947 gameMode = PlayFromGameFile;
6949 SetTrainingModeOff();
6950 DisplayInformation(_("End of game"));
6953 DisplayError(_("Incorrect move"), 0);
6958 /* Ok, now we know that the move is good, so we can kill
6959 the previous line in Analysis Mode */
6960 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6961 && currentMove < forwardMostMove) {
6962 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6963 else forwardMostMove = currentMove;
6968 /* If we need the chess program but it's dead, restart it */
6969 ResurrectChessProgram();
6971 /* A user move restarts a paused game*/
6975 thinkOutput[0] = NULLCHAR;
6977 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6979 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6980 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6984 if (gameMode == BeginningOfGame) {
6985 if (appData.noChessProgram) {
6986 gameMode = EditGame;
6990 gameMode = MachinePlaysBlack;
6993 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6995 if (first.sendName) {
6996 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6997 SendToProgram(buf, &first);
7004 /* Relay move to ICS or chess engine */
7005 if (appData.icsActive) {
7006 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7007 gameMode == IcsExamining) {
7008 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7009 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7011 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7013 // also send plain move, in case ICS does not understand atomic claims
7014 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7018 if (first.sendTime && (gameMode == BeginningOfGame ||
7019 gameMode == MachinePlaysWhite ||
7020 gameMode == MachinePlaysBlack)) {
7021 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7023 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7024 // [HGM] book: if program might be playing, let it use book
7025 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7026 first.maybeThinking = TRUE;
7027 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7028 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7029 SendBoard(&first, currentMove+1);
7030 if(second.analyzing) {
7031 if(!second.useSetboard) SendToProgram("undo\n", &second);
7032 SendBoard(&second, currentMove+1);
7035 SendMoveToProgram(forwardMostMove-1, &first);
7036 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7038 if (currentMove == cmailOldMove + 1) {
7039 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7043 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7047 if(appData.testLegality)
7048 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7054 if (WhiteOnMove(currentMove)) {
7055 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7057 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7061 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7066 case MachinePlaysBlack:
7067 case MachinePlaysWhite:
7068 /* disable certain menu options while machine is thinking */
7069 SetMachineThinkingEnables();
7076 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7077 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7079 if(bookHit) { // [HGM] book: simulate book reply
7080 static char bookMove[MSG_SIZ]; // a bit generous?
7082 programStats.nodes = programStats.depth = programStats.time =
7083 programStats.score = programStats.got_only_move = 0;
7084 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7086 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7087 strcat(bookMove, bookHit);
7088 HandleMachineMove(bookMove, &first);
7094 MarkByFEN(char *fen)
7097 if(!appData.markers || !appData.highlightDragging) return;
7098 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7099 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7103 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7104 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7105 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7106 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7107 if(*fen == 'T') marker[r][f++] = 0; else
7108 if(*fen == 'Y') marker[r][f++] = 1; else
7109 if(*fen == 'G') marker[r][f++] = 3; else
7110 if(*fen == 'B') marker[r][f++] = 4; else
7111 if(*fen == 'C') marker[r][f++] = 5; else
7112 if(*fen == 'M') marker[r][f++] = 6; else
7113 if(*fen == 'W') marker[r][f++] = 7; else
7114 if(*fen == 'D') marker[r][f++] = 8; else
7115 if(*fen == 'R') marker[r][f++] = 2; else {
7116 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7119 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7123 DrawPosition(TRUE, NULL);
7126 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7129 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7131 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7132 Markers *m = (Markers *) closure;
7133 if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7134 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7135 || kind == WhiteCapturesEnPassant
7136 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7137 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7141 MarkTargetSquares (int clear)
7144 if(clear) { // no reason to ever suppress clearing
7145 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = baseMarker[y][x] = 0;
7146 if(!sum) return; // nothing was cleared,no redraw needed
7149 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7150 !appData.testLegality || gameMode == EditPosition) return;
7151 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7152 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7153 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7155 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7158 DrawPosition(FALSE, NULL);
7162 Explode (Board board, int fromX, int fromY, int toX, int toY)
7164 if(gameInfo.variant == VariantAtomic &&
7165 (board[toY][toX] != EmptySquare || // capture?
7166 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7167 board[fromY][fromX] == BlackPawn )
7169 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7175 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7178 CanPromote (ChessSquare piece, int y)
7180 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7181 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7182 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7183 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7184 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7185 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7186 return (piece == BlackPawn && y == 1 ||
7187 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7188 piece == BlackLance && y == 1 ||
7189 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7193 HoverEvent (int xPix, int yPix, int x, int y)
7195 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7197 if(!first.highlight) return;
7198 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7199 if(x == oldX && y == oldY) return; // only do something if we enter new square
7200 oldFromX = fromX; oldFromY = fromY;
7201 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7202 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7203 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7204 else if(oldX != x || oldY != y) {
7205 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7206 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7207 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7208 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7210 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7211 SendToProgram(buf, &first);
7214 // SetHighlights(fromX, fromY, x, y);
7218 void ReportClick(char *action, int x, int y)
7220 char buf[MSG_SIZ]; // Inform engine of what user does
7222 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7223 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7224 if(!first.highlight || gameMode == EditPosition) return;
7225 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7226 SendToProgram(buf, &first);
7230 LeftClick (ClickType clickType, int xPix, int yPix)
7233 Boolean saveAnimate;
7234 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7235 char promoChoice = NULLCHAR;
7237 static TimeMark lastClickTime, prevClickTime;
7239 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7241 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7243 if (clickType == Press) ErrorPopDown();
7244 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7246 x = EventToSquare(xPix, BOARD_WIDTH);
7247 y = EventToSquare(yPix, BOARD_HEIGHT);
7248 if (!flipView && y >= 0) {
7249 y = BOARD_HEIGHT - 1 - y;
7251 if (flipView && x >= 0) {
7252 x = BOARD_WIDTH - 1 - x;
7255 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7256 defaultPromoChoice = promoSweep;
7257 promoSweep = EmptySquare; // terminate sweep
7258 promoDefaultAltered = TRUE;
7259 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7262 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7263 if(clickType == Release) return; // ignore upclick of click-click destination
7264 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7265 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7266 if(gameInfo.holdingsWidth &&
7267 (WhiteOnMove(currentMove)
7268 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7269 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7270 // click in right holdings, for determining promotion piece
7271 ChessSquare p = boards[currentMove][y][x];
7272 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7273 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7274 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7275 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7280 DrawPosition(FALSE, boards[currentMove]);
7284 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7285 if(clickType == Press
7286 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7287 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7288 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7291 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7292 // could be static click on premove from-square: abort premove
7294 ClearPremoveHighlights();
7297 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7298 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7300 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7301 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7302 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7303 defaultPromoChoice = DefaultPromoChoice(side);
7306 autoQueen = appData.alwaysPromoteToQueen;
7310 gatingPiece = EmptySquare;
7311 if (clickType != Press) {
7312 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7313 DragPieceEnd(xPix, yPix); dragging = 0;
7314 DrawPosition(FALSE, NULL);
7318 doubleClick = FALSE;
7319 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7320 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7322 fromX = x; fromY = y; toX = toY = killX = killY = -1;
7323 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7324 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7325 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7327 if (OKToStartUserMove(fromX, fromY)) {
7329 ReportClick("lift", x, y);
7330 MarkTargetSquares(0);
7331 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7332 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7333 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7334 promoSweep = defaultPromoChoice;
7335 selectFlag = 0; lastX = xPix; lastY = yPix;
7336 Sweep(0); // Pawn that is going to promote: preview promotion piece
7337 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7339 if (appData.highlightDragging) {
7340 SetHighlights(fromX, fromY, -1, -1);
7344 } else fromX = fromY = -1;
7350 if (clickType == Press && gameMode != EditPosition) {
7355 // ignore off-board to clicks
7356 if(y < 0 || x < 0) return;
7358 /* Check if clicking again on the same color piece */
7359 fromP = boards[currentMove][fromY][fromX];
7360 toP = boards[currentMove][y][x];
7361 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7362 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7363 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7364 WhitePawn <= toP && toP <= WhiteKing &&
7365 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7366 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7367 (BlackPawn <= fromP && fromP <= BlackKing &&
7368 BlackPawn <= toP && toP <= BlackKing &&
7369 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7370 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7371 /* Clicked again on same color piece -- changed his mind */
7372 second = (x == fromX && y == fromY);
7374 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7375 second = FALSE; // first double-click rather than scond click
7376 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7378 promoDefaultAltered = FALSE;
7379 MarkTargetSquares(1);
7380 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7381 if (appData.highlightDragging) {
7382 SetHighlights(x, y, -1, -1);
7386 if (OKToStartUserMove(x, y)) {
7387 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7388 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7389 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7390 gatingPiece = boards[currentMove][fromY][fromX];
7391 else gatingPiece = doubleClick ? fromP : EmptySquare;
7393 fromY = y; dragging = 1;
7394 ReportClick("lift", x, y);
7395 MarkTargetSquares(0);
7396 DragPieceBegin(xPix, yPix, FALSE);
7397 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7398 promoSweep = defaultPromoChoice;
7399 selectFlag = 0; lastX = xPix; lastY = yPix;
7400 Sweep(0); // Pawn that is going to promote: preview promotion piece
7404 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7407 // ignore clicks on holdings
7408 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7411 if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7412 DragPieceEnd(xPix, yPix); dragging = 0;
7414 // a deferred attempt to click-click move an empty square on top of a piece
7415 boards[currentMove][y][x] = EmptySquare;
7417 DrawPosition(FALSE, boards[currentMove]);
7418 fromX = fromY = -1; clearFlag = 0;
7421 if (appData.animateDragging) {
7422 /* Undo animation damage if any */
7423 DrawPosition(FALSE, NULL);
7425 if (second || sweepSelecting) {
7426 /* Second up/down in same square; just abort move */
7427 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7428 second = sweepSelecting = 0;
7430 gatingPiece = EmptySquare;
7431 MarkTargetSquares(1);
7434 ClearPremoveHighlights();
7436 /* First upclick in same square; start click-click mode */
7437 SetHighlights(x, y, -1, -1);
7444 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7445 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7446 DisplayMessage(_("only marked squares are legal"),"");
7447 DrawPosition(TRUE, NULL);
7448 return; // ignore to-click
7451 /* we now have a different from- and (possibly off-board) to-square */
7452 /* Completed move */
7453 if(!sweepSelecting) {
7458 saveAnimate = appData.animate;
7459 if (clickType == Press) {
7460 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7461 // must be Edit Position mode with empty-square selected
7462 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7463 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7466 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7469 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7470 killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7472 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7473 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7474 if(appData.sweepSelect) {
7475 ChessSquare piece = boards[currentMove][fromY][fromX];
7476 promoSweep = defaultPromoChoice;
7477 if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7478 selectFlag = 0; lastX = xPix; lastY = yPix;
7479 Sweep(0); // Pawn that is going to promote: preview promotion piece
7481 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7482 MarkTargetSquares(1);
7484 return; // promo popup appears on up-click
7486 /* Finish clickclick move */
7487 if (appData.animate || appData.highlightLastMove) {
7488 SetHighlights(fromX, fromY, toX, toY);
7492 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7494 if (appData.animate || appData.highlightLastMove) {
7495 SetHighlights(fromX, fromY, toX, toY);
7501 // [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
7502 /* Finish drag move */
7503 if (appData.highlightLastMove) {
7504 SetHighlights(fromX, fromY, toX, toY);
7509 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7510 dragging *= 2; // flag button-less dragging if we are dragging
7511 MarkTargetSquares(1);
7512 if(x == killX && y == killY) killX = killY = -1; else {
7513 killX = x; killY = y; //remeber this square as intermediate
7514 ReportClick("put", x, y); // and inform engine
7515 ReportClick("lift", x, y);
7516 MarkTargetSquares(0);
7520 DragPieceEnd(xPix, yPix); dragging = 0;
7521 /* Don't animate move and drag both */
7522 appData.animate = FALSE;
7525 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7526 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7527 ChessSquare piece = boards[currentMove][fromY][fromX];
7528 if(gameMode == EditPosition && piece != EmptySquare &&
7529 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7532 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7533 n = PieceToNumber(piece - (int)BlackPawn);
7534 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7535 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7536 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7538 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7539 n = PieceToNumber(piece);
7540 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7541 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7542 boards[currentMove][n][BOARD_WIDTH-2]++;
7544 boards[currentMove][fromY][fromX] = EmptySquare;
7548 MarkTargetSquares(1);
7549 DrawPosition(TRUE, boards[currentMove]);
7553 // off-board moves should not be highlighted
7554 if(x < 0 || y < 0) ClearHighlights();
7555 else ReportClick("put", x, y);
7557 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7559 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7560 SetHighlights(fromX, fromY, toX, toY);
7561 MarkTargetSquares(1);
7562 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7563 // [HGM] super: promotion to captured piece selected from holdings
7564 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7565 promotionChoice = TRUE;
7566 // kludge follows to temporarily execute move on display, without promoting yet
7567 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7568 boards[currentMove][toY][toX] = p;
7569 DrawPosition(FALSE, boards[currentMove]);
7570 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7571 boards[currentMove][toY][toX] = q;
7572 DisplayMessage("Click in holdings to choose piece", "");
7577 int oldMove = currentMove;
7578 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7579 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7580 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7581 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7582 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7583 DrawPosition(TRUE, boards[currentMove]);
7584 MarkTargetSquares(1);
7587 appData.animate = saveAnimate;
7588 if (appData.animate || appData.animateDragging) {
7589 /* Undo animation damage if needed */
7590 DrawPosition(FALSE, NULL);
7595 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7596 { // front-end-free part taken out of PieceMenuPopup
7597 int whichMenu; int xSqr, ySqr;
7599 if(seekGraphUp) { // [HGM] seekgraph
7600 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7601 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7605 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7606 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7607 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7608 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7609 if(action == Press) {
7610 originalFlip = flipView;
7611 flipView = !flipView; // temporarily flip board to see game from partners perspective
7612 DrawPosition(TRUE, partnerBoard);
7613 DisplayMessage(partnerStatus, "");
7615 } else if(action == Release) {
7616 flipView = originalFlip;
7617 DrawPosition(TRUE, boards[currentMove]);
7623 xSqr = EventToSquare(x, BOARD_WIDTH);
7624 ySqr = EventToSquare(y, BOARD_HEIGHT);
7625 if (action == Release) {
7626 if(pieceSweep != EmptySquare) {
7627 EditPositionMenuEvent(pieceSweep, toX, toY);
7628 pieceSweep = EmptySquare;
7629 } else UnLoadPV(); // [HGM] pv
7631 if (action != Press) return -2; // return code to be ignored
7634 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7636 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7637 if (xSqr < 0 || ySqr < 0) return -1;
7638 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7639 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7640 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7641 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7645 if(!appData.icsEngineAnalyze) return -1;
7646 case IcsPlayingWhite:
7647 case IcsPlayingBlack:
7648 if(!appData.zippyPlay) goto noZip;
7651 case MachinePlaysWhite:
7652 case MachinePlaysBlack:
7653 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7654 if (!appData.dropMenu) {
7656 return 2; // flag front-end to grab mouse events
7658 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7659 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7662 if (xSqr < 0 || ySqr < 0) return -1;
7663 if (!appData.dropMenu || appData.testLegality &&
7664 gameInfo.variant != VariantBughouse &&
7665 gameInfo.variant != VariantCrazyhouse) return -1;
7666 whichMenu = 1; // drop menu
7672 if (((*fromX = xSqr) < 0) ||
7673 ((*fromY = ySqr) < 0)) {
7674 *fromX = *fromY = -1;
7678 *fromX = BOARD_WIDTH - 1 - *fromX;
7680 *fromY = BOARD_HEIGHT - 1 - *fromY;
7686 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7688 // char * hint = lastHint;
7689 FrontEndProgramStats stats;
7691 stats.which = cps == &first ? 0 : 1;
7692 stats.depth = cpstats->depth;
7693 stats.nodes = cpstats->nodes;
7694 stats.score = cpstats->score;
7695 stats.time = cpstats->time;
7696 stats.pv = cpstats->movelist;
7697 stats.hint = lastHint;
7698 stats.an_move_index = 0;
7699 stats.an_move_count = 0;
7701 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7702 stats.hint = cpstats->move_name;
7703 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7704 stats.an_move_count = cpstats->nr_moves;
7707 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
7709 SetProgramStats( &stats );
7713 ClearEngineOutputPane (int which)
7715 static FrontEndProgramStats dummyStats;
7716 dummyStats.which = which;
7717 dummyStats.pv = "#";
7718 SetProgramStats( &dummyStats );
7721 #define MAXPLAYERS 500
7724 TourneyStandings (int display)
7726 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7727 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7728 char result, *p, *names[MAXPLAYERS];
7730 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7731 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7732 names[0] = p = strdup(appData.participants);
7733 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7735 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7737 while(result = appData.results[nr]) {
7738 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7739 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7740 wScore = bScore = 0;
7742 case '+': wScore = 2; break;
7743 case '-': bScore = 2; break;
7744 case '=': wScore = bScore = 1; break;
7746 case '*': return strdup("busy"); // tourney not finished
7754 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7755 for(w=0; w<nPlayers; w++) {
7757 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7758 ranking[w] = b; points[w] = bScore; score[b] = -2;
7760 p = malloc(nPlayers*34+1);
7761 for(w=0; w<nPlayers && w<display; w++)
7762 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7768 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7769 { // count all piece types
7771 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7772 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7773 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7776 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7777 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7778 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7779 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7780 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7781 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7786 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7788 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7789 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7791 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7792 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7793 if(myPawns == 2 && nMine == 3) // KPP
7794 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7795 if(myPawns == 1 && nMine == 2) // KP
7796 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7797 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7798 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7799 if(myPawns) return FALSE;
7800 if(pCnt[WhiteRook+side])
7801 return pCnt[BlackRook-side] ||
7802 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7803 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7804 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7805 if(pCnt[WhiteCannon+side]) {
7806 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7807 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7809 if(pCnt[WhiteKnight+side])
7810 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7815 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7817 VariantClass v = gameInfo.variant;
7819 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7820 if(v == VariantShatranj) return TRUE; // always winnable through baring
7821 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7822 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7824 if(v == VariantXiangqi) {
7825 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7827 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7828 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7829 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7830 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7831 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7832 if(stale) // we have at least one last-rank P plus perhaps C
7833 return majors // KPKX
7834 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7836 return pCnt[WhiteFerz+side] // KCAK
7837 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7838 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7839 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7841 } else if(v == VariantKnightmate) {
7842 if(nMine == 1) return FALSE;
7843 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7844 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7845 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7847 if(nMine == 1) return FALSE; // bare King
7848 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
7849 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7850 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7851 // by now we have King + 1 piece (or multiple Bishops on the same color)
7852 if(pCnt[WhiteKnight+side])
7853 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7854 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7855 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7857 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7858 if(pCnt[WhiteAlfil+side])
7859 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7860 if(pCnt[WhiteWazir+side])
7861 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7868 CompareWithRights (Board b1, Board b2)
7871 if(!CompareBoards(b1, b2)) return FALSE;
7872 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7873 /* compare castling rights */
7874 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7875 rights++; /* King lost rights, while rook still had them */
7876 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7877 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7878 rights++; /* but at least one rook lost them */
7880 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7882 if( b1[CASTLING][5] != NoRights ) {
7883 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7890 Adjudicate (ChessProgramState *cps)
7891 { // [HGM] some adjudications useful with buggy engines
7892 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7893 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7894 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7895 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7896 int k, drop, count = 0; static int bare = 1;
7897 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7898 Boolean canAdjudicate = !appData.icsActive;
7900 // most tests only when we understand the game, i.e. legality-checking on
7901 if( appData.testLegality )
7902 { /* [HGM] Some more adjudications for obstinate engines */
7903 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7904 static int moveCount = 6;
7906 char *reason = NULL;
7908 /* Count what is on board. */
7909 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7911 /* Some material-based adjudications that have to be made before stalemate test */
7912 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7913 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7914 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7915 if(canAdjudicate && appData.checkMates) {
7917 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7918 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7919 "Xboard adjudication: King destroyed", GE_XBOARD );
7924 /* Bare King in Shatranj (loses) or Losers (wins) */
7925 if( nrW == 1 || nrB == 1) {
7926 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7927 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7928 if(canAdjudicate && appData.checkMates) {
7930 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7931 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7932 "Xboard adjudication: Bare king", GE_XBOARD );
7936 if( gameInfo.variant == VariantShatranj && --bare < 0)
7938 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7939 if(canAdjudicate && appData.checkMates) {
7940 /* but only adjudicate if adjudication enabled */
7942 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7943 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7944 "Xboard adjudication: Bare king", GE_XBOARD );
7951 // don't wait for engine to announce game end if we can judge ourselves
7952 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7954 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7955 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7956 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7957 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7960 reason = "Xboard adjudication: 3rd check";
7961 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7971 reason = "Xboard adjudication: Stalemate";
7972 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7973 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7974 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7975 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7976 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7977 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7978 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7979 EP_CHECKMATE : EP_WINS);
7980 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7981 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7985 reason = "Xboard adjudication: Checkmate";
7986 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7987 if(gameInfo.variant == VariantShogi) {
7988 if(forwardMostMove > backwardMostMove
7989 && moveList[forwardMostMove-1][1] == '@'
7990 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7991 reason = "XBoard adjudication: pawn-drop mate";
7992 boards[forwardMostMove][EP_STATUS] = EP_WINS;
7998 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8000 result = GameIsDrawn; break;
8002 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8004 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8008 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8010 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8011 GameEnds( result, reason, GE_XBOARD );
8015 /* Next absolutely insufficient mating material. */
8016 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8017 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8018 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8020 /* always flag draws, for judging claims */
8021 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8023 if(canAdjudicate && appData.materialDraws) {
8024 /* but only adjudicate them if adjudication enabled */
8025 if(engineOpponent) {
8026 SendToProgram("force\n", engineOpponent); // suppress reply
8027 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8029 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8034 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8035 if(gameInfo.variant == VariantXiangqi ?
8036 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8038 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8039 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8040 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8041 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8043 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8044 { /* if the first 3 moves do not show a tactical win, declare draw */
8045 if(engineOpponent) {
8046 SendToProgram("force\n", engineOpponent); // suppress reply
8047 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8049 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8052 } else moveCount = 6;
8055 // Repetition draws and 50-move rule can be applied independently of legality testing
8057 /* Check for rep-draws */
8059 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8060 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8061 for(k = forwardMostMove-2;
8062 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8063 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8064 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8067 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8068 /* compare castling rights */
8069 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8070 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8071 rights++; /* King lost rights, while rook still had them */
8072 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8073 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8074 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8075 rights++; /* but at least one rook lost them */
8077 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8078 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8080 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8081 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8082 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8085 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8086 && appData.drawRepeats > 1) {
8087 /* adjudicate after user-specified nr of repeats */
8088 int result = GameIsDrawn;
8089 char *details = "XBoard adjudication: repetition draw";
8090 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8091 // [HGM] xiangqi: check for forbidden perpetuals
8092 int m, ourPerpetual = 1, hisPerpetual = 1;
8093 for(m=forwardMostMove; m>k; m-=2) {
8094 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8095 ourPerpetual = 0; // the current mover did not always check
8096 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8097 hisPerpetual = 0; // the opponent did not always check
8099 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8100 ourPerpetual, hisPerpetual);
8101 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8102 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8103 details = "Xboard adjudication: perpetual checking";
8105 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8106 break; // (or we would have caught him before). Abort repetition-checking loop.
8108 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8109 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8111 details = "Xboard adjudication: repetition";
8113 } else // it must be XQ
8114 // Now check for perpetual chases
8115 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8116 hisPerpetual = PerpetualChase(k, forwardMostMove);
8117 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8118 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8119 static char resdet[MSG_SIZ];
8120 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8122 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8124 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8125 break; // Abort repetition-checking loop.
8127 // if neither of us is checking or chasing all the time, or both are, it is draw
8129 if(engineOpponent) {
8130 SendToProgram("force\n", engineOpponent); // suppress reply
8131 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8133 GameEnds( result, details, GE_XBOARD );
8136 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8137 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8141 /* Now we test for 50-move draws. Determine ply count */
8142 count = forwardMostMove;
8143 /* look for last irreversble move */
8144 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8146 /* if we hit starting position, add initial plies */
8147 if( count == backwardMostMove )
8148 count -= initialRulePlies;
8149 count = forwardMostMove - count;
8150 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8151 // adjust reversible move counter for checks in Xiangqi
8152 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8153 if(i < backwardMostMove) i = backwardMostMove;
8154 while(i <= forwardMostMove) {
8155 lastCheck = inCheck; // check evasion does not count
8156 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8157 if(inCheck || lastCheck) count--; // check does not count
8162 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8163 /* this is used to judge if draw claims are legal */
8164 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8165 if(engineOpponent) {
8166 SendToProgram("force\n", engineOpponent); // suppress reply
8167 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8169 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8173 /* if draw offer is pending, treat it as a draw claim
8174 * when draw condition present, to allow engines a way to
8175 * claim draws before making their move to avoid a race
8176 * condition occurring after their move
8178 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8180 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8181 p = "Draw claim: 50-move rule";
8182 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8183 p = "Draw claim: 3-fold repetition";
8184 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8185 p = "Draw claim: insufficient mating material";
8186 if( p != NULL && canAdjudicate) {
8187 if(engineOpponent) {
8188 SendToProgram("force\n", engineOpponent); // suppress reply
8189 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8191 GameEnds( GameIsDrawn, p, GE_XBOARD );
8196 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8197 if(engineOpponent) {
8198 SendToProgram("force\n", engineOpponent); // suppress reply
8199 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8201 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8208 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8209 { // [HGM] book: this routine intercepts moves to simulate book replies
8210 char *bookHit = NULL;
8212 //first determine if the incoming move brings opponent into his book
8213 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8214 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8215 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8216 if(bookHit != NULL && !cps->bookSuspend) {
8217 // make sure opponent is not going to reply after receiving move to book position
8218 SendToProgram("force\n", cps);
8219 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8221 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8222 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8223 // now arrange restart after book miss
8225 // after a book hit we never send 'go', and the code after the call to this routine
8226 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8227 char buf[MSG_SIZ], *move = bookHit;
8229 int fromX, fromY, toX, toY;
8233 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8234 &fromX, &fromY, &toX, &toY, &promoChar)) {
8235 (void) CoordsToAlgebraic(boards[forwardMostMove],
8236 PosFlags(forwardMostMove),
8237 fromY, fromX, toY, toX, promoChar, move);
8239 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8243 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8244 SendToProgram(buf, cps);
8245 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8246 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8247 SendToProgram("go\n", cps);
8248 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8249 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8250 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8251 SendToProgram("go\n", cps);
8252 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8254 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8258 LoadError (char *errmess, ChessProgramState *cps)
8259 { // unloads engine and switches back to -ncp mode if it was first
8260 if(cps->initDone) return FALSE;
8261 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8262 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8265 appData.noChessProgram = TRUE;
8266 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8267 gameMode = BeginningOfGame; ModeHighlight();
8270 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8271 DisplayMessage("", ""); // erase waiting message
8272 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8277 ChessProgramState *savedState;
8279 DeferredBookMove (void)
8281 if(savedState->lastPing != savedState->lastPong)
8282 ScheduleDelayedEvent(DeferredBookMove, 10);
8284 HandleMachineMove(savedMessage, savedState);
8287 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8288 static ChessProgramState *stalledEngine;
8289 static char stashedInputMove[MSG_SIZ];
8292 HandleMachineMove (char *message, ChessProgramState *cps)
8294 static char firstLeg[20];
8295 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8296 char realname[MSG_SIZ];
8297 int fromX, fromY, toX, toY;
8299 char promoChar, roar;
8301 int machineWhite, oldError;
8304 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8305 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8306 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8307 DisplayError(_("Invalid pairing from pairing engine"), 0);
8310 pairingReceived = 1;
8312 return; // Skim the pairing messages here.
8315 oldError = cps->userError; cps->userError = 0;
8317 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8319 * Kludge to ignore BEL characters
8321 while (*message == '\007') message++;
8324 * [HGM] engine debug message: ignore lines starting with '#' character
8326 if(cps->debug && *message == '#') return;
8329 * Look for book output
8331 if (cps == &first && bookRequested) {
8332 if (message[0] == '\t' || message[0] == ' ') {
8333 /* Part of the book output is here; append it */
8334 strcat(bookOutput, message);
8335 strcat(bookOutput, " \n");
8337 } else if (bookOutput[0] != NULLCHAR) {
8338 /* All of book output has arrived; display it */
8339 char *p = bookOutput;
8340 while (*p != NULLCHAR) {
8341 if (*p == '\t') *p = ' ';
8344 DisplayInformation(bookOutput);
8345 bookRequested = FALSE;
8346 /* Fall through to parse the current output */
8351 * Look for machine move.
8353 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8354 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8356 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8357 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8358 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8359 stalledEngine = cps;
8360 if(appData.ponderNextMove) { // bring opponent out of ponder
8361 if(gameMode == TwoMachinesPlay) {
8362 if(cps->other->pause)
8363 PauseEngine(cps->other);
8365 SendToProgram("easy\n", cps->other);
8372 /* This method is only useful on engines that support ping */
8373 if (cps->lastPing != cps->lastPong) {
8374 if (gameMode == BeginningOfGame) {
8375 /* Extra move from before last new; ignore */
8376 if (appData.debugMode) {
8377 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8380 if (appData.debugMode) {
8381 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8382 cps->which, gameMode);
8385 SendToProgram("undo\n", cps);
8391 case BeginningOfGame:
8392 /* Extra move from before last reset; ignore */
8393 if (appData.debugMode) {
8394 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8401 /* Extra move after we tried to stop. The mode test is
8402 not a reliable way of detecting this problem, but it's
8403 the best we can do on engines that don't support ping.
8405 if (appData.debugMode) {
8406 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8407 cps->which, gameMode);
8409 SendToProgram("undo\n", cps);
8412 case MachinePlaysWhite:
8413 case IcsPlayingWhite:
8414 machineWhite = TRUE;
8417 case MachinePlaysBlack:
8418 case IcsPlayingBlack:
8419 machineWhite = FALSE;
8422 case TwoMachinesPlay:
8423 machineWhite = (cps->twoMachinesColor[0] == 'w');
8426 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8427 if (appData.debugMode) {
8429 "Ignoring move out of turn by %s, gameMode %d"
8430 ", forwardMost %d\n",
8431 cps->which, gameMode, forwardMostMove);
8436 if(cps->alphaRank) AlphaRank(machineMove, 4);
8438 // [HGM] lion: (some very limited) support for Alien protocol
8440 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8441 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8443 } else if(firstLeg[0]) { // there was a previous leg;
8444 // only support case where same piece makes two step (and don't even test that!)
8445 char buf[20], *p = machineMove+1, *q = buf+1, f;
8446 safeStrCpy(buf, machineMove, 20);
8447 while(isdigit(*q)) q++; // find start of to-square
8448 safeStrCpy(machineMove, firstLeg, 20);
8449 while(isdigit(*p)) p++;
8450 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8451 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8452 firstLeg[0] = NULLCHAR;
8455 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8456 &fromX, &fromY, &toX, &toY, &promoChar)) {
8457 /* Machine move could not be parsed; ignore it. */
8458 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8459 machineMove, _(cps->which));
8460 DisplayMoveError(buf1);
8461 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8462 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8463 if (gameMode == TwoMachinesPlay) {
8464 GameEnds(machineWhite ? BlackWins : WhiteWins,
8470 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8471 /* So we have to redo legality test with true e.p. status here, */
8472 /* to make sure an illegal e.p. capture does not slip through, */
8473 /* to cause a forfeit on a justified illegal-move complaint */
8474 /* of the opponent. */
8475 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8477 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8478 fromY, fromX, toY, toX, promoChar);
8479 if(moveType == IllegalMove) {
8480 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8481 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8482 GameEnds(machineWhite ? BlackWins : WhiteWins,
8485 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8486 /* [HGM] Kludge to handle engines that send FRC-style castling
8487 when they shouldn't (like TSCP-Gothic) */
8489 case WhiteASideCastleFR:
8490 case BlackASideCastleFR:
8492 currentMoveString[2]++;
8494 case WhiteHSideCastleFR:
8495 case BlackHSideCastleFR:
8497 currentMoveString[2]--;
8499 default: ; // nothing to do, but suppresses warning of pedantic compilers
8502 hintRequested = FALSE;
8503 lastHint[0] = NULLCHAR;
8504 bookRequested = FALSE;
8505 /* Program may be pondering now */
8506 cps->maybeThinking = TRUE;
8507 if (cps->sendTime == 2) cps->sendTime = 1;
8508 if (cps->offeredDraw) cps->offeredDraw--;
8510 /* [AS] Save move info*/
8511 pvInfoList[ forwardMostMove ].score = programStats.score;
8512 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8513 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8515 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8517 /* Test suites abort the 'game' after one move */
8518 if(*appData.finger) {
8520 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8521 if(!f) f = fopen(appData.finger, "w");
8522 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8523 else { DisplayFatalError("Bad output file", errno, 0); return; }
8525 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8528 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8529 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8532 while( count < adjudicateLossPlies ) {
8533 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8536 score = -score; /* Flip score for winning side */
8539 if( score > adjudicateLossThreshold ) {
8546 if( count >= adjudicateLossPlies ) {
8547 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8549 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8550 "Xboard adjudication",
8557 if(Adjudicate(cps)) {
8558 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8559 return; // [HGM] adjudicate: for all automatic game ends
8563 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8565 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8566 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8568 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8570 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8572 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8573 char buf[3*MSG_SIZ];
8575 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8576 programStats.score / 100.,
8578 programStats.time / 100.,
8579 (unsigned int)programStats.nodes,
8580 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8581 programStats.movelist);
8583 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8588 /* [AS] Clear stats for next move */
8589 ClearProgramStats();
8590 thinkOutput[0] = NULLCHAR;
8591 hiddenThinkOutputState = 0;
8594 if (gameMode == TwoMachinesPlay) {
8595 /* [HGM] relaying draw offers moved to after reception of move */
8596 /* and interpreting offer as claim if it brings draw condition */
8597 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8598 SendToProgram("draw\n", cps->other);
8600 if (cps->other->sendTime) {
8601 SendTimeRemaining(cps->other,
8602 cps->other->twoMachinesColor[0] == 'w');
8604 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8605 if (firstMove && !bookHit) {
8607 if (cps->other->useColors) {
8608 SendToProgram(cps->other->twoMachinesColor, cps->other);
8610 SendToProgram("go\n", cps->other);
8612 cps->other->maybeThinking = TRUE;
8615 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8617 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8619 if (!pausing && appData.ringBellAfterMoves) {
8620 if(!roar) RingBell();
8624 * Reenable menu items that were disabled while
8625 * machine was thinking
8627 if (gameMode != TwoMachinesPlay)
8628 SetUserThinkingEnables();
8630 // [HGM] book: after book hit opponent has received move and is now in force mode
8631 // force the book reply into it, and then fake that it outputted this move by jumping
8632 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8634 static char bookMove[MSG_SIZ]; // a bit generous?
8636 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8637 strcat(bookMove, bookHit);
8640 programStats.nodes = programStats.depth = programStats.time =
8641 programStats.score = programStats.got_only_move = 0;
8642 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8644 if(cps->lastPing != cps->lastPong) {
8645 savedMessage = message; // args for deferred call
8647 ScheduleDelayedEvent(DeferredBookMove, 10);
8656 /* Set special modes for chess engines. Later something general
8657 * could be added here; for now there is just one kludge feature,
8658 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8659 * when "xboard" is given as an interactive command.
8661 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8662 cps->useSigint = FALSE;
8663 cps->useSigterm = FALSE;
8665 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8666 ParseFeatures(message+8, cps);
8667 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8670 if (!strncmp(message, "setup ", 6) &&
8671 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8672 ) { // [HGM] allow first engine to define opening position
8673 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8674 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8676 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8677 if(startedFromSetupPosition) return;
8678 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8680 while(message[s] && message[s++] != ' ');
8681 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8682 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8683 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8684 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8685 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8686 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8689 ParseFEN(boards[0], &dummy, message+s, FALSE);
8690 DrawPosition(TRUE, boards[0]);
8691 startedFromSetupPosition = TRUE;
8694 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8695 * want this, I was asked to put it in, and obliged.
8697 if (!strncmp(message, "setboard ", 9)) {
8698 Board initial_position;
8700 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8702 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8703 DisplayError(_("Bad FEN received from engine"), 0);
8707 CopyBoard(boards[0], initial_position);
8708 initialRulePlies = FENrulePlies;
8709 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8710 else gameMode = MachinePlaysBlack;
8711 DrawPosition(FALSE, boards[currentMove]);
8717 * Look for communication commands
8719 if (!strncmp(message, "telluser ", 9)) {
8720 if(message[9] == '\\' && message[10] == '\\')
8721 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8723 DisplayNote(message + 9);
8726 if (!strncmp(message, "tellusererror ", 14)) {
8728 if(message[14] == '\\' && message[15] == '\\')
8729 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8731 DisplayError(message + 14, 0);
8734 if (!strncmp(message, "tellopponent ", 13)) {
8735 if (appData.icsActive) {
8737 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8741 DisplayNote(message + 13);
8745 if (!strncmp(message, "tellothers ", 11)) {
8746 if (appData.icsActive) {
8748 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8751 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8754 if (!strncmp(message, "tellall ", 8)) {
8755 if (appData.icsActive) {
8757 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8761 DisplayNote(message + 8);
8765 if (strncmp(message, "warning", 7) == 0) {
8766 /* Undocumented feature, use tellusererror in new code */
8767 DisplayError(message, 0);
8770 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8771 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8772 strcat(realname, " query");
8773 AskQuestion(realname, buf2, buf1, cps->pr);
8776 /* Commands from the engine directly to ICS. We don't allow these to be
8777 * sent until we are logged on. Crafty kibitzes have been known to
8778 * interfere with the login process.
8781 if (!strncmp(message, "tellics ", 8)) {
8782 SendToICS(message + 8);
8786 if (!strncmp(message, "tellicsnoalias ", 15)) {
8787 SendToICS(ics_prefix);
8788 SendToICS(message + 15);
8792 /* The following are for backward compatibility only */
8793 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8794 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8795 SendToICS(ics_prefix);
8801 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8804 if(!strncmp(message, "highlight ", 10)) {
8805 if(appData.testLegality && appData.markers) return;
8806 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8809 if(!strncmp(message, "click ", 6)) {
8810 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8811 if(appData.testLegality || !appData.oneClick) return;
8812 sscanf(message+6, "%c%d%c", &f, &y, &c);
8813 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8814 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8815 x = x*squareSize + (x+1)*lineGap + squareSize/2;
8816 y = y*squareSize + (y+1)*lineGap + squareSize/2;
8817 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8818 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8819 LeftClick(Release, lastLeftX, lastLeftY);
8820 controlKey = (c == ',');
8821 LeftClick(Press, x, y);
8822 LeftClick(Release, x, y);
8823 first.highlight = f;
8827 * If the move is illegal, cancel it and redraw the board.
8828 * Also deal with other error cases. Matching is rather loose
8829 * here to accommodate engines written before the spec.
8831 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8832 strncmp(message, "Error", 5) == 0) {
8833 if (StrStr(message, "name") ||
8834 StrStr(message, "rating") || StrStr(message, "?") ||
8835 StrStr(message, "result") || StrStr(message, "board") ||
8836 StrStr(message, "bk") || StrStr(message, "computer") ||
8837 StrStr(message, "variant") || StrStr(message, "hint") ||
8838 StrStr(message, "random") || StrStr(message, "depth") ||
8839 StrStr(message, "accepted")) {
8842 if (StrStr(message, "protover")) {
8843 /* Program is responding to input, so it's apparently done
8844 initializing, and this error message indicates it is
8845 protocol version 1. So we don't need to wait any longer
8846 for it to initialize and send feature commands. */
8847 FeatureDone(cps, 1);
8848 cps->protocolVersion = 1;
8851 cps->maybeThinking = FALSE;
8853 if (StrStr(message, "draw")) {
8854 /* Program doesn't have "draw" command */
8855 cps->sendDrawOffers = 0;
8858 if (cps->sendTime != 1 &&
8859 (StrStr(message, "time") || StrStr(message, "otim"))) {
8860 /* Program apparently doesn't have "time" or "otim" command */
8864 if (StrStr(message, "analyze")) {
8865 cps->analysisSupport = FALSE;
8866 cps->analyzing = FALSE;
8867 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8868 EditGameEvent(); // [HGM] try to preserve loaded game
8869 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8870 DisplayError(buf2, 0);
8873 if (StrStr(message, "(no matching move)st")) {
8874 /* Special kludge for GNU Chess 4 only */
8875 cps->stKludge = TRUE;
8876 SendTimeControl(cps, movesPerSession, timeControl,
8877 timeIncrement, appData.searchDepth,
8881 if (StrStr(message, "(no matching move)sd")) {
8882 /* Special kludge for GNU Chess 4 only */
8883 cps->sdKludge = TRUE;
8884 SendTimeControl(cps, movesPerSession, timeControl,
8885 timeIncrement, appData.searchDepth,
8889 if (!StrStr(message, "llegal")) {
8892 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8893 gameMode == IcsIdle) return;
8894 if (forwardMostMove <= backwardMostMove) return;
8895 if (pausing) PauseEvent();
8896 if(appData.forceIllegal) {
8897 // [HGM] illegal: machine refused move; force position after move into it
8898 SendToProgram("force\n", cps);
8899 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8900 // we have a real problem now, as SendBoard will use the a2a3 kludge
8901 // when black is to move, while there might be nothing on a2 or black
8902 // might already have the move. So send the board as if white has the move.
8903 // But first we must change the stm of the engine, as it refused the last move
8904 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8905 if(WhiteOnMove(forwardMostMove)) {
8906 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8907 SendBoard(cps, forwardMostMove); // kludgeless board
8909 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8910 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8911 SendBoard(cps, forwardMostMove+1); // kludgeless board
8913 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8914 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8915 gameMode == TwoMachinesPlay)
8916 SendToProgram("go\n", cps);
8919 if (gameMode == PlayFromGameFile) {
8920 /* Stop reading this game file */
8921 gameMode = EditGame;
8924 /* [HGM] illegal-move claim should forfeit game when Xboard */
8925 /* only passes fully legal moves */
8926 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8927 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8928 "False illegal-move claim", GE_XBOARD );
8929 return; // do not take back move we tested as valid
8931 currentMove = forwardMostMove-1;
8932 DisplayMove(currentMove-1); /* before DisplayMoveError */
8933 SwitchClocks(forwardMostMove-1); // [HGM] race
8934 DisplayBothClocks();
8935 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8936 parseList[currentMove], _(cps->which));
8937 DisplayMoveError(buf1);
8938 DrawPosition(FALSE, boards[currentMove]);
8940 SetUserThinkingEnables();
8943 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8944 /* Program has a broken "time" command that
8945 outputs a string not ending in newline.
8951 * If chess program startup fails, exit with an error message.
8952 * Attempts to recover here are futile. [HGM] Well, we try anyway
8954 if ((StrStr(message, "unknown host") != NULL)
8955 || (StrStr(message, "No remote directory") != NULL)
8956 || (StrStr(message, "not found") != NULL)
8957 || (StrStr(message, "No such file") != NULL)
8958 || (StrStr(message, "can't alloc") != NULL)
8959 || (StrStr(message, "Permission denied") != NULL)) {
8961 cps->maybeThinking = FALSE;
8962 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8963 _(cps->which), cps->program, cps->host, message);
8964 RemoveInputSource(cps->isr);
8965 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8966 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8967 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8973 * Look for hint output
8975 if (sscanf(message, "Hint: %s", buf1) == 1) {
8976 if (cps == &first && hintRequested) {
8977 hintRequested = FALSE;
8978 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8979 &fromX, &fromY, &toX, &toY, &promoChar)) {
8980 (void) CoordsToAlgebraic(boards[forwardMostMove],
8981 PosFlags(forwardMostMove),
8982 fromY, fromX, toY, toX, promoChar, buf1);
8983 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8984 DisplayInformation(buf2);
8986 /* Hint move could not be parsed!? */
8987 snprintf(buf2, sizeof(buf2),
8988 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8989 buf1, _(cps->which));
8990 DisplayError(buf2, 0);
8993 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8999 * Ignore other messages if game is not in progress
9001 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9002 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9005 * look for win, lose, draw, or draw offer
9007 if (strncmp(message, "1-0", 3) == 0) {
9008 char *p, *q, *r = "";
9009 p = strchr(message, '{');
9017 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9019 } else if (strncmp(message, "0-1", 3) == 0) {
9020 char *p, *q, *r = "";
9021 p = strchr(message, '{');
9029 /* Kludge for Arasan 4.1 bug */
9030 if (strcmp(r, "Black resigns") == 0) {
9031 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9034 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9036 } else if (strncmp(message, "1/2", 3) == 0) {
9037 char *p, *q, *r = "";
9038 p = strchr(message, '{');
9047 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9050 } else if (strncmp(message, "White resign", 12) == 0) {
9051 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9053 } else if (strncmp(message, "Black resign", 12) == 0) {
9054 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9056 } else if (strncmp(message, "White matches", 13) == 0 ||
9057 strncmp(message, "Black matches", 13) == 0 ) {
9058 /* [HGM] ignore GNUShogi noises */
9060 } else if (strncmp(message, "White", 5) == 0 &&
9061 message[5] != '(' &&
9062 StrStr(message, "Black") == NULL) {
9063 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9065 } else if (strncmp(message, "Black", 5) == 0 &&
9066 message[5] != '(') {
9067 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9069 } else if (strcmp(message, "resign") == 0 ||
9070 strcmp(message, "computer resigns") == 0) {
9072 case MachinePlaysBlack:
9073 case IcsPlayingBlack:
9074 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9076 case MachinePlaysWhite:
9077 case IcsPlayingWhite:
9078 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9080 case TwoMachinesPlay:
9081 if (cps->twoMachinesColor[0] == 'w')
9082 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9084 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9091 } else if (strncmp(message, "opponent mates", 14) == 0) {
9093 case MachinePlaysBlack:
9094 case IcsPlayingBlack:
9095 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9097 case MachinePlaysWhite:
9098 case IcsPlayingWhite:
9099 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9101 case TwoMachinesPlay:
9102 if (cps->twoMachinesColor[0] == 'w')
9103 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9105 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9112 } else if (strncmp(message, "computer mates", 14) == 0) {
9114 case MachinePlaysBlack:
9115 case IcsPlayingBlack:
9116 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9118 case MachinePlaysWhite:
9119 case IcsPlayingWhite:
9120 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9122 case TwoMachinesPlay:
9123 if (cps->twoMachinesColor[0] == 'w')
9124 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9126 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9133 } else if (strncmp(message, "checkmate", 9) == 0) {
9134 if (WhiteOnMove(forwardMostMove)) {
9135 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9137 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9140 } else if (strstr(message, "Draw") != NULL ||
9141 strstr(message, "game is a draw") != NULL) {
9142 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9144 } else if (strstr(message, "offer") != NULL &&
9145 strstr(message, "draw") != NULL) {
9147 if (appData.zippyPlay && first.initDone) {
9148 /* Relay offer to ICS */
9149 SendToICS(ics_prefix);
9150 SendToICS("draw\n");
9153 cps->offeredDraw = 2; /* valid until this engine moves twice */
9154 if (gameMode == TwoMachinesPlay) {
9155 if (cps->other->offeredDraw) {
9156 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9157 /* [HGM] in two-machine mode we delay relaying draw offer */
9158 /* until after we also have move, to see if it is really claim */
9160 } else if (gameMode == MachinePlaysWhite ||
9161 gameMode == MachinePlaysBlack) {
9162 if (userOfferedDraw) {
9163 DisplayInformation(_("Machine accepts your draw offer"));
9164 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9166 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9173 * Look for thinking output
9175 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9176 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9178 int plylev, mvleft, mvtot, curscore, time;
9179 char mvname[MOVE_LEN];
9183 int prefixHint = FALSE;
9184 mvname[0] = NULLCHAR;
9187 case MachinePlaysBlack:
9188 case IcsPlayingBlack:
9189 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9191 case MachinePlaysWhite:
9192 case IcsPlayingWhite:
9193 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9198 case IcsObserving: /* [DM] icsEngineAnalyze */
9199 if (!appData.icsEngineAnalyze) ignore = TRUE;
9201 case TwoMachinesPlay:
9202 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9212 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9214 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9215 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9217 if (plyext != ' ' && plyext != '\t') {
9221 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9222 if( cps->scoreIsAbsolute &&
9223 ( gameMode == MachinePlaysBlack ||
9224 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9225 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9226 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9227 !WhiteOnMove(currentMove)
9230 curscore = -curscore;
9233 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9235 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9238 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9239 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9240 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9241 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9242 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9243 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9247 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9248 DisplayError(_("failed writing PV"), 0);
9251 tempStats.depth = plylev;
9252 tempStats.nodes = nodes;
9253 tempStats.time = time;
9254 tempStats.score = curscore;
9255 tempStats.got_only_move = 0;
9257 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9260 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9261 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9262 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9263 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9264 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9265 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9266 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9267 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9270 /* Buffer overflow protection */
9271 if (pv[0] != NULLCHAR) {
9272 if (strlen(pv) >= sizeof(tempStats.movelist)
9273 && appData.debugMode) {
9275 "PV is too long; using the first %u bytes.\n",
9276 (unsigned) sizeof(tempStats.movelist) - 1);
9279 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9281 sprintf(tempStats.movelist, " no PV\n");
9284 if (tempStats.seen_stat) {
9285 tempStats.ok_to_send = 1;
9288 if (strchr(tempStats.movelist, '(') != NULL) {
9289 tempStats.line_is_book = 1;
9290 tempStats.nr_moves = 0;
9291 tempStats.moves_left = 0;
9293 tempStats.line_is_book = 0;
9296 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9297 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9299 SendProgramStatsToFrontend( cps, &tempStats );
9302 [AS] Protect the thinkOutput buffer from overflow... this
9303 is only useful if buf1 hasn't overflowed first!
9305 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9307 (gameMode == TwoMachinesPlay ?
9308 ToUpper(cps->twoMachinesColor[0]) : ' '),
9309 ((double) curscore) / 100.0,
9310 prefixHint ? lastHint : "",
9311 prefixHint ? " " : "" );
9313 if( buf1[0] != NULLCHAR ) {
9314 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9316 if( strlen(pv) > max_len ) {
9317 if( appData.debugMode) {
9318 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9320 pv[max_len+1] = '\0';
9323 strcat( thinkOutput, pv);
9326 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9327 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9328 DisplayMove(currentMove - 1);
9332 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9333 /* crafty (9.25+) says "(only move) <move>"
9334 * if there is only 1 legal move
9336 sscanf(p, "(only move) %s", buf1);
9337 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9338 sprintf(programStats.movelist, "%s (only move)", buf1);
9339 programStats.depth = 1;
9340 programStats.nr_moves = 1;
9341 programStats.moves_left = 1;
9342 programStats.nodes = 1;
9343 programStats.time = 1;
9344 programStats.got_only_move = 1;
9346 /* Not really, but we also use this member to
9347 mean "line isn't going to change" (Crafty
9348 isn't searching, so stats won't change) */
9349 programStats.line_is_book = 1;
9351 SendProgramStatsToFrontend( cps, &programStats );
9353 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9354 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9355 DisplayMove(currentMove - 1);
9358 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9359 &time, &nodes, &plylev, &mvleft,
9360 &mvtot, mvname) >= 5) {
9361 /* The stat01: line is from Crafty (9.29+) in response
9362 to the "." command */
9363 programStats.seen_stat = 1;
9364 cps->maybeThinking = TRUE;
9366 if (programStats.got_only_move || !appData.periodicUpdates)
9369 programStats.depth = plylev;
9370 programStats.time = time;
9371 programStats.nodes = nodes;
9372 programStats.moves_left = mvleft;
9373 programStats.nr_moves = mvtot;
9374 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9375 programStats.ok_to_send = 1;
9376 programStats.movelist[0] = '\0';
9378 SendProgramStatsToFrontend( cps, &programStats );
9382 } else if (strncmp(message,"++",2) == 0) {
9383 /* Crafty 9.29+ outputs this */
9384 programStats.got_fail = 2;
9387 } else if (strncmp(message,"--",2) == 0) {
9388 /* Crafty 9.29+ outputs this */
9389 programStats.got_fail = 1;
9392 } else if (thinkOutput[0] != NULLCHAR &&
9393 strncmp(message, " ", 4) == 0) {
9394 unsigned message_len;
9397 while (*p && *p == ' ') p++;
9399 message_len = strlen( p );
9401 /* [AS] Avoid buffer overflow */
9402 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9403 strcat(thinkOutput, " ");
9404 strcat(thinkOutput, p);
9407 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9408 strcat(programStats.movelist, " ");
9409 strcat(programStats.movelist, p);
9412 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9413 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9414 DisplayMove(currentMove - 1);
9422 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9423 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9425 ChessProgramStats cpstats;
9427 if (plyext != ' ' && plyext != '\t') {
9431 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9432 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9433 curscore = -curscore;
9436 cpstats.depth = plylev;
9437 cpstats.nodes = nodes;
9438 cpstats.time = time;
9439 cpstats.score = curscore;
9440 cpstats.got_only_move = 0;
9441 cpstats.movelist[0] = '\0';
9443 if (buf1[0] != NULLCHAR) {
9444 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9447 cpstats.ok_to_send = 0;
9448 cpstats.line_is_book = 0;
9449 cpstats.nr_moves = 0;
9450 cpstats.moves_left = 0;
9452 SendProgramStatsToFrontend( cps, &cpstats );
9459 /* Parse a game score from the character string "game", and
9460 record it as the history of the current game. The game
9461 score is NOT assumed to start from the standard position.
9462 The display is not updated in any way.
9465 ParseGameHistory (char *game)
9468 int fromX, fromY, toX, toY, boardIndex;
9473 if (appData.debugMode)
9474 fprintf(debugFP, "Parsing game history: %s\n", game);
9476 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9477 gameInfo.site = StrSave(appData.icsHost);
9478 gameInfo.date = PGNDate();
9479 gameInfo.round = StrSave("-");
9481 /* Parse out names of players */
9482 while (*game == ' ') game++;
9484 while (*game != ' ') *p++ = *game++;
9486 gameInfo.white = StrSave(buf);
9487 while (*game == ' ') game++;
9489 while (*game != ' ' && *game != '\n') *p++ = *game++;
9491 gameInfo.black = StrSave(buf);
9494 boardIndex = blackPlaysFirst ? 1 : 0;
9497 yyboardindex = boardIndex;
9498 moveType = (ChessMove) Myylex();
9500 case IllegalMove: /* maybe suicide chess, etc. */
9501 if (appData.debugMode) {
9502 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9503 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9504 setbuf(debugFP, NULL);
9506 case WhitePromotion:
9507 case BlackPromotion:
9508 case WhiteNonPromotion:
9509 case BlackNonPromotion:
9512 case WhiteCapturesEnPassant:
9513 case BlackCapturesEnPassant:
9514 case WhiteKingSideCastle:
9515 case WhiteQueenSideCastle:
9516 case BlackKingSideCastle:
9517 case BlackQueenSideCastle:
9518 case WhiteKingSideCastleWild:
9519 case WhiteQueenSideCastleWild:
9520 case BlackKingSideCastleWild:
9521 case BlackQueenSideCastleWild:
9523 case WhiteHSideCastleFR:
9524 case WhiteASideCastleFR:
9525 case BlackHSideCastleFR:
9526 case BlackASideCastleFR:
9528 fromX = currentMoveString[0] - AAA;
9529 fromY = currentMoveString[1] - ONE;
9530 toX = currentMoveString[2] - AAA;
9531 toY = currentMoveString[3] - ONE;
9532 promoChar = currentMoveString[4];
9536 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9537 fromX = moveType == WhiteDrop ?
9538 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9539 (int) CharToPiece(ToLower(currentMoveString[0]));
9541 toX = currentMoveString[2] - AAA;
9542 toY = currentMoveString[3] - ONE;
9543 promoChar = NULLCHAR;
9547 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9548 if (appData.debugMode) {
9549 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9550 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9551 setbuf(debugFP, NULL);
9553 DisplayError(buf, 0);
9555 case ImpossibleMove:
9557 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9558 if (appData.debugMode) {
9559 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9560 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9561 setbuf(debugFP, NULL);
9563 DisplayError(buf, 0);
9566 if (boardIndex < backwardMostMove) {
9567 /* Oops, gap. How did that happen? */
9568 DisplayError(_("Gap in move list"), 0);
9571 backwardMostMove = blackPlaysFirst ? 1 : 0;
9572 if (boardIndex > forwardMostMove) {
9573 forwardMostMove = boardIndex;
9577 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9578 strcat(parseList[boardIndex-1], " ");
9579 strcat(parseList[boardIndex-1], yy_text);
9591 case GameUnfinished:
9592 if (gameMode == IcsExamining) {
9593 if (boardIndex < backwardMostMove) {
9594 /* Oops, gap. How did that happen? */
9597 backwardMostMove = blackPlaysFirst ? 1 : 0;
9600 gameInfo.result = moveType;
9601 p = strchr(yy_text, '{');
9602 if (p == NULL) p = strchr(yy_text, '(');
9605 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9607 q = strchr(p, *p == '{' ? '}' : ')');
9608 if (q != NULL) *q = NULLCHAR;
9611 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9612 gameInfo.resultDetails = StrSave(p);
9615 if (boardIndex >= forwardMostMove &&
9616 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9617 backwardMostMove = blackPlaysFirst ? 1 : 0;
9620 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9621 fromY, fromX, toY, toX, promoChar,
9622 parseList[boardIndex]);
9623 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9624 /* currentMoveString is set as a side-effect of yylex */
9625 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9626 strcat(moveList[boardIndex], "\n");
9628 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9629 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9635 if(gameInfo.variant != VariantShogi)
9636 strcat(parseList[boardIndex - 1], "+");
9640 strcat(parseList[boardIndex - 1], "#");
9647 /* Apply a move to the given board */
9649 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9651 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9652 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9654 /* [HGM] compute & store e.p. status and castling rights for new position */
9655 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9657 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9658 oldEP = (signed char)board[EP_STATUS];
9659 board[EP_STATUS] = EP_NONE;
9661 if (fromY == DROP_RANK) {
9663 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9664 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9667 piece = board[toY][toX] = (ChessSquare) fromX;
9672 if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9673 victim = board[killY][killX],
9674 board[killY][killX] = EmptySquare,
9675 board[EP_STATUS] = EP_CAPTURE;
9677 if( board[toY][toX] != EmptySquare ) {
9678 board[EP_STATUS] = EP_CAPTURE;
9679 if( (fromX != toX || fromY != toY) && // not igui!
9680 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9681 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
9682 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9686 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9687 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9688 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9690 if( board[fromY][fromX] == WhitePawn ) {
9691 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9692 board[EP_STATUS] = EP_PAWN_MOVE;
9694 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9695 gameInfo.variant != VariantBerolina || toX < fromX)
9696 board[EP_STATUS] = toX | berolina;
9697 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9698 gameInfo.variant != VariantBerolina || toX > fromX)
9699 board[EP_STATUS] = toX;
9702 if( board[fromY][fromX] == BlackPawn ) {
9703 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9704 board[EP_STATUS] = EP_PAWN_MOVE;
9705 if( toY-fromY== -2) {
9706 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9707 gameInfo.variant != VariantBerolina || toX < fromX)
9708 board[EP_STATUS] = toX | berolina;
9709 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9710 gameInfo.variant != VariantBerolina || toX > fromX)
9711 board[EP_STATUS] = toX;
9715 for(i=0; i<nrCastlingRights; i++) {
9716 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9717 board[CASTLING][i] == toX && castlingRank[i] == toY
9718 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9721 if(gameInfo.variant == VariantSChess) { // update virginity
9722 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9723 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9724 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9725 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9728 if (fromX == toX && fromY == toY) return;
9730 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9731 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9732 if(gameInfo.variant == VariantKnightmate)
9733 king += (int) WhiteUnicorn - (int) WhiteKing;
9735 /* Code added by Tord: */
9736 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9737 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9738 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9739 board[fromY][fromX] = EmptySquare;
9740 board[toY][toX] = EmptySquare;
9741 if((toX > fromX) != (piece == WhiteRook)) {
9742 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9744 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9746 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9747 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9748 board[fromY][fromX] = EmptySquare;
9749 board[toY][toX] = EmptySquare;
9750 if((toX > fromX) != (piece == BlackRook)) {
9751 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9753 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9755 /* End of code added by Tord */
9757 } else if (board[fromY][fromX] == king
9758 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9759 && toY == fromY && toX > fromX+1) {
9760 board[fromY][fromX] = EmptySquare;
9761 board[toY][toX] = king;
9762 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9763 board[fromY][BOARD_RGHT-1] = EmptySquare;
9764 } else if (board[fromY][fromX] == king
9765 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9766 && toY == fromY && toX < fromX-1) {
9767 board[fromY][fromX] = EmptySquare;
9768 board[toY][toX] = king;
9769 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9770 board[fromY][BOARD_LEFT] = EmptySquare;
9771 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9772 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9773 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9775 /* white pawn promotion */
9776 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9777 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9778 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9779 board[fromY][fromX] = EmptySquare;
9780 } else if ((fromY >= BOARD_HEIGHT>>1)
9781 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9783 && gameInfo.variant != VariantXiangqi
9784 && gameInfo.variant != VariantBerolina
9785 && (board[fromY][fromX] == WhitePawn)
9786 && (board[toY][toX] == EmptySquare)) {
9787 board[fromY][fromX] = EmptySquare;
9788 board[toY][toX] = WhitePawn;
9789 captured = board[toY - 1][toX];
9790 board[toY - 1][toX] = EmptySquare;
9791 } else if ((fromY == BOARD_HEIGHT-4)
9793 && gameInfo.variant == VariantBerolina
9794 && (board[fromY][fromX] == WhitePawn)
9795 && (board[toY][toX] == EmptySquare)) {
9796 board[fromY][fromX] = EmptySquare;
9797 board[toY][toX] = WhitePawn;
9798 if(oldEP & EP_BEROLIN_A) {
9799 captured = board[fromY][fromX-1];
9800 board[fromY][fromX-1] = EmptySquare;
9801 }else{ captured = board[fromY][fromX+1];
9802 board[fromY][fromX+1] = EmptySquare;
9804 } else if (board[fromY][fromX] == king
9805 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9806 && toY == fromY && toX > fromX+1) {
9807 board[fromY][fromX] = EmptySquare;
9808 board[toY][toX] = king;
9809 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9810 board[fromY][BOARD_RGHT-1] = EmptySquare;
9811 } else if (board[fromY][fromX] == king
9812 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9813 && toY == fromY && toX < fromX-1) {
9814 board[fromY][fromX] = EmptySquare;
9815 board[toY][toX] = king;
9816 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9817 board[fromY][BOARD_LEFT] = EmptySquare;
9818 } else if (fromY == 7 && fromX == 3
9819 && board[fromY][fromX] == BlackKing
9820 && toY == 7 && toX == 5) {
9821 board[fromY][fromX] = EmptySquare;
9822 board[toY][toX] = BlackKing;
9823 board[fromY][7] = EmptySquare;
9824 board[toY][4] = BlackRook;
9825 } else if (fromY == 7 && fromX == 3
9826 && board[fromY][fromX] == BlackKing
9827 && toY == 7 && toX == 1) {
9828 board[fromY][fromX] = EmptySquare;
9829 board[toY][toX] = BlackKing;
9830 board[fromY][0] = EmptySquare;
9831 board[toY][2] = BlackRook;
9832 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9833 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9834 && toY < promoRank && promoChar
9836 /* black pawn promotion */
9837 board[toY][toX] = CharToPiece(ToLower(promoChar));
9838 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9839 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9840 board[fromY][fromX] = EmptySquare;
9841 } else if ((fromY < BOARD_HEIGHT>>1)
9842 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9844 && gameInfo.variant != VariantXiangqi
9845 && gameInfo.variant != VariantBerolina
9846 && (board[fromY][fromX] == BlackPawn)
9847 && (board[toY][toX] == EmptySquare)) {
9848 board[fromY][fromX] = EmptySquare;
9849 board[toY][toX] = BlackPawn;
9850 captured = board[toY + 1][toX];
9851 board[toY + 1][toX] = EmptySquare;
9852 } else if ((fromY == 3)
9854 && gameInfo.variant == VariantBerolina
9855 && (board[fromY][fromX] == BlackPawn)
9856 && (board[toY][toX] == EmptySquare)) {
9857 board[fromY][fromX] = EmptySquare;
9858 board[toY][toX] = BlackPawn;
9859 if(oldEP & EP_BEROLIN_A) {
9860 captured = board[fromY][fromX-1];
9861 board[fromY][fromX-1] = EmptySquare;
9862 }else{ captured = board[fromY][fromX+1];
9863 board[fromY][fromX+1] = EmptySquare;
9866 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9867 board[fromY][fromX] = EmptySquare;
9868 board[toY][toX] = piece;
9872 if (gameInfo.holdingsWidth != 0) {
9874 /* !!A lot more code needs to be written to support holdings */
9875 /* [HGM] OK, so I have written it. Holdings are stored in the */
9876 /* penultimate board files, so they are automaticlly stored */
9877 /* in the game history. */
9878 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9879 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9880 /* Delete from holdings, by decreasing count */
9881 /* and erasing image if necessary */
9882 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9883 if(p < (int) BlackPawn) { /* white drop */
9884 p -= (int)WhitePawn;
9885 p = PieceToNumber((ChessSquare)p);
9886 if(p >= gameInfo.holdingsSize) p = 0;
9887 if(--board[p][BOARD_WIDTH-2] <= 0)
9888 board[p][BOARD_WIDTH-1] = EmptySquare;
9889 if((int)board[p][BOARD_WIDTH-2] < 0)
9890 board[p][BOARD_WIDTH-2] = 0;
9891 } else { /* black drop */
9892 p -= (int)BlackPawn;
9893 p = PieceToNumber((ChessSquare)p);
9894 if(p >= gameInfo.holdingsSize) p = 0;
9895 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9896 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9897 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9898 board[BOARD_HEIGHT-1-p][1] = 0;
9901 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9902 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9903 /* [HGM] holdings: Add to holdings, if holdings exist */
9904 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9905 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9906 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9909 if (p >= (int) BlackPawn) {
9910 p -= (int)BlackPawn;
9911 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9912 /* in Shogi restore piece to its original first */
9913 captured = (ChessSquare) (DEMOTED captured);
9916 p = PieceToNumber((ChessSquare)p);
9917 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9918 board[p][BOARD_WIDTH-2]++;
9919 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9921 p -= (int)WhitePawn;
9922 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9923 captured = (ChessSquare) (DEMOTED captured);
9926 p = PieceToNumber((ChessSquare)p);
9927 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9928 board[BOARD_HEIGHT-1-p][1]++;
9929 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9932 } else if (gameInfo.variant == VariantAtomic) {
9933 if (captured != EmptySquare) {
9935 for (y = toY-1; y <= toY+1; y++) {
9936 for (x = toX-1; x <= toX+1; x++) {
9937 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9938 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9939 board[y][x] = EmptySquare;
9943 board[toY][toX] = EmptySquare;
9946 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9947 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9949 if(promoChar == '+') {
9950 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9951 board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9952 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9953 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9954 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9955 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9956 board[toY][toX] = newPiece;
9958 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9959 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9960 // [HGM] superchess: take promotion piece out of holdings
9961 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9962 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9963 if(!--board[k][BOARD_WIDTH-2])
9964 board[k][BOARD_WIDTH-1] = EmptySquare;
9966 if(!--board[BOARD_HEIGHT-1-k][1])
9967 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9972 /* Updates forwardMostMove */
9974 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9976 int x = toX, y = toY;
9977 char *s = parseList[forwardMostMove];
9978 ChessSquare p = boards[forwardMostMove][toY][toX];
9979 // forwardMostMove++; // [HGM] bare: moved downstream
9981 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
9982 (void) CoordsToAlgebraic(boards[forwardMostMove],
9983 PosFlags(forwardMostMove),
9984 fromY, fromX, y, x, promoChar,
9986 if(killX >= 0 && killY >= 0)
9987 sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
9989 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9990 int timeLeft; static int lastLoadFlag=0; int king, piece;
9991 piece = boards[forwardMostMove][fromY][fromX];
9992 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9993 if(gameInfo.variant == VariantKnightmate)
9994 king += (int) WhiteUnicorn - (int) WhiteKing;
9995 if(forwardMostMove == 0) {
9996 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9997 fprintf(serverMoves, "%s;", UserName());
9998 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9999 fprintf(serverMoves, "%s;", second.tidy);
10000 fprintf(serverMoves, "%s;", first.tidy);
10001 if(gameMode == MachinePlaysWhite)
10002 fprintf(serverMoves, "%s;", UserName());
10003 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10004 fprintf(serverMoves, "%s;", second.tidy);
10005 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10006 lastLoadFlag = loadFlag;
10008 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10009 // print castling suffix
10010 if( toY == fromY && piece == king ) {
10012 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10014 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10017 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10018 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10019 boards[forwardMostMove][toY][toX] == EmptySquare
10020 && fromX != toX && fromY != toY)
10021 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10022 // promotion suffix
10023 if(promoChar != NULLCHAR) {
10024 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10025 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10026 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10027 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10030 char buf[MOVE_LEN*2], *p; int len;
10031 fprintf(serverMoves, "/%d/%d",
10032 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10033 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10034 else timeLeft = blackTimeRemaining/1000;
10035 fprintf(serverMoves, "/%d", timeLeft);
10036 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10037 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10038 if(p = strchr(buf, '=')) *p = NULLCHAR;
10039 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10040 fprintf(serverMoves, "/%s", buf);
10042 fflush(serverMoves);
10045 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10046 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10049 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10050 if (commentList[forwardMostMove+1] != NULL) {
10051 free(commentList[forwardMostMove+1]);
10052 commentList[forwardMostMove+1] = NULL;
10054 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10055 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10056 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10057 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10058 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10059 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10060 adjustedClock = FALSE;
10061 gameInfo.result = GameUnfinished;
10062 if (gameInfo.resultDetails != NULL) {
10063 free(gameInfo.resultDetails);
10064 gameInfo.resultDetails = NULL;
10066 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10067 moveList[forwardMostMove - 1]);
10068 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10074 if(gameInfo.variant != VariantShogi)
10075 strcat(parseList[forwardMostMove - 1], "+");
10079 strcat(parseList[forwardMostMove - 1], "#");
10084 /* Updates currentMove if not pausing */
10086 ShowMove (int fromX, int fromY, int toX, int toY)
10088 int instant = (gameMode == PlayFromGameFile) ?
10089 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10090 if(appData.noGUI) return;
10091 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10093 if (forwardMostMove == currentMove + 1) {
10094 AnimateMove(boards[forwardMostMove - 1],
10095 fromX, fromY, toX, toY);
10098 currentMove = forwardMostMove;
10101 killX = killY = -1; // [HGM] lion: used up
10103 if (instant) return;
10105 DisplayMove(currentMove - 1);
10106 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10107 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10108 SetHighlights(fromX, fromY, toX, toY);
10111 DrawPosition(FALSE, boards[currentMove]);
10112 DisplayBothClocks();
10113 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10117 SendEgtPath (ChessProgramState *cps)
10118 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10119 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10121 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10124 char c, *q = name+1, *r, *s;
10126 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10127 while(*p && *p != ',') *q++ = *p++;
10128 *q++ = ':'; *q = 0;
10129 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10130 strcmp(name, ",nalimov:") == 0 ) {
10131 // take nalimov path from the menu-changeable option first, if it is defined
10132 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10133 SendToProgram(buf,cps); // send egtbpath command for nalimov
10135 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10136 (s = StrStr(appData.egtFormats, name)) != NULL) {
10137 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10138 s = r = StrStr(s, ":") + 1; // beginning of path info
10139 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10140 c = *r; *r = 0; // temporarily null-terminate path info
10141 *--q = 0; // strip of trailig ':' from name
10142 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10144 SendToProgram(buf,cps); // send egtbpath command for this format
10146 if(*p == ',') p++; // read away comma to position for next format name
10151 NonStandardBoardSize ()
10153 /* [HGM] Awkward testing. Should really be a table */
10154 int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10155 if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10156 if( gameInfo.variant == VariantXiangqi )
10157 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10158 if( gameInfo.variant == VariantShogi )
10159 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10160 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10161 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10162 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10163 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10164 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10165 if( gameInfo.variant == VariantCourier )
10166 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10167 if( gameInfo.variant == VariantSuper )
10168 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10169 if( gameInfo.variant == VariantGreat )
10170 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10171 if( gameInfo.variant == VariantSChess )
10172 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10173 if( gameInfo.variant == VariantGrand )
10174 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10175 if( gameInfo.variant == VariantChu )
10176 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
10181 InitChessProgram (ChessProgramState *cps, int setup)
10182 /* setup needed to setup FRC opening position */
10184 char buf[MSG_SIZ], b[MSG_SIZ];
10185 if (appData.noChessProgram) return;
10186 hintRequested = FALSE;
10187 bookRequested = FALSE;
10189 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10190 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10191 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10192 if(cps->memSize) { /* [HGM] memory */
10193 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10194 SendToProgram(buf, cps);
10196 SendEgtPath(cps); /* [HGM] EGT */
10197 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10198 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10199 SendToProgram(buf, cps);
10202 SendToProgram(cps->initString, cps);
10203 if (gameInfo.variant != VariantNormal &&
10204 gameInfo.variant != VariantLoadable
10205 /* [HGM] also send variant if board size non-standard */
10206 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10208 char *v = VariantName(gameInfo.variant);
10209 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10210 /* [HGM] in protocol 1 we have to assume all variants valid */
10211 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10212 DisplayFatalError(buf, 0, 1);
10216 if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10217 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10218 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10219 /* [HGM] varsize: try first if this defiant size variant is specifically known */
10220 if(StrStr(cps->variants, b) == NULL) {
10221 // specific sized variant not known, check if general sizing allowed
10222 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10223 if(StrStr(cps->variants, "boardsize") == NULL) {
10224 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10225 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10226 DisplayFatalError(buf, 0, 1);
10229 /* [HGM] here we really should compare with the maximum supported board size */
10232 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10233 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10234 SendToProgram(buf, cps);
10236 currentlyInitializedVariant = gameInfo.variant;
10238 /* [HGM] send opening position in FRC to first engine */
10240 SendToProgram("force\n", cps);
10242 /* engine is now in force mode! Set flag to wake it up after first move. */
10243 setboardSpoiledMachineBlack = 1;
10246 if (cps->sendICS) {
10247 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10248 SendToProgram(buf, cps);
10250 cps->maybeThinking = FALSE;
10251 cps->offeredDraw = 0;
10252 if (!appData.icsActive) {
10253 SendTimeControl(cps, movesPerSession, timeControl,
10254 timeIncrement, appData.searchDepth,
10257 if (appData.showThinking
10258 // [HGM] thinking: four options require thinking output to be sent
10259 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10261 SendToProgram("post\n", cps);
10263 SendToProgram("hard\n", cps);
10264 if (!appData.ponderNextMove) {
10265 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10266 it without being sure what state we are in first. "hard"
10267 is not a toggle, so that one is OK.
10269 SendToProgram("easy\n", cps);
10271 if (cps->usePing) {
10272 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10273 SendToProgram(buf, cps);
10275 cps->initDone = TRUE;
10276 ClearEngineOutputPane(cps == &second);
10281 ResendOptions (ChessProgramState *cps)
10282 { // send the stored value of the options
10285 Option *opt = cps->option;
10286 for(i=0; i<cps->nrOptions; i++, opt++) {
10287 switch(opt->type) {
10291 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10294 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10297 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10303 SendToProgram(buf, cps);
10308 StartChessProgram (ChessProgramState *cps)
10313 if (appData.noChessProgram) return;
10314 cps->initDone = FALSE;
10316 if (strcmp(cps->host, "localhost") == 0) {
10317 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10318 } else if (*appData.remoteShell == NULLCHAR) {
10319 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10321 if (*appData.remoteUser == NULLCHAR) {
10322 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10325 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10326 cps->host, appData.remoteUser, cps->program);
10328 err = StartChildProcess(buf, "", &cps->pr);
10332 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10333 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10334 if(cps != &first) return;
10335 appData.noChessProgram = TRUE;
10338 // DisplayFatalError(buf, err, 1);
10339 // cps->pr = NoProc;
10340 // cps->isr = NULL;
10344 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10345 if (cps->protocolVersion > 1) {
10346 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10347 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10348 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10349 cps->comboCnt = 0; // and values of combo boxes
10351 SendToProgram(buf, cps);
10352 if(cps->reload) ResendOptions(cps);
10354 SendToProgram("xboard\n", cps);
10359 TwoMachinesEventIfReady P((void))
10361 static int curMess = 0;
10362 if (first.lastPing != first.lastPong) {
10363 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10364 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10367 if (second.lastPing != second.lastPong) {
10368 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10369 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10372 DisplayMessage("", ""); curMess = 0;
10373 TwoMachinesEvent();
10377 MakeName (char *template)
10381 static char buf[MSG_SIZ];
10385 clock = time((time_t *)NULL);
10386 tm = localtime(&clock);
10388 while(*p++ = *template++) if(p[-1] == '%') {
10389 switch(*template++) {
10390 case 0: *p = 0; return buf;
10391 case 'Y': i = tm->tm_year+1900; break;
10392 case 'y': i = tm->tm_year-100; break;
10393 case 'M': i = tm->tm_mon+1; break;
10394 case 'd': i = tm->tm_mday; break;
10395 case 'h': i = tm->tm_hour; break;
10396 case 'm': i = tm->tm_min; break;
10397 case 's': i = tm->tm_sec; break;
10400 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10406 CountPlayers (char *p)
10409 while(p = strchr(p, '\n')) p++, n++; // count participants
10414 WriteTourneyFile (char *results, FILE *f)
10415 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10416 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10417 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10418 // create a file with tournament description
10419 fprintf(f, "-participants {%s}\n", appData.participants);
10420 fprintf(f, "-seedBase %d\n", appData.seedBase);
10421 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10422 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10423 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10424 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10425 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10426 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10427 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10428 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10429 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10430 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10431 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10432 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10433 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10434 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10435 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10436 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10437 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10438 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10439 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10440 fprintf(f, "-smpCores %d\n", appData.smpCores);
10442 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10444 fprintf(f, "-mps %d\n", appData.movesPerSession);
10445 fprintf(f, "-tc %s\n", appData.timeControl);
10446 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10448 fprintf(f, "-results \"%s\"\n", results);
10453 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10456 Substitute (char *participants, int expunge)
10458 int i, changed, changes=0, nPlayers=0;
10459 char *p, *q, *r, buf[MSG_SIZ];
10460 if(participants == NULL) return;
10461 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10462 r = p = participants; q = appData.participants;
10463 while(*p && *p == *q) {
10464 if(*p == '\n') r = p+1, nPlayers++;
10467 if(*p) { // difference
10468 while(*p && *p++ != '\n');
10469 while(*q && *q++ != '\n');
10470 changed = nPlayers;
10471 changes = 1 + (strcmp(p, q) != 0);
10473 if(changes == 1) { // a single engine mnemonic was changed
10474 q = r; while(*q) nPlayers += (*q++ == '\n');
10475 p = buf; while(*r && (*p = *r++) != '\n') p++;
10477 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10478 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10479 if(mnemonic[i]) { // The substitute is valid
10481 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10482 flock(fileno(f), LOCK_EX);
10483 ParseArgsFromFile(f);
10484 fseek(f, 0, SEEK_SET);
10485 FREE(appData.participants); appData.participants = participants;
10486 if(expunge) { // erase results of replaced engine
10487 int len = strlen(appData.results), w, b, dummy;
10488 for(i=0; i<len; i++) {
10489 Pairing(i, nPlayers, &w, &b, &dummy);
10490 if((w == changed || b == changed) && appData.results[i] == '*') {
10491 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10496 for(i=0; i<len; i++) {
10497 Pairing(i, nPlayers, &w, &b, &dummy);
10498 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10501 WriteTourneyFile(appData.results, f);
10502 fclose(f); // release lock
10505 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10507 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10508 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10509 free(participants);
10514 CheckPlayers (char *participants)
10517 char buf[MSG_SIZ], *p;
10518 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10519 while(p = strchr(participants, '\n')) {
10521 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10523 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10525 DisplayError(buf, 0);
10529 participants = p + 1;
10535 CreateTourney (char *name)
10538 if(matchMode && strcmp(name, appData.tourneyFile)) {
10539 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10541 if(name[0] == NULLCHAR) {
10542 if(appData.participants[0])
10543 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10546 f = fopen(name, "r");
10547 if(f) { // file exists
10548 ASSIGN(appData.tourneyFile, name);
10549 ParseArgsFromFile(f); // parse it
10551 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10552 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10553 DisplayError(_("Not enough participants"), 0);
10556 if(CheckPlayers(appData.participants)) return 0;
10557 ASSIGN(appData.tourneyFile, name);
10558 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10559 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10562 appData.noChessProgram = FALSE;
10563 appData.clockMode = TRUE;
10569 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10571 char buf[MSG_SIZ], *p, *q;
10572 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10573 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10574 skip = !all && group[0]; // if group requested, we start in skip mode
10575 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10576 p = names; q = buf; header = 0;
10577 while(*p && *p != '\n') *q++ = *p++;
10579 if(*p == '\n') p++;
10580 if(buf[0] == '#') {
10581 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10582 depth++; // we must be entering a new group
10583 if(all) continue; // suppress printing group headers when complete list requested
10585 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10587 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10588 if(engineList[i]) free(engineList[i]);
10589 engineList[i] = strdup(buf);
10590 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10591 if(engineMnemonic[i]) free(engineMnemonic[i]);
10592 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10594 sscanf(q + 8, "%s", buf + strlen(buf));
10597 engineMnemonic[i] = strdup(buf);
10600 engineList[i] = engineMnemonic[i] = NULL;
10604 // following implemented as macro to avoid type limitations
10605 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10608 SwapEngines (int n)
10609 { // swap settings for first engine and other engine (so far only some selected options)
10614 SWAP(chessProgram, p)
10616 SWAP(hasOwnBookUCI, h)
10617 SWAP(protocolVersion, h)
10619 SWAP(scoreIsAbsolute, h)
10624 SWAP(engOptions, p)
10625 SWAP(engInitString, p)
10626 SWAP(computerString, p)
10628 SWAP(fenOverride, p)
10630 SWAP(accumulateTC, h)
10635 GetEngineLine (char *s, int n)
10639 extern char *icsNames;
10640 if(!s || !*s) return 0;
10641 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10642 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10643 if(!mnemonic[i]) return 0;
10644 if(n == 11) return 1; // just testing if there was a match
10645 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10646 if(n == 1) SwapEngines(n);
10647 ParseArgsFromString(buf);
10648 if(n == 1) SwapEngines(n);
10649 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10650 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10651 ParseArgsFromString(buf);
10657 SetPlayer (int player, char *p)
10658 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10660 char buf[MSG_SIZ], *engineName;
10661 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10662 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10663 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10665 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10666 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10667 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10668 ParseArgsFromString(buf);
10669 } else { // no engine with this nickname is installed!
10670 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10671 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10672 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10674 DisplayError(buf, 0);
10681 char *recentEngines;
10684 RecentEngineEvent (int nr)
10687 // SwapEngines(1); // bump first to second
10688 // ReplaceEngine(&second, 1); // and load it there
10689 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10690 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10691 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10692 ReplaceEngine(&first, 0);
10693 FloatToFront(&appData.recentEngineList, command[n]);
10698 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10699 { // determine players from game number
10700 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10702 if(appData.tourneyType == 0) {
10703 roundsPerCycle = (nPlayers - 1) | 1;
10704 pairingsPerRound = nPlayers / 2;
10705 } else if(appData.tourneyType > 0) {
10706 roundsPerCycle = nPlayers - appData.tourneyType;
10707 pairingsPerRound = appData.tourneyType;
10709 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10710 gamesPerCycle = gamesPerRound * roundsPerCycle;
10711 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10712 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10713 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10714 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10715 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10716 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10718 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10719 if(appData.roundSync) *syncInterval = gamesPerRound;
10721 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10723 if(appData.tourneyType == 0) {
10724 if(curPairing == (nPlayers-1)/2 ) {
10725 *whitePlayer = curRound;
10726 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10728 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10729 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10730 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10731 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10733 } else if(appData.tourneyType > 1) {
10734 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10735 *whitePlayer = curRound + appData.tourneyType;
10736 } else if(appData.tourneyType > 0) {
10737 *whitePlayer = curPairing;
10738 *blackPlayer = curRound + appData.tourneyType;
10741 // take care of white/black alternation per round.
10742 // For cycles and games this is already taken care of by default, derived from matchGame!
10743 return curRound & 1;
10747 NextTourneyGame (int nr, int *swapColors)
10748 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10750 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10752 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10753 tf = fopen(appData.tourneyFile, "r");
10754 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10755 ParseArgsFromFile(tf); fclose(tf);
10756 InitTimeControls(); // TC might be altered from tourney file
10758 nPlayers = CountPlayers(appData.participants); // count participants
10759 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10760 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10763 p = q = appData.results;
10764 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10765 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10766 DisplayMessage(_("Waiting for other game(s)"),"");
10767 waitingForGame = TRUE;
10768 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10771 waitingForGame = FALSE;
10774 if(appData.tourneyType < 0) {
10775 if(nr>=0 && !pairingReceived) {
10777 if(pairing.pr == NoProc) {
10778 if(!appData.pairingEngine[0]) {
10779 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10782 StartChessProgram(&pairing); // starts the pairing engine
10784 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10785 SendToProgram(buf, &pairing);
10786 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10787 SendToProgram(buf, &pairing);
10788 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10790 pairingReceived = 0; // ... so we continue here
10792 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10793 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10794 matchGame = 1; roundNr = nr / syncInterval + 1;
10797 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10799 // redefine engines, engine dir, etc.
10800 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10801 if(first.pr == NoProc) {
10802 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10803 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10805 if(second.pr == NoProc) {
10807 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10808 SwapEngines(1); // and make that valid for second engine by swapping
10809 InitEngine(&second, 1);
10811 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10812 UpdateLogos(FALSE); // leave display to ModeHiglight()
10818 { // performs game initialization that does not invoke engines, and then tries to start the game
10819 int res, firstWhite, swapColors = 0;
10820 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10821 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
10823 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10824 if(strcmp(buf, currentDebugFile)) { // name has changed
10825 FILE *f = fopen(buf, "w");
10826 if(f) { // if opening the new file failed, just keep using the old one
10827 ASSIGN(currentDebugFile, buf);
10831 if(appData.serverFileName) {
10832 if(serverFP) fclose(serverFP);
10833 serverFP = fopen(appData.serverFileName, "w");
10834 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10835 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10839 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10840 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10841 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10842 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10843 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10844 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10845 Reset(FALSE, first.pr != NoProc);
10846 res = LoadGameOrPosition(matchGame); // setup game
10847 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10848 if(!res) return; // abort when bad game/pos file
10849 TwoMachinesEvent();
10853 UserAdjudicationEvent (int result)
10855 ChessMove gameResult = GameIsDrawn;
10858 gameResult = WhiteWins;
10860 else if( result < 0 ) {
10861 gameResult = BlackWins;
10864 if( gameMode == TwoMachinesPlay ) {
10865 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10870 // [HGM] save: calculate checksum of game to make games easily identifiable
10872 StringCheckSum (char *s)
10875 if(s==NULL) return 0;
10876 while(*s) i = i*259 + *s++;
10884 for(i=backwardMostMove; i<forwardMostMove; i++) {
10885 sum += pvInfoList[i].depth;
10886 sum += StringCheckSum(parseList[i]);
10887 sum += StringCheckSum(commentList[i]);
10890 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10891 return sum + StringCheckSum(commentList[i]);
10892 } // end of save patch
10895 GameEnds (ChessMove result, char *resultDetails, int whosays)
10897 GameMode nextGameMode;
10899 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10901 if(endingGame) return; /* [HGM] crash: forbid recursion */
10903 if(twoBoards) { // [HGM] dual: switch back to one board
10904 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10905 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10907 if (appData.debugMode) {
10908 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10909 result, resultDetails ? resultDetails : "(null)", whosays);
10912 fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10914 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10916 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10917 /* If we are playing on ICS, the server decides when the
10918 game is over, but the engine can offer to draw, claim
10922 if (appData.zippyPlay && first.initDone) {
10923 if (result == GameIsDrawn) {
10924 /* In case draw still needs to be claimed */
10925 SendToICS(ics_prefix);
10926 SendToICS("draw\n");
10927 } else if (StrCaseStr(resultDetails, "resign")) {
10928 SendToICS(ics_prefix);
10929 SendToICS("resign\n");
10933 endingGame = 0; /* [HGM] crash */
10937 /* If we're loading the game from a file, stop */
10938 if (whosays == GE_FILE) {
10939 (void) StopLoadGameTimer();
10943 /* Cancel draw offers */
10944 first.offeredDraw = second.offeredDraw = 0;
10946 /* If this is an ICS game, only ICS can really say it's done;
10947 if not, anyone can. */
10948 isIcsGame = (gameMode == IcsPlayingWhite ||
10949 gameMode == IcsPlayingBlack ||
10950 gameMode == IcsObserving ||
10951 gameMode == IcsExamining);
10953 if (!isIcsGame || whosays == GE_ICS) {
10954 /* OK -- not an ICS game, or ICS said it was done */
10956 if (!isIcsGame && !appData.noChessProgram)
10957 SetUserThinkingEnables();
10959 /* [HGM] if a machine claims the game end we verify this claim */
10960 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10961 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10963 ChessMove trueResult = (ChessMove) -1;
10965 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10966 first.twoMachinesColor[0] :
10967 second.twoMachinesColor[0] ;
10969 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10970 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10971 /* [HGM] verify: engine mate claims accepted if they were flagged */
10972 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10974 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10975 /* [HGM] verify: engine mate claims accepted if they were flagged */
10976 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10978 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10979 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10982 // now verify win claims, but not in drop games, as we don't understand those yet
10983 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10984 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10985 (result == WhiteWins && claimer == 'w' ||
10986 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10987 if (appData.debugMode) {
10988 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10989 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10991 if(result != trueResult) {
10992 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10993 result = claimer == 'w' ? BlackWins : WhiteWins;
10994 resultDetails = buf;
10997 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10998 && (forwardMostMove <= backwardMostMove ||
10999 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11000 (claimer=='b')==(forwardMostMove&1))
11002 /* [HGM] verify: draws that were not flagged are false claims */
11003 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11004 result = claimer == 'w' ? BlackWins : WhiteWins;
11005 resultDetails = buf;
11007 /* (Claiming a loss is accepted no questions asked!) */
11008 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11009 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11010 result = GameUnfinished;
11011 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11013 /* [HGM] bare: don't allow bare King to win */
11014 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11015 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11016 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11017 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11018 && result != GameIsDrawn)
11019 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11020 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11021 int p = (signed char)boards[forwardMostMove][i][j] - color;
11022 if(p >= 0 && p <= (int)WhiteKing) k++;
11024 if (appData.debugMode) {
11025 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11026 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11029 result = GameIsDrawn;
11030 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11031 resultDetails = buf;
11037 if(serverMoves != NULL && !loadFlag) { char c = '=';
11038 if(result==WhiteWins) c = '+';
11039 if(result==BlackWins) c = '-';
11040 if(resultDetails != NULL)
11041 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11043 if (resultDetails != NULL) {
11044 gameInfo.result = result;
11045 gameInfo.resultDetails = StrSave(resultDetails);
11047 /* display last move only if game was not loaded from file */
11048 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11049 DisplayMove(currentMove - 1);
11051 if (forwardMostMove != 0) {
11052 if (gameMode != PlayFromGameFile && gameMode != EditGame
11053 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11055 if (*appData.saveGameFile != NULLCHAR) {
11056 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11057 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11059 SaveGameToFile(appData.saveGameFile, TRUE);
11060 } else if (appData.autoSaveGames) {
11061 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11063 if (*appData.savePositionFile != NULLCHAR) {
11064 SavePositionToFile(appData.savePositionFile);
11066 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11070 /* Tell program how game ended in case it is learning */
11071 /* [HGM] Moved this to after saving the PGN, just in case */
11072 /* engine died and we got here through time loss. In that */
11073 /* case we will get a fatal error writing the pipe, which */
11074 /* would otherwise lose us the PGN. */
11075 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11076 /* output during GameEnds should never be fatal anymore */
11077 if (gameMode == MachinePlaysWhite ||
11078 gameMode == MachinePlaysBlack ||
11079 gameMode == TwoMachinesPlay ||
11080 gameMode == IcsPlayingWhite ||
11081 gameMode == IcsPlayingBlack ||
11082 gameMode == BeginningOfGame) {
11084 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11086 if (first.pr != NoProc) {
11087 SendToProgram(buf, &first);
11089 if (second.pr != NoProc &&
11090 gameMode == TwoMachinesPlay) {
11091 SendToProgram(buf, &second);
11096 if (appData.icsActive) {
11097 if (appData.quietPlay &&
11098 (gameMode == IcsPlayingWhite ||
11099 gameMode == IcsPlayingBlack)) {
11100 SendToICS(ics_prefix);
11101 SendToICS("set shout 1\n");
11103 nextGameMode = IcsIdle;
11104 ics_user_moved = FALSE;
11105 /* clean up premove. It's ugly when the game has ended and the
11106 * premove highlights are still on the board.
11109 gotPremove = FALSE;
11110 ClearPremoveHighlights();
11111 DrawPosition(FALSE, boards[currentMove]);
11113 if (whosays == GE_ICS) {
11116 if (gameMode == IcsPlayingWhite)
11118 else if(gameMode == IcsPlayingBlack)
11119 PlayIcsLossSound();
11122 if (gameMode == IcsPlayingBlack)
11124 else if(gameMode == IcsPlayingWhite)
11125 PlayIcsLossSound();
11128 PlayIcsDrawSound();
11131 PlayIcsUnfinishedSound();
11134 if(appData.quitNext) { ExitEvent(0); return; }
11135 } else if (gameMode == EditGame ||
11136 gameMode == PlayFromGameFile ||
11137 gameMode == AnalyzeMode ||
11138 gameMode == AnalyzeFile) {
11139 nextGameMode = gameMode;
11141 nextGameMode = EndOfGame;
11146 nextGameMode = gameMode;
11149 if (appData.noChessProgram) {
11150 gameMode = nextGameMode;
11152 endingGame = 0; /* [HGM] crash */
11157 /* Put first chess program into idle state */
11158 if (first.pr != NoProc &&
11159 (gameMode == MachinePlaysWhite ||
11160 gameMode == MachinePlaysBlack ||
11161 gameMode == TwoMachinesPlay ||
11162 gameMode == IcsPlayingWhite ||
11163 gameMode == IcsPlayingBlack ||
11164 gameMode == BeginningOfGame)) {
11165 SendToProgram("force\n", &first);
11166 if (first.usePing) {
11168 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11169 SendToProgram(buf, &first);
11172 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11173 /* Kill off first chess program */
11174 if (first.isr != NULL)
11175 RemoveInputSource(first.isr);
11178 if (first.pr != NoProc) {
11180 DoSleep( appData.delayBeforeQuit );
11181 SendToProgram("quit\n", &first);
11182 DoSleep( appData.delayAfterQuit );
11183 DestroyChildProcess(first.pr, first.useSigterm);
11184 first.reload = TRUE;
11188 if (second.reuse) {
11189 /* Put second chess program into idle state */
11190 if (second.pr != NoProc &&
11191 gameMode == TwoMachinesPlay) {
11192 SendToProgram("force\n", &second);
11193 if (second.usePing) {
11195 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11196 SendToProgram(buf, &second);
11199 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11200 /* Kill off second chess program */
11201 if (second.isr != NULL)
11202 RemoveInputSource(second.isr);
11205 if (second.pr != NoProc) {
11206 DoSleep( appData.delayBeforeQuit );
11207 SendToProgram("quit\n", &second);
11208 DoSleep( appData.delayAfterQuit );
11209 DestroyChildProcess(second.pr, second.useSigterm);
11210 second.reload = TRUE;
11212 second.pr = NoProc;
11215 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11216 char resChar = '=';
11220 if (first.twoMachinesColor[0] == 'w') {
11223 second.matchWins++;
11228 if (first.twoMachinesColor[0] == 'b') {
11231 second.matchWins++;
11234 case GameUnfinished:
11240 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11241 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11242 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11243 ReserveGame(nextGame, resChar); // sets nextGame
11244 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11245 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11246 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11248 if (nextGame <= appData.matchGames && !abortMatch) {
11249 gameMode = nextGameMode;
11250 matchGame = nextGame; // this will be overruled in tourney mode!
11251 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11252 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11253 endingGame = 0; /* [HGM] crash */
11256 gameMode = nextGameMode;
11257 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11258 first.tidy, second.tidy,
11259 first.matchWins, second.matchWins,
11260 appData.matchGames - (first.matchWins + second.matchWins));
11261 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11262 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11263 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11264 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11265 first.twoMachinesColor = "black\n";
11266 second.twoMachinesColor = "white\n";
11268 first.twoMachinesColor = "white\n";
11269 second.twoMachinesColor = "black\n";
11273 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11274 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11276 gameMode = nextGameMode;
11278 endingGame = 0; /* [HGM] crash */
11279 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11280 if(matchMode == TRUE) { // match through command line: exit with or without popup
11282 ToNrEvent(forwardMostMove);
11283 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11285 } else DisplayFatalError(buf, 0, 0);
11286 } else { // match through menu; just stop, with or without popup
11287 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11290 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11291 } else DisplayNote(buf);
11293 if(ranking) free(ranking);
11297 /* Assumes program was just initialized (initString sent).
11298 Leaves program in force mode. */
11300 FeedMovesToProgram (ChessProgramState *cps, int upto)
11304 if (appData.debugMode)
11305 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11306 startedFromSetupPosition ? "position and " : "",
11307 backwardMostMove, upto, cps->which);
11308 if(currentlyInitializedVariant != gameInfo.variant) {
11310 // [HGM] variantswitch: make engine aware of new variant
11311 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11312 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11313 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11314 SendToProgram(buf, cps);
11315 currentlyInitializedVariant = gameInfo.variant;
11317 SendToProgram("force\n", cps);
11318 if (startedFromSetupPosition) {
11319 SendBoard(cps, backwardMostMove);
11320 if (appData.debugMode) {
11321 fprintf(debugFP, "feedMoves\n");
11324 for (i = backwardMostMove; i < upto; i++) {
11325 SendMoveToProgram(i, cps);
11331 ResurrectChessProgram ()
11333 /* The chess program may have exited.
11334 If so, restart it and feed it all the moves made so far. */
11335 static int doInit = 0;
11337 if (appData.noChessProgram) return 1;
11339 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11340 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11341 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11342 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11344 if (first.pr != NoProc) return 1;
11345 StartChessProgram(&first);
11347 InitChessProgram(&first, FALSE);
11348 FeedMovesToProgram(&first, currentMove);
11350 if (!first.sendTime) {
11351 /* can't tell gnuchess what its clock should read,
11352 so we bow to its notion. */
11354 timeRemaining[0][currentMove] = whiteTimeRemaining;
11355 timeRemaining[1][currentMove] = blackTimeRemaining;
11358 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11359 appData.icsEngineAnalyze) && first.analysisSupport) {
11360 SendToProgram("analyze\n", &first);
11361 first.analyzing = TRUE;
11367 * Button procedures
11370 Reset (int redraw, int init)
11374 if (appData.debugMode) {
11375 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11376 redraw, init, gameMode);
11378 CleanupTail(); // [HGM] vari: delete any stored variations
11379 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11380 pausing = pauseExamInvalid = FALSE;
11381 startedFromSetupPosition = blackPlaysFirst = FALSE;
11383 whiteFlag = blackFlag = FALSE;
11384 userOfferedDraw = FALSE;
11385 hintRequested = bookRequested = FALSE;
11386 first.maybeThinking = FALSE;
11387 second.maybeThinking = FALSE;
11388 first.bookSuspend = FALSE; // [HGM] book
11389 second.bookSuspend = FALSE;
11390 thinkOutput[0] = NULLCHAR;
11391 lastHint[0] = NULLCHAR;
11392 ClearGameInfo(&gameInfo);
11393 gameInfo.variant = StringToVariant(appData.variant);
11394 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11395 ics_user_moved = ics_clock_paused = FALSE;
11396 ics_getting_history = H_FALSE;
11398 white_holding[0] = black_holding[0] = NULLCHAR;
11399 ClearProgramStats();
11400 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11404 flipView = appData.flipView;
11405 ClearPremoveHighlights();
11406 gotPremove = FALSE;
11407 alarmSounded = FALSE;
11408 killX = killY = -1; // [HGM] lion
11410 GameEnds(EndOfFile, NULL, GE_PLAYER);
11411 if(appData.serverMovesName != NULL) {
11412 /* [HGM] prepare to make moves file for broadcasting */
11413 clock_t t = clock();
11414 if(serverMoves != NULL) fclose(serverMoves);
11415 serverMoves = fopen(appData.serverMovesName, "r");
11416 if(serverMoves != NULL) {
11417 fclose(serverMoves);
11418 /* delay 15 sec before overwriting, so all clients can see end */
11419 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11421 serverMoves = fopen(appData.serverMovesName, "w");
11425 gameMode = BeginningOfGame;
11427 if(appData.icsActive) gameInfo.variant = VariantNormal;
11428 currentMove = forwardMostMove = backwardMostMove = 0;
11429 MarkTargetSquares(1);
11430 InitPosition(redraw);
11431 for (i = 0; i < MAX_MOVES; i++) {
11432 if (commentList[i] != NULL) {
11433 free(commentList[i]);
11434 commentList[i] = NULL;
11438 timeRemaining[0][0] = whiteTimeRemaining;
11439 timeRemaining[1][0] = blackTimeRemaining;
11441 if (first.pr == NoProc) {
11442 StartChessProgram(&first);
11445 InitChessProgram(&first, startedFromSetupPosition);
11448 DisplayMessage("", "");
11449 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11450 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11451 ClearMap(); // [HGM] exclude: invalidate map
11455 AutoPlayGameLoop ()
11458 if (!AutoPlayOneMove())
11460 if (matchMode || appData.timeDelay == 0)
11462 if (appData.timeDelay < 0)
11464 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11472 ReloadGame(1); // next game
11478 int fromX, fromY, toX, toY;
11480 if (appData.debugMode) {
11481 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11484 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11487 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11488 pvInfoList[currentMove].depth = programStats.depth;
11489 pvInfoList[currentMove].score = programStats.score;
11490 pvInfoList[currentMove].time = 0;
11491 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11492 else { // append analysis of final position as comment
11494 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11495 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11497 programStats.depth = 0;
11500 if (currentMove >= forwardMostMove) {
11501 if(gameMode == AnalyzeFile) {
11502 if(appData.loadGameIndex == -1) {
11503 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11504 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11506 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11509 // gameMode = EndOfGame;
11510 // ModeHighlight();
11512 /* [AS] Clear current move marker at the end of a game */
11513 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11518 toX = moveList[currentMove][2] - AAA;
11519 toY = moveList[currentMove][3] - ONE;
11521 if (moveList[currentMove][1] == '@') {
11522 if (appData.highlightLastMove) {
11523 SetHighlights(-1, -1, toX, toY);
11526 fromX = moveList[currentMove][0] - AAA;
11527 fromY = moveList[currentMove][1] - ONE;
11529 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11531 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11533 if (appData.highlightLastMove) {
11534 SetHighlights(fromX, fromY, toX, toY);
11537 DisplayMove(currentMove);
11538 SendMoveToProgram(currentMove++, &first);
11539 DisplayBothClocks();
11540 DrawPosition(FALSE, boards[currentMove]);
11541 // [HGM] PV info: always display, routine tests if empty
11542 DisplayComment(currentMove - 1, commentList[currentMove]);
11548 LoadGameOneMove (ChessMove readAhead)
11550 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11551 char promoChar = NULLCHAR;
11552 ChessMove moveType;
11553 char move[MSG_SIZ];
11556 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11557 gameMode != AnalyzeMode && gameMode != Training) {
11562 yyboardindex = forwardMostMove;
11563 if (readAhead != EndOfFile) {
11564 moveType = readAhead;
11566 if (gameFileFP == NULL)
11568 moveType = (ChessMove) Myylex();
11572 switch (moveType) {
11574 if (appData.debugMode)
11575 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11578 /* append the comment but don't display it */
11579 AppendComment(currentMove, p, FALSE);
11582 case WhiteCapturesEnPassant:
11583 case BlackCapturesEnPassant:
11584 case WhitePromotion:
11585 case BlackPromotion:
11586 case WhiteNonPromotion:
11587 case BlackNonPromotion:
11590 case WhiteKingSideCastle:
11591 case WhiteQueenSideCastle:
11592 case BlackKingSideCastle:
11593 case BlackQueenSideCastle:
11594 case WhiteKingSideCastleWild:
11595 case WhiteQueenSideCastleWild:
11596 case BlackKingSideCastleWild:
11597 case BlackQueenSideCastleWild:
11599 case WhiteHSideCastleFR:
11600 case WhiteASideCastleFR:
11601 case BlackHSideCastleFR:
11602 case BlackASideCastleFR:
11604 if (appData.debugMode)
11605 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11606 fromX = currentMoveString[0] - AAA;
11607 fromY = currentMoveString[1] - ONE;
11608 toX = currentMoveString[2] - AAA;
11609 toY = currentMoveString[3] - ONE;
11610 promoChar = currentMoveString[4];
11611 if(promoChar == ';') promoChar = NULLCHAR;
11616 if (appData.debugMode)
11617 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11618 fromX = moveType == WhiteDrop ?
11619 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11620 (int) CharToPiece(ToLower(currentMoveString[0]));
11622 toX = currentMoveString[2] - AAA;
11623 toY = currentMoveString[3] - ONE;
11629 case GameUnfinished:
11630 if (appData.debugMode)
11631 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11632 p = strchr(yy_text, '{');
11633 if (p == NULL) p = strchr(yy_text, '(');
11636 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11638 q = strchr(p, *p == '{' ? '}' : ')');
11639 if (q != NULL) *q = NULLCHAR;
11642 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11643 GameEnds(moveType, p, GE_FILE);
11645 if (cmailMsgLoaded) {
11647 flipView = WhiteOnMove(currentMove);
11648 if (moveType == GameUnfinished) flipView = !flipView;
11649 if (appData.debugMode)
11650 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11655 if (appData.debugMode)
11656 fprintf(debugFP, "Parser hit end of file\n");
11657 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11663 if (WhiteOnMove(currentMove)) {
11664 GameEnds(BlackWins, "Black mates", GE_FILE);
11666 GameEnds(WhiteWins, "White mates", GE_FILE);
11670 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11676 case MoveNumberOne:
11677 if (lastLoadGameStart == GNUChessGame) {
11678 /* GNUChessGames have numbers, but they aren't move numbers */
11679 if (appData.debugMode)
11680 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11681 yy_text, (int) moveType);
11682 return LoadGameOneMove(EndOfFile); /* tail recursion */
11684 /* else fall thru */
11689 /* Reached start of next game in file */
11690 if (appData.debugMode)
11691 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11692 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11698 if (WhiteOnMove(currentMove)) {
11699 GameEnds(BlackWins, "Black mates", GE_FILE);
11701 GameEnds(WhiteWins, "White mates", GE_FILE);
11705 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11711 case PositionDiagram: /* should not happen; ignore */
11712 case ElapsedTime: /* ignore */
11713 case NAG: /* ignore */
11714 if (appData.debugMode)
11715 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11716 yy_text, (int) moveType);
11717 return LoadGameOneMove(EndOfFile); /* tail recursion */
11720 if (appData.testLegality) {
11721 if (appData.debugMode)
11722 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11723 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11724 (forwardMostMove / 2) + 1,
11725 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11726 DisplayError(move, 0);
11729 if (appData.debugMode)
11730 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11731 yy_text, currentMoveString);
11732 fromX = currentMoveString[0] - AAA;
11733 fromY = currentMoveString[1] - ONE;
11734 toX = currentMoveString[2] - AAA;
11735 toY = currentMoveString[3] - ONE;
11736 promoChar = currentMoveString[4];
11740 case AmbiguousMove:
11741 if (appData.debugMode)
11742 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11743 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11744 (forwardMostMove / 2) + 1,
11745 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11746 DisplayError(move, 0);
11751 case ImpossibleMove:
11752 if (appData.debugMode)
11753 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11754 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11755 (forwardMostMove / 2) + 1,
11756 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11757 DisplayError(move, 0);
11763 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11764 DrawPosition(FALSE, boards[currentMove]);
11765 DisplayBothClocks();
11766 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11767 DisplayComment(currentMove - 1, commentList[currentMove]);
11769 (void) StopLoadGameTimer();
11771 cmailOldMove = forwardMostMove;
11774 /* currentMoveString is set as a side-effect of yylex */
11776 thinkOutput[0] = NULLCHAR;
11777 MakeMove(fromX, fromY, toX, toY, promoChar);
11778 killX = killY = -1; // [HGM] lion: used up
11779 currentMove = forwardMostMove;
11784 /* Load the nth game from the given file */
11786 LoadGameFromFile (char *filename, int n, char *title, int useList)
11791 if (strcmp(filename, "-") == 0) {
11795 f = fopen(filename, "rb");
11797 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11798 DisplayError(buf, errno);
11802 if (fseek(f, 0, 0) == -1) {
11803 /* f is not seekable; probably a pipe */
11806 if (useList && n == 0) {
11807 int error = GameListBuild(f);
11809 DisplayError(_("Cannot build game list"), error);
11810 } else if (!ListEmpty(&gameList) &&
11811 ((ListGame *) gameList.tailPred)->number > 1) {
11812 GameListPopUp(f, title);
11819 return LoadGame(f, n, title, FALSE);
11824 MakeRegisteredMove ()
11826 int fromX, fromY, toX, toY;
11828 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11829 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11832 if (appData.debugMode)
11833 fprintf(debugFP, "Restoring %s for game %d\n",
11834 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11836 thinkOutput[0] = NULLCHAR;
11837 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11838 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11839 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11840 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11841 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11842 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11843 MakeMove(fromX, fromY, toX, toY, promoChar);
11844 ShowMove(fromX, fromY, toX, toY);
11846 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11853 if (WhiteOnMove(currentMove)) {
11854 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11856 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11861 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11868 if (WhiteOnMove(currentMove)) {
11869 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11871 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11876 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11887 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11889 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11893 if (gameNumber > nCmailGames) {
11894 DisplayError(_("No more games in this message"), 0);
11897 if (f == lastLoadGameFP) {
11898 int offset = gameNumber - lastLoadGameNumber;
11900 cmailMsg[0] = NULLCHAR;
11901 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11902 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11903 nCmailMovesRegistered--;
11905 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11906 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11907 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11910 if (! RegisterMove()) return FALSE;
11914 retVal = LoadGame(f, gameNumber, title, useList);
11916 /* Make move registered during previous look at this game, if any */
11917 MakeRegisteredMove();
11919 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11920 commentList[currentMove]
11921 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11922 DisplayComment(currentMove - 1, commentList[currentMove]);
11928 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11930 ReloadGame (int offset)
11932 int gameNumber = lastLoadGameNumber + offset;
11933 if (lastLoadGameFP == NULL) {
11934 DisplayError(_("No game has been loaded yet"), 0);
11937 if (gameNumber <= 0) {
11938 DisplayError(_("Can't back up any further"), 0);
11941 if (cmailMsgLoaded) {
11942 return CmailLoadGame(lastLoadGameFP, gameNumber,
11943 lastLoadGameTitle, lastLoadGameUseList);
11945 return LoadGame(lastLoadGameFP, gameNumber,
11946 lastLoadGameTitle, lastLoadGameUseList);
11950 int keys[EmptySquare+1];
11953 PositionMatches (Board b1, Board b2)
11956 switch(appData.searchMode) {
11957 case 1: return CompareWithRights(b1, b2);
11959 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11960 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11964 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11965 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11966 sum += keys[b1[r][f]] - keys[b2[r][f]];
11970 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11971 sum += keys[b1[r][f]] - keys[b2[r][f]];
11983 int pieceList[256], quickBoard[256];
11984 ChessSquare pieceType[256] = { EmptySquare };
11985 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11986 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11987 int soughtTotal, turn;
11988 Boolean epOK, flipSearch;
11991 unsigned char piece, to;
11994 #define DSIZE (250000)
11996 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11997 Move *moveDatabase = initialSpace;
11998 unsigned int movePtr, dataSize = DSIZE;
12001 MakePieceList (Board board, int *counts)
12003 int r, f, n=Q_PROMO, total=0;
12004 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12005 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12006 int sq = f + (r<<4);
12007 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12008 quickBoard[sq] = ++n;
12010 pieceType[n] = board[r][f];
12011 counts[board[r][f]]++;
12012 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12013 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12017 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12022 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12024 int sq = fromX + (fromY<<4);
12025 int piece = quickBoard[sq];
12026 quickBoard[sq] = 0;
12027 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12028 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12029 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12030 moveDatabase[movePtr++].piece = Q_WCASTL;
12031 quickBoard[sq] = piece;
12032 piece = quickBoard[from]; quickBoard[from] = 0;
12033 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12035 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12036 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12037 moveDatabase[movePtr++].piece = Q_BCASTL;
12038 quickBoard[sq] = piece;
12039 piece = quickBoard[from]; quickBoard[from] = 0;
12040 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12042 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12043 quickBoard[(fromY<<4)+toX] = 0;
12044 moveDatabase[movePtr].piece = Q_EP;
12045 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12046 moveDatabase[movePtr].to = sq;
12048 if(promoPiece != pieceType[piece]) {
12049 moveDatabase[movePtr++].piece = Q_PROMO;
12050 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12052 moveDatabase[movePtr].piece = piece;
12053 quickBoard[sq] = piece;
12058 PackGame (Board board)
12060 Move *newSpace = NULL;
12061 moveDatabase[movePtr].piece = 0; // terminate previous game
12062 if(movePtr > dataSize) {
12063 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12064 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12065 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12068 Move *p = moveDatabase, *q = newSpace;
12069 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12070 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12071 moveDatabase = newSpace;
12072 } else { // calloc failed, we must be out of memory. Too bad...
12073 dataSize = 0; // prevent calloc events for all subsequent games
12074 return 0; // and signal this one isn't cached
12078 MakePieceList(board, counts);
12083 QuickCompare (Board board, int *minCounts, int *maxCounts)
12084 { // compare according to search mode
12086 switch(appData.searchMode)
12088 case 1: // exact position match
12089 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12090 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12091 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12094 case 2: // can have extra material on empty squares
12095 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12096 if(board[r][f] == EmptySquare) continue;
12097 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12100 case 3: // material with exact Pawn structure
12101 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12102 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12103 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12104 } // fall through to material comparison
12105 case 4: // exact material
12106 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12108 case 6: // material range with given imbalance
12109 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12110 // fall through to range comparison
12111 case 5: // material range
12112 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12118 QuickScan (Board board, Move *move)
12119 { // reconstruct game,and compare all positions in it
12120 int cnt=0, stretch=0, total = MakePieceList(board, counts);
12122 int piece = move->piece;
12123 int to = move->to, from = pieceList[piece];
12124 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12125 if(!piece) return -1;
12126 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12127 piece = (++move)->piece;
12128 from = pieceList[piece];
12129 counts[pieceType[piece]]--;
12130 pieceType[piece] = (ChessSquare) move->to;
12131 counts[move->to]++;
12132 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12133 counts[pieceType[quickBoard[to]]]--;
12134 quickBoard[to] = 0; total--;
12137 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12138 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12139 from = pieceList[piece]; // so this must be King
12140 quickBoard[from] = 0;
12141 pieceList[piece] = to;
12142 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12143 quickBoard[from] = 0; // rook
12144 quickBoard[to] = piece;
12145 to = move->to; piece = move->piece;
12149 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12150 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12151 quickBoard[from] = 0;
12153 quickBoard[to] = piece;
12154 pieceList[piece] = to;
12156 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12157 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12158 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12159 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12161 static int lastCounts[EmptySquare+1];
12163 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12164 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12165 } else stretch = 0;
12166 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12175 flipSearch = FALSE;
12176 CopyBoard(soughtBoard, boards[currentMove]);
12177 soughtTotal = MakePieceList(soughtBoard, maxSought);
12178 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12179 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12180 CopyBoard(reverseBoard, boards[currentMove]);
12181 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12182 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12183 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12184 reverseBoard[r][f] = piece;
12186 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12187 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12188 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12189 || (boards[currentMove][CASTLING][2] == NoRights ||
12190 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12191 && (boards[currentMove][CASTLING][5] == NoRights ||
12192 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12195 CopyBoard(flipBoard, soughtBoard);
12196 CopyBoard(rotateBoard, reverseBoard);
12197 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12198 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12199 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12202 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12203 if(appData.searchMode >= 5) {
12204 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12205 MakePieceList(soughtBoard, minSought);
12206 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12208 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12209 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12212 GameInfo dummyInfo;
12213 static int creatingBook;
12216 GameContainsPosition (FILE *f, ListGame *lg)
12218 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12219 int fromX, fromY, toX, toY;
12221 static int initDone=FALSE;
12223 // weed out games based on numerical tag comparison
12224 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12225 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12226 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12227 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12229 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12232 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12233 else CopyBoard(boards[scratch], initialPosition); // default start position
12236 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12237 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12240 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12241 fseek(f, lg->offset, 0);
12244 yyboardindex = scratch;
12245 quickFlag = plyNr+1;
12250 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12256 if(plyNr) return -1; // after we have seen moves, this is for new game
12259 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12260 case ImpossibleMove:
12261 case WhiteWins: // game ends here with these four
12264 case GameUnfinished:
12268 if(appData.testLegality) return -1;
12269 case WhiteCapturesEnPassant:
12270 case BlackCapturesEnPassant:
12271 case WhitePromotion:
12272 case BlackPromotion:
12273 case WhiteNonPromotion:
12274 case BlackNonPromotion:
12277 case WhiteKingSideCastle:
12278 case WhiteQueenSideCastle:
12279 case BlackKingSideCastle:
12280 case BlackQueenSideCastle:
12281 case WhiteKingSideCastleWild:
12282 case WhiteQueenSideCastleWild:
12283 case BlackKingSideCastleWild:
12284 case BlackQueenSideCastleWild:
12285 case WhiteHSideCastleFR:
12286 case WhiteASideCastleFR:
12287 case BlackHSideCastleFR:
12288 case BlackASideCastleFR:
12289 fromX = currentMoveString[0] - AAA;
12290 fromY = currentMoveString[1] - ONE;
12291 toX = currentMoveString[2] - AAA;
12292 toY = currentMoveString[3] - ONE;
12293 promoChar = currentMoveString[4];
12297 fromX = next == WhiteDrop ?
12298 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12299 (int) CharToPiece(ToLower(currentMoveString[0]));
12301 toX = currentMoveString[2] - AAA;
12302 toY = currentMoveString[3] - ONE;
12306 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12308 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12309 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12310 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12311 if(appData.findMirror) {
12312 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12313 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12318 /* Load the nth game from open file f */
12320 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12324 int gn = gameNumber;
12325 ListGame *lg = NULL;
12326 int numPGNTags = 0;
12328 GameMode oldGameMode;
12329 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12331 if (appData.debugMode)
12332 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12334 if (gameMode == Training )
12335 SetTrainingModeOff();
12337 oldGameMode = gameMode;
12338 if (gameMode != BeginningOfGame) {
12339 Reset(FALSE, TRUE);
12341 killX = killY = -1; // [HGM] lion: in case we did not Reset
12344 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12345 fclose(lastLoadGameFP);
12349 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12352 fseek(f, lg->offset, 0);
12353 GameListHighlight(gameNumber);
12354 pos = lg->position;
12358 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12359 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12361 DisplayError(_("Game number out of range"), 0);
12366 if (fseek(f, 0, 0) == -1) {
12367 if (f == lastLoadGameFP ?
12368 gameNumber == lastLoadGameNumber + 1 :
12372 DisplayError(_("Can't seek on game file"), 0);
12377 lastLoadGameFP = f;
12378 lastLoadGameNumber = gameNumber;
12379 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12380 lastLoadGameUseList = useList;
12384 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12385 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12386 lg->gameInfo.black);
12388 } else if (*title != NULLCHAR) {
12389 if (gameNumber > 1) {
12390 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12393 DisplayTitle(title);
12397 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12398 gameMode = PlayFromGameFile;
12402 currentMove = forwardMostMove = backwardMostMove = 0;
12403 CopyBoard(boards[0], initialPosition);
12407 * Skip the first gn-1 games in the file.
12408 * Also skip over anything that precedes an identifiable
12409 * start of game marker, to avoid being confused by
12410 * garbage at the start of the file. Currently
12411 * recognized start of game markers are the move number "1",
12412 * the pattern "gnuchess .* game", the pattern
12413 * "^[#;%] [^ ]* game file", and a PGN tag block.
12414 * A game that starts with one of the latter two patterns
12415 * will also have a move number 1, possibly
12416 * following a position diagram.
12417 * 5-4-02: Let's try being more lenient and allowing a game to
12418 * start with an unnumbered move. Does that break anything?
12420 cm = lastLoadGameStart = EndOfFile;
12422 yyboardindex = forwardMostMove;
12423 cm = (ChessMove) Myylex();
12426 if (cmailMsgLoaded) {
12427 nCmailGames = CMAIL_MAX_GAMES - gn;
12430 DisplayError(_("Game not found in file"), 0);
12437 lastLoadGameStart = cm;
12440 case MoveNumberOne:
12441 switch (lastLoadGameStart) {
12446 case MoveNumberOne:
12448 gn--; /* count this game */
12449 lastLoadGameStart = cm;
12458 switch (lastLoadGameStart) {
12461 case MoveNumberOne:
12463 gn--; /* count this game */
12464 lastLoadGameStart = cm;
12467 lastLoadGameStart = cm; /* game counted already */
12475 yyboardindex = forwardMostMove;
12476 cm = (ChessMove) Myylex();
12477 } while (cm == PGNTag || cm == Comment);
12484 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12485 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12486 != CMAIL_OLD_RESULT) {
12488 cmailResult[ CMAIL_MAX_GAMES
12489 - gn - 1] = CMAIL_OLD_RESULT;
12496 /* Only a NormalMove can be at the start of a game
12497 * without a position diagram. */
12498 if (lastLoadGameStart == EndOfFile ) {
12500 lastLoadGameStart = MoveNumberOne;
12509 if (appData.debugMode)
12510 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12512 if (cm == XBoardGame) {
12513 /* Skip any header junk before position diagram and/or move 1 */
12515 yyboardindex = forwardMostMove;
12516 cm = (ChessMove) Myylex();
12518 if (cm == EndOfFile ||
12519 cm == GNUChessGame || cm == XBoardGame) {
12520 /* Empty game; pretend end-of-file and handle later */
12525 if (cm == MoveNumberOne || cm == PositionDiagram ||
12526 cm == PGNTag || cm == Comment)
12529 } else if (cm == GNUChessGame) {
12530 if (gameInfo.event != NULL) {
12531 free(gameInfo.event);
12533 gameInfo.event = StrSave(yy_text);
12536 startedFromSetupPosition = FALSE;
12537 while (cm == PGNTag) {
12538 if (appData.debugMode)
12539 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12540 err = ParsePGNTag(yy_text, &gameInfo);
12541 if (!err) numPGNTags++;
12543 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12544 if(gameInfo.variant != oldVariant) {
12545 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12546 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12547 InitPosition(TRUE);
12548 oldVariant = gameInfo.variant;
12549 if (appData.debugMode)
12550 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12554 if (gameInfo.fen != NULL) {
12555 Board initial_position;
12556 startedFromSetupPosition = TRUE;
12557 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12559 DisplayError(_("Bad FEN position in file"), 0);
12562 CopyBoard(boards[0], initial_position);
12563 if (blackPlaysFirst) {
12564 currentMove = forwardMostMove = backwardMostMove = 1;
12565 CopyBoard(boards[1], initial_position);
12566 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12567 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12568 timeRemaining[0][1] = whiteTimeRemaining;
12569 timeRemaining[1][1] = blackTimeRemaining;
12570 if (commentList[0] != NULL) {
12571 commentList[1] = commentList[0];
12572 commentList[0] = NULL;
12575 currentMove = forwardMostMove = backwardMostMove = 0;
12577 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12579 initialRulePlies = FENrulePlies;
12580 for( i=0; i< nrCastlingRights; i++ )
12581 initialRights[i] = initial_position[CASTLING][i];
12583 yyboardindex = forwardMostMove;
12584 free(gameInfo.fen);
12585 gameInfo.fen = NULL;
12588 yyboardindex = forwardMostMove;
12589 cm = (ChessMove) Myylex();
12591 /* Handle comments interspersed among the tags */
12592 while (cm == Comment) {
12594 if (appData.debugMode)
12595 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12597 AppendComment(currentMove, p, FALSE);
12598 yyboardindex = forwardMostMove;
12599 cm = (ChessMove) Myylex();
12603 /* don't rely on existence of Event tag since if game was
12604 * pasted from clipboard the Event tag may not exist
12606 if (numPGNTags > 0){
12608 if (gameInfo.variant == VariantNormal) {
12609 VariantClass v = StringToVariant(gameInfo.event);
12610 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12611 if(v < VariantShogi) gameInfo.variant = v;
12614 if( appData.autoDisplayTags ) {
12615 tags = PGNTags(&gameInfo);
12616 TagsPopUp(tags, CmailMsg());
12621 /* Make something up, but don't display it now */
12626 if (cm == PositionDiagram) {
12629 Board initial_position;
12631 if (appData.debugMode)
12632 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12634 if (!startedFromSetupPosition) {
12636 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12637 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12648 initial_position[i][j++] = CharToPiece(*p);
12651 while (*p == ' ' || *p == '\t' ||
12652 *p == '\n' || *p == '\r') p++;
12654 if (strncmp(p, "black", strlen("black"))==0)
12655 blackPlaysFirst = TRUE;
12657 blackPlaysFirst = FALSE;
12658 startedFromSetupPosition = TRUE;
12660 CopyBoard(boards[0], initial_position);
12661 if (blackPlaysFirst) {
12662 currentMove = forwardMostMove = backwardMostMove = 1;
12663 CopyBoard(boards[1], initial_position);
12664 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12665 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12666 timeRemaining[0][1] = whiteTimeRemaining;
12667 timeRemaining[1][1] = blackTimeRemaining;
12668 if (commentList[0] != NULL) {
12669 commentList[1] = commentList[0];
12670 commentList[0] = NULL;
12673 currentMove = forwardMostMove = backwardMostMove = 0;
12676 yyboardindex = forwardMostMove;
12677 cm = (ChessMove) Myylex();
12680 if(!creatingBook) {
12681 if (first.pr == NoProc) {
12682 StartChessProgram(&first);
12684 InitChessProgram(&first, FALSE);
12685 SendToProgram("force\n", &first);
12686 if (startedFromSetupPosition) {
12687 SendBoard(&first, forwardMostMove);
12688 if (appData.debugMode) {
12689 fprintf(debugFP, "Load Game\n");
12691 DisplayBothClocks();
12695 /* [HGM] server: flag to write setup moves in broadcast file as one */
12696 loadFlag = appData.suppressLoadMoves;
12698 while (cm == Comment) {
12700 if (appData.debugMode)
12701 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12703 AppendComment(currentMove, p, FALSE);
12704 yyboardindex = forwardMostMove;
12705 cm = (ChessMove) Myylex();
12708 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12709 cm == WhiteWins || cm == BlackWins ||
12710 cm == GameIsDrawn || cm == GameUnfinished) {
12711 DisplayMessage("", _("No moves in game"));
12712 if (cmailMsgLoaded) {
12713 if (appData.debugMode)
12714 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12718 DrawPosition(FALSE, boards[currentMove]);
12719 DisplayBothClocks();
12720 gameMode = EditGame;
12727 // [HGM] PV info: routine tests if comment empty
12728 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12729 DisplayComment(currentMove - 1, commentList[currentMove]);
12731 if (!matchMode && appData.timeDelay != 0)
12732 DrawPosition(FALSE, boards[currentMove]);
12734 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12735 programStats.ok_to_send = 1;
12738 /* if the first token after the PGN tags is a move
12739 * and not move number 1, retrieve it from the parser
12741 if (cm != MoveNumberOne)
12742 LoadGameOneMove(cm);
12744 /* load the remaining moves from the file */
12745 while (LoadGameOneMove(EndOfFile)) {
12746 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12747 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12750 /* rewind to the start of the game */
12751 currentMove = backwardMostMove;
12753 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12755 if (oldGameMode == AnalyzeFile) {
12756 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12757 AnalyzeFileEvent();
12759 if (oldGameMode == AnalyzeMode) {
12760 AnalyzeFileEvent();
12763 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12764 long int w, b; // [HGM] adjourn: restore saved clock times
12765 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12766 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12767 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12768 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12772 if(creatingBook) return TRUE;
12773 if (!matchMode && pos > 0) {
12774 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12776 if (matchMode || appData.timeDelay == 0) {
12778 } else if (appData.timeDelay > 0) {
12779 AutoPlayGameLoop();
12782 if (appData.debugMode)
12783 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12785 loadFlag = 0; /* [HGM] true game starts */
12789 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12791 ReloadPosition (int offset)
12793 int positionNumber = lastLoadPositionNumber + offset;
12794 if (lastLoadPositionFP == NULL) {
12795 DisplayError(_("No position has been loaded yet"), 0);
12798 if (positionNumber <= 0) {
12799 DisplayError(_("Can't back up any further"), 0);
12802 return LoadPosition(lastLoadPositionFP, positionNumber,
12803 lastLoadPositionTitle);
12806 /* Load the nth position from the given file */
12808 LoadPositionFromFile (char *filename, int n, char *title)
12813 if (strcmp(filename, "-") == 0) {
12814 return LoadPosition(stdin, n, "stdin");
12816 f = fopen(filename, "rb");
12818 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12819 DisplayError(buf, errno);
12822 return LoadPosition(f, n, title);
12827 /* Load the nth position from the given open file, and close it */
12829 LoadPosition (FILE *f, int positionNumber, char *title)
12831 char *p, line[MSG_SIZ];
12832 Board initial_position;
12833 int i, j, fenMode, pn;
12835 if (gameMode == Training )
12836 SetTrainingModeOff();
12838 if (gameMode != BeginningOfGame) {
12839 Reset(FALSE, TRUE);
12841 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12842 fclose(lastLoadPositionFP);
12844 if (positionNumber == 0) positionNumber = 1;
12845 lastLoadPositionFP = f;
12846 lastLoadPositionNumber = positionNumber;
12847 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12848 if (first.pr == NoProc && !appData.noChessProgram) {
12849 StartChessProgram(&first);
12850 InitChessProgram(&first, FALSE);
12852 pn = positionNumber;
12853 if (positionNumber < 0) {
12854 /* Negative position number means to seek to that byte offset */
12855 if (fseek(f, -positionNumber, 0) == -1) {
12856 DisplayError(_("Can't seek on position file"), 0);
12861 if (fseek(f, 0, 0) == -1) {
12862 if (f == lastLoadPositionFP ?
12863 positionNumber == lastLoadPositionNumber + 1 :
12864 positionNumber == 1) {
12867 DisplayError(_("Can't seek on position file"), 0);
12872 /* See if this file is FEN or old-style xboard */
12873 if (fgets(line, MSG_SIZ, f) == NULL) {
12874 DisplayError(_("Position not found in file"), 0);
12877 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12878 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12881 if (fenMode || line[0] == '#') pn--;
12883 /* skip positions before number pn */
12884 if (fgets(line, MSG_SIZ, f) == NULL) {
12886 DisplayError(_("Position not found in file"), 0);
12889 if (fenMode || line[0] == '#') pn--;
12894 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12895 DisplayError(_("Bad FEN position in file"), 0);
12899 (void) fgets(line, MSG_SIZ, f);
12900 (void) fgets(line, MSG_SIZ, f);
12902 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12903 (void) fgets(line, MSG_SIZ, f);
12904 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12907 initial_position[i][j++] = CharToPiece(*p);
12911 blackPlaysFirst = FALSE;
12913 (void) fgets(line, MSG_SIZ, f);
12914 if (strncmp(line, "black", strlen("black"))==0)
12915 blackPlaysFirst = TRUE;
12918 startedFromSetupPosition = TRUE;
12920 CopyBoard(boards[0], initial_position);
12921 if (blackPlaysFirst) {
12922 currentMove = forwardMostMove = backwardMostMove = 1;
12923 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12924 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12925 CopyBoard(boards[1], initial_position);
12926 DisplayMessage("", _("Black to play"));
12928 currentMove = forwardMostMove = backwardMostMove = 0;
12929 DisplayMessage("", _("White to play"));
12931 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12932 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12933 SendToProgram("force\n", &first);
12934 SendBoard(&first, forwardMostMove);
12936 if (appData.debugMode) {
12938 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12939 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12940 fprintf(debugFP, "Load Position\n");
12943 if (positionNumber > 1) {
12944 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12945 DisplayTitle(line);
12947 DisplayTitle(title);
12949 gameMode = EditGame;
12952 timeRemaining[0][1] = whiteTimeRemaining;
12953 timeRemaining[1][1] = blackTimeRemaining;
12954 DrawPosition(FALSE, boards[currentMove]);
12961 CopyPlayerNameIntoFileName (char **dest, char *src)
12963 while (*src != NULLCHAR && *src != ',') {
12968 *(*dest)++ = *src++;
12974 DefaultFileName (char *ext)
12976 static char def[MSG_SIZ];
12979 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12981 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12983 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12985 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12992 /* Save the current game to the given file */
12994 SaveGameToFile (char *filename, int append)
12998 int result, i, t,tot=0;
13000 if (strcmp(filename, "-") == 0) {
13001 return SaveGame(stdout, 0, NULL);
13003 for(i=0; i<10; i++) { // upto 10 tries
13004 f = fopen(filename, append ? "a" : "w");
13005 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13006 if(f || errno != 13) break;
13007 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13011 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13012 DisplayError(buf, errno);
13015 safeStrCpy(buf, lastMsg, MSG_SIZ);
13016 DisplayMessage(_("Waiting for access to save file"), "");
13017 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13018 DisplayMessage(_("Saving game"), "");
13019 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13020 result = SaveGame(f, 0, NULL);
13021 DisplayMessage(buf, "");
13028 SavePart (char *str)
13030 static char buf[MSG_SIZ];
13033 p = strchr(str, ' ');
13034 if (p == NULL) return str;
13035 strncpy(buf, str, p - str);
13036 buf[p - str] = NULLCHAR;
13040 #define PGN_MAX_LINE 75
13042 #define PGN_SIDE_WHITE 0
13043 #define PGN_SIDE_BLACK 1
13046 FindFirstMoveOutOfBook (int side)
13050 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13051 int index = backwardMostMove;
13052 int has_book_hit = 0;
13054 if( (index % 2) != side ) {
13058 while( index < forwardMostMove ) {
13059 /* Check to see if engine is in book */
13060 int depth = pvInfoList[index].depth;
13061 int score = pvInfoList[index].score;
13067 else if( score == 0 && depth == 63 ) {
13068 in_book = 1; /* Zappa */
13070 else if( score == 2 && depth == 99 ) {
13071 in_book = 1; /* Abrok */
13074 has_book_hit += in_book;
13090 GetOutOfBookInfo (char * buf)
13094 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13096 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13097 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13101 if( oob[0] >= 0 || oob[1] >= 0 ) {
13102 for( i=0; i<2; i++ ) {
13106 if( i > 0 && oob[0] >= 0 ) {
13107 strcat( buf, " " );
13110 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13111 sprintf( buf+strlen(buf), "%s%.2f",
13112 pvInfoList[idx].score >= 0 ? "+" : "",
13113 pvInfoList[idx].score / 100.0 );
13119 /* Save game in PGN style and close the file */
13121 SaveGamePGN (FILE *f)
13123 int i, offset, linelen, newblock;
13126 int movelen, numlen, blank;
13127 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13129 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13131 PrintPGNTags(f, &gameInfo);
13133 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13135 if (backwardMostMove > 0 || startedFromSetupPosition) {
13136 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13137 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13138 fprintf(f, "\n{--------------\n");
13139 PrintPosition(f, backwardMostMove);
13140 fprintf(f, "--------------}\n");
13144 /* [AS] Out of book annotation */
13145 if( appData.saveOutOfBookInfo ) {
13148 GetOutOfBookInfo( buf );
13150 if( buf[0] != '\0' ) {
13151 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13158 i = backwardMostMove;
13162 while (i < forwardMostMove) {
13163 /* Print comments preceding this move */
13164 if (commentList[i] != NULL) {
13165 if (linelen > 0) fprintf(f, "\n");
13166 fprintf(f, "%s", commentList[i]);
13171 /* Format move number */
13173 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13176 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13178 numtext[0] = NULLCHAR;
13180 numlen = strlen(numtext);
13183 /* Print move number */
13184 blank = linelen > 0 && numlen > 0;
13185 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13194 fprintf(f, "%s", numtext);
13198 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13199 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13202 blank = linelen > 0 && movelen > 0;
13203 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13212 fprintf(f, "%s", move_buffer);
13213 linelen += movelen;
13215 /* [AS] Add PV info if present */
13216 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13217 /* [HGM] add time */
13218 char buf[MSG_SIZ]; int seconds;
13220 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13226 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13229 seconds = (seconds + 4)/10; // round to full seconds
13231 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13233 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13236 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13237 pvInfoList[i].score >= 0 ? "+" : "",
13238 pvInfoList[i].score / 100.0,
13239 pvInfoList[i].depth,
13242 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13244 /* Print score/depth */
13245 blank = linelen > 0 && movelen > 0;
13246 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13255 fprintf(f, "%s", move_buffer);
13256 linelen += movelen;
13262 /* Start a new line */
13263 if (linelen > 0) fprintf(f, "\n");
13265 /* Print comments after last move */
13266 if (commentList[i] != NULL) {
13267 fprintf(f, "%s\n", commentList[i]);
13271 if (gameInfo.resultDetails != NULL &&
13272 gameInfo.resultDetails[0] != NULLCHAR) {
13273 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13274 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13275 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13276 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13277 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13279 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13283 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13287 /* Save game in old style and close the file */
13289 SaveGameOldStyle (FILE *f)
13294 tm = time((time_t *) NULL);
13296 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13299 if (backwardMostMove > 0 || startedFromSetupPosition) {
13300 fprintf(f, "\n[--------------\n");
13301 PrintPosition(f, backwardMostMove);
13302 fprintf(f, "--------------]\n");
13307 i = backwardMostMove;
13308 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13310 while (i < forwardMostMove) {
13311 if (commentList[i] != NULL) {
13312 fprintf(f, "[%s]\n", commentList[i]);
13315 if ((i % 2) == 1) {
13316 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13319 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13321 if (commentList[i] != NULL) {
13325 if (i >= forwardMostMove) {
13329 fprintf(f, "%s\n", parseList[i]);
13334 if (commentList[i] != NULL) {
13335 fprintf(f, "[%s]\n", commentList[i]);
13338 /* This isn't really the old style, but it's close enough */
13339 if (gameInfo.resultDetails != NULL &&
13340 gameInfo.resultDetails[0] != NULLCHAR) {
13341 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13342 gameInfo.resultDetails);
13344 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13351 /* Save the current game to open file f and close the file */
13353 SaveGame (FILE *f, int dummy, char *dummy2)
13355 if (gameMode == EditPosition) EditPositionDone(TRUE);
13356 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13357 if (appData.oldSaveStyle)
13358 return SaveGameOldStyle(f);
13360 return SaveGamePGN(f);
13363 /* Save the current position to the given file */
13365 SavePositionToFile (char *filename)
13370 if (strcmp(filename, "-") == 0) {
13371 return SavePosition(stdout, 0, NULL);
13373 f = fopen(filename, "a");
13375 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13376 DisplayError(buf, errno);
13379 safeStrCpy(buf, lastMsg, MSG_SIZ);
13380 DisplayMessage(_("Waiting for access to save file"), "");
13381 flock(fileno(f), LOCK_EX); // [HGM] lock
13382 DisplayMessage(_("Saving position"), "");
13383 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13384 SavePosition(f, 0, NULL);
13385 DisplayMessage(buf, "");
13391 /* Save the current position to the given open file and close the file */
13393 SavePosition (FILE *f, int dummy, char *dummy2)
13398 if (gameMode == EditPosition) EditPositionDone(TRUE);
13399 if (appData.oldSaveStyle) {
13400 tm = time((time_t *) NULL);
13402 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13404 fprintf(f, "[--------------\n");
13405 PrintPosition(f, currentMove);
13406 fprintf(f, "--------------]\n");
13408 fen = PositionToFEN(currentMove, NULL, 1);
13409 fprintf(f, "%s\n", fen);
13417 ReloadCmailMsgEvent (int unregister)
13420 static char *inFilename = NULL;
13421 static char *outFilename;
13423 struct stat inbuf, outbuf;
13426 /* Any registered moves are unregistered if unregister is set, */
13427 /* i.e. invoked by the signal handler */
13429 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13430 cmailMoveRegistered[i] = FALSE;
13431 if (cmailCommentList[i] != NULL) {
13432 free(cmailCommentList[i]);
13433 cmailCommentList[i] = NULL;
13436 nCmailMovesRegistered = 0;
13439 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13440 cmailResult[i] = CMAIL_NOT_RESULT;
13444 if (inFilename == NULL) {
13445 /* Because the filenames are static they only get malloced once */
13446 /* and they never get freed */
13447 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13448 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13450 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13451 sprintf(outFilename, "%s.out", appData.cmailGameName);
13454 status = stat(outFilename, &outbuf);
13456 cmailMailedMove = FALSE;
13458 status = stat(inFilename, &inbuf);
13459 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13462 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13463 counts the games, notes how each one terminated, etc.
13465 It would be nice to remove this kludge and instead gather all
13466 the information while building the game list. (And to keep it
13467 in the game list nodes instead of having a bunch of fixed-size
13468 parallel arrays.) Note this will require getting each game's
13469 termination from the PGN tags, as the game list builder does
13470 not process the game moves. --mann
13472 cmailMsgLoaded = TRUE;
13473 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13475 /* Load first game in the file or popup game menu */
13476 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13478 #endif /* !WIN32 */
13486 char string[MSG_SIZ];
13488 if ( cmailMailedMove
13489 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13490 return TRUE; /* Allow free viewing */
13493 /* Unregister move to ensure that we don't leave RegisterMove */
13494 /* with the move registered when the conditions for registering no */
13496 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13497 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13498 nCmailMovesRegistered --;
13500 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13502 free(cmailCommentList[lastLoadGameNumber - 1]);
13503 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13507 if (cmailOldMove == -1) {
13508 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13512 if (currentMove > cmailOldMove + 1) {
13513 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13517 if (currentMove < cmailOldMove) {
13518 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13522 if (forwardMostMove > currentMove) {
13523 /* Silently truncate extra moves */
13527 if ( (currentMove == cmailOldMove + 1)
13528 || ( (currentMove == cmailOldMove)
13529 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13530 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13531 if (gameInfo.result != GameUnfinished) {
13532 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13535 if (commentList[currentMove] != NULL) {
13536 cmailCommentList[lastLoadGameNumber - 1]
13537 = StrSave(commentList[currentMove]);
13539 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13541 if (appData.debugMode)
13542 fprintf(debugFP, "Saving %s for game %d\n",
13543 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13545 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13547 f = fopen(string, "w");
13548 if (appData.oldSaveStyle) {
13549 SaveGameOldStyle(f); /* also closes the file */
13551 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13552 f = fopen(string, "w");
13553 SavePosition(f, 0, NULL); /* also closes the file */
13555 fprintf(f, "{--------------\n");
13556 PrintPosition(f, currentMove);
13557 fprintf(f, "--------------}\n\n");
13559 SaveGame(f, 0, NULL); /* also closes the file*/
13562 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13563 nCmailMovesRegistered ++;
13564 } else if (nCmailGames == 1) {
13565 DisplayError(_("You have not made a move yet"), 0);
13576 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13577 FILE *commandOutput;
13578 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13579 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13585 if (! cmailMsgLoaded) {
13586 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13590 if (nCmailGames == nCmailResults) {
13591 DisplayError(_("No unfinished games"), 0);
13595 #if CMAIL_PROHIBIT_REMAIL
13596 if (cmailMailedMove) {
13597 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);
13598 DisplayError(msg, 0);
13603 if (! (cmailMailedMove || RegisterMove())) return;
13605 if ( cmailMailedMove
13606 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13607 snprintf(string, MSG_SIZ, partCommandString,
13608 appData.debugMode ? " -v" : "", appData.cmailGameName);
13609 commandOutput = popen(string, "r");
13611 if (commandOutput == NULL) {
13612 DisplayError(_("Failed to invoke cmail"), 0);
13614 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13615 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13617 if (nBuffers > 1) {
13618 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13619 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13620 nBytes = MSG_SIZ - 1;
13622 (void) memcpy(msg, buffer, nBytes);
13624 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13626 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13627 cmailMailedMove = TRUE; /* Prevent >1 moves */
13630 for (i = 0; i < nCmailGames; i ++) {
13631 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13636 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13638 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13640 appData.cmailGameName,
13642 LoadGameFromFile(buffer, 1, buffer, FALSE);
13643 cmailMsgLoaded = FALSE;
13647 DisplayInformation(msg);
13648 pclose(commandOutput);
13651 if ((*cmailMsg) != '\0') {
13652 DisplayInformation(cmailMsg);
13657 #endif /* !WIN32 */
13666 int prependComma = 0;
13668 char string[MSG_SIZ]; /* Space for game-list */
13671 if (!cmailMsgLoaded) return "";
13673 if (cmailMailedMove) {
13674 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13676 /* Create a list of games left */
13677 snprintf(string, MSG_SIZ, "[");
13678 for (i = 0; i < nCmailGames; i ++) {
13679 if (! ( cmailMoveRegistered[i]
13680 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13681 if (prependComma) {
13682 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13684 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13688 strcat(string, number);
13691 strcat(string, "]");
13693 if (nCmailMovesRegistered + nCmailResults == 0) {
13694 switch (nCmailGames) {
13696 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13700 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13704 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13709 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13711 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13716 if (nCmailResults == nCmailGames) {
13717 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13719 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13724 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13736 if (gameMode == Training)
13737 SetTrainingModeOff();
13740 cmailMsgLoaded = FALSE;
13741 if (appData.icsActive) {
13742 SendToICS(ics_prefix);
13743 SendToICS("refresh\n");
13748 ExitEvent (int status)
13752 /* Give up on clean exit */
13756 /* Keep trying for clean exit */
13760 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13762 if (telnetISR != NULL) {
13763 RemoveInputSource(telnetISR);
13765 if (icsPR != NoProc) {
13766 DestroyChildProcess(icsPR, TRUE);
13769 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13770 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13772 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13773 /* make sure this other one finishes before killing it! */
13774 if(endingGame) { int count = 0;
13775 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13776 while(endingGame && count++ < 10) DoSleep(1);
13777 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13780 /* Kill off chess programs */
13781 if (first.pr != NoProc) {
13784 DoSleep( appData.delayBeforeQuit );
13785 SendToProgram("quit\n", &first);
13786 DoSleep( appData.delayAfterQuit );
13787 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13789 if (second.pr != NoProc) {
13790 DoSleep( appData.delayBeforeQuit );
13791 SendToProgram("quit\n", &second);
13792 DoSleep( appData.delayAfterQuit );
13793 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13795 if (first.isr != NULL) {
13796 RemoveInputSource(first.isr);
13798 if (second.isr != NULL) {
13799 RemoveInputSource(second.isr);
13802 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13803 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13805 ShutDownFrontEnd();
13810 PauseEngine (ChessProgramState *cps)
13812 SendToProgram("pause\n", cps);
13817 UnPauseEngine (ChessProgramState *cps)
13819 SendToProgram("resume\n", cps);
13826 if (appData.debugMode)
13827 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13831 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13833 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13834 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13835 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13837 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13838 HandleMachineMove(stashedInputMove, stalledEngine);
13839 stalledEngine = NULL;
13842 if (gameMode == MachinePlaysWhite ||
13843 gameMode == TwoMachinesPlay ||
13844 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13845 if(first.pause) UnPauseEngine(&first);
13846 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13847 if(second.pause) UnPauseEngine(&second);
13848 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13851 DisplayBothClocks();
13853 if (gameMode == PlayFromGameFile) {
13854 if (appData.timeDelay >= 0)
13855 AutoPlayGameLoop();
13856 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13857 Reset(FALSE, TRUE);
13858 SendToICS(ics_prefix);
13859 SendToICS("refresh\n");
13860 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13861 ForwardInner(forwardMostMove);
13863 pauseExamInvalid = FALSE;
13865 switch (gameMode) {
13869 pauseExamForwardMostMove = forwardMostMove;
13870 pauseExamInvalid = FALSE;
13873 case IcsPlayingWhite:
13874 case IcsPlayingBlack:
13878 case PlayFromGameFile:
13879 (void) StopLoadGameTimer();
13883 case BeginningOfGame:
13884 if (appData.icsActive) return;
13885 /* else fall through */
13886 case MachinePlaysWhite:
13887 case MachinePlaysBlack:
13888 case TwoMachinesPlay:
13889 if (forwardMostMove == 0)
13890 return; /* don't pause if no one has moved */
13891 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13892 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13893 if(onMove->pause) { // thinking engine can be paused
13894 PauseEngine(onMove); // do it
13895 if(onMove->other->pause) // pondering opponent can always be paused immediately
13896 PauseEngine(onMove->other);
13898 SendToProgram("easy\n", onMove->other);
13900 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13901 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13903 PauseEngine(&first);
13905 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13906 } else { // human on move, pause pondering by either method
13908 PauseEngine(&first);
13909 else if(appData.ponderNextMove)
13910 SendToProgram("easy\n", &first);
13913 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13923 EditCommentEvent ()
13925 char title[MSG_SIZ];
13927 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13928 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13930 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13931 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13932 parseList[currentMove - 1]);
13935 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13942 char *tags = PGNTags(&gameInfo);
13944 EditTagsPopUp(tags, NULL);
13951 if(second.analyzing) {
13952 SendToProgram("exit\n", &second);
13953 second.analyzing = FALSE;
13955 if (second.pr == NoProc) StartChessProgram(&second);
13956 InitChessProgram(&second, FALSE);
13957 FeedMovesToProgram(&second, currentMove);
13959 SendToProgram("analyze\n", &second);
13960 second.analyzing = TRUE;
13964 /* Toggle ShowThinking */
13966 ToggleShowThinking()
13968 appData.showThinking = !appData.showThinking;
13969 ShowThinkingEvent();
13973 AnalyzeModeEvent ()
13977 if (!first.analysisSupport) {
13978 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13979 DisplayError(buf, 0);
13982 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13983 if (appData.icsActive) {
13984 if (gameMode != IcsObserving) {
13985 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13986 DisplayError(buf, 0);
13988 if (appData.icsEngineAnalyze) {
13989 if (appData.debugMode)
13990 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13996 /* if enable, user wants to disable icsEngineAnalyze */
13997 if (appData.icsEngineAnalyze) {
14002 appData.icsEngineAnalyze = TRUE;
14003 if (appData.debugMode)
14004 fprintf(debugFP, "ICS engine analyze starting... \n");
14007 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14008 if (appData.noChessProgram || gameMode == AnalyzeMode)
14011 if (gameMode != AnalyzeFile) {
14012 if (!appData.icsEngineAnalyze) {
14014 if (gameMode != EditGame) return 0;
14016 if (!appData.showThinking) ToggleShowThinking();
14017 ResurrectChessProgram();
14018 SendToProgram("analyze\n", &first);
14019 first.analyzing = TRUE;
14020 /*first.maybeThinking = TRUE;*/
14021 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14022 EngineOutputPopUp();
14024 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14029 StartAnalysisClock();
14030 GetTimeMark(&lastNodeCountTime);
14036 AnalyzeFileEvent ()
14038 if (appData.noChessProgram || gameMode == AnalyzeFile)
14041 if (!first.analysisSupport) {
14043 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14044 DisplayError(buf, 0);
14048 if (gameMode != AnalyzeMode) {
14049 keepInfo = 1; // mere annotating should not alter PGN tags
14052 if (gameMode != EditGame) return;
14053 if (!appData.showThinking) ToggleShowThinking();
14054 ResurrectChessProgram();
14055 SendToProgram("analyze\n", &first);
14056 first.analyzing = TRUE;
14057 /*first.maybeThinking = TRUE;*/
14058 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14059 EngineOutputPopUp();
14061 gameMode = AnalyzeFile;
14065 StartAnalysisClock();
14066 GetTimeMark(&lastNodeCountTime);
14068 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14069 AnalysisPeriodicEvent(1);
14073 MachineWhiteEvent ()
14076 char *bookHit = NULL;
14078 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14082 if (gameMode == PlayFromGameFile ||
14083 gameMode == TwoMachinesPlay ||
14084 gameMode == Training ||
14085 gameMode == AnalyzeMode ||
14086 gameMode == EndOfGame)
14089 if (gameMode == EditPosition)
14090 EditPositionDone(TRUE);
14092 if (!WhiteOnMove(currentMove)) {
14093 DisplayError(_("It is not White's turn"), 0);
14097 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14100 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14101 gameMode == AnalyzeFile)
14104 ResurrectChessProgram(); /* in case it isn't running */
14105 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14106 gameMode = MachinePlaysWhite;
14109 gameMode = MachinePlaysWhite;
14113 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14115 if (first.sendName) {
14116 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14117 SendToProgram(buf, &first);
14119 if (first.sendTime) {
14120 if (first.useColors) {
14121 SendToProgram("black\n", &first); /*gnu kludge*/
14123 SendTimeRemaining(&first, TRUE);
14125 if (first.useColors) {
14126 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14128 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14129 SetMachineThinkingEnables();
14130 first.maybeThinking = TRUE;
14134 if (appData.autoFlipView && !flipView) {
14135 flipView = !flipView;
14136 DrawPosition(FALSE, NULL);
14137 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14140 if(bookHit) { // [HGM] book: simulate book reply
14141 static char bookMove[MSG_SIZ]; // a bit generous?
14143 programStats.nodes = programStats.depth = programStats.time =
14144 programStats.score = programStats.got_only_move = 0;
14145 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14147 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14148 strcat(bookMove, bookHit);
14149 HandleMachineMove(bookMove, &first);
14154 MachineBlackEvent ()
14157 char *bookHit = NULL;
14159 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14163 if (gameMode == PlayFromGameFile ||
14164 gameMode == TwoMachinesPlay ||
14165 gameMode == Training ||
14166 gameMode == AnalyzeMode ||
14167 gameMode == EndOfGame)
14170 if (gameMode == EditPosition)
14171 EditPositionDone(TRUE);
14173 if (WhiteOnMove(currentMove)) {
14174 DisplayError(_("It is not Black's turn"), 0);
14178 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14181 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14182 gameMode == AnalyzeFile)
14185 ResurrectChessProgram(); /* in case it isn't running */
14186 gameMode = MachinePlaysBlack;
14190 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14192 if (first.sendName) {
14193 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14194 SendToProgram(buf, &first);
14196 if (first.sendTime) {
14197 if (first.useColors) {
14198 SendToProgram("white\n", &first); /*gnu kludge*/
14200 SendTimeRemaining(&first, FALSE);
14202 if (first.useColors) {
14203 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14205 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14206 SetMachineThinkingEnables();
14207 first.maybeThinking = TRUE;
14210 if (appData.autoFlipView && flipView) {
14211 flipView = !flipView;
14212 DrawPosition(FALSE, NULL);
14213 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14215 if(bookHit) { // [HGM] book: simulate book reply
14216 static char bookMove[MSG_SIZ]; // a bit generous?
14218 programStats.nodes = programStats.depth = programStats.time =
14219 programStats.score = programStats.got_only_move = 0;
14220 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14222 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14223 strcat(bookMove, bookHit);
14224 HandleMachineMove(bookMove, &first);
14230 DisplayTwoMachinesTitle ()
14233 if (appData.matchGames > 0) {
14234 if(appData.tourneyFile[0]) {
14235 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14236 gameInfo.white, _("vs."), gameInfo.black,
14237 nextGame+1, appData.matchGames+1,
14238 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14240 if (first.twoMachinesColor[0] == 'w') {
14241 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14242 gameInfo.white, _("vs."), gameInfo.black,
14243 first.matchWins, second.matchWins,
14244 matchGame - 1 - (first.matchWins + second.matchWins));
14246 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14247 gameInfo.white, _("vs."), gameInfo.black,
14248 second.matchWins, first.matchWins,
14249 matchGame - 1 - (first.matchWins + second.matchWins));
14252 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14258 SettingsMenuIfReady ()
14260 if (second.lastPing != second.lastPong) {
14261 DisplayMessage("", _("Waiting for second chess program"));
14262 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14266 DisplayMessage("", "");
14267 SettingsPopUp(&second);
14271 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14274 if (cps->pr == NoProc) {
14275 StartChessProgram(cps);
14276 if (cps->protocolVersion == 1) {
14278 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14280 /* kludge: allow timeout for initial "feature" command */
14281 if(retry != TwoMachinesEventIfReady) FreezeUI();
14282 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14283 DisplayMessage("", buf);
14284 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14292 TwoMachinesEvent P((void))
14296 ChessProgramState *onmove;
14297 char *bookHit = NULL;
14298 static int stalling = 0;
14302 if (appData.noChessProgram) return;
14304 switch (gameMode) {
14305 case TwoMachinesPlay:
14307 case MachinePlaysWhite:
14308 case MachinePlaysBlack:
14309 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14310 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14314 case BeginningOfGame:
14315 case PlayFromGameFile:
14318 if (gameMode != EditGame) return;
14321 EditPositionDone(TRUE);
14332 // forwardMostMove = currentMove;
14333 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14334 startingEngine = TRUE;
14336 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14338 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14339 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14340 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14343 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14345 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14346 startingEngine = FALSE;
14347 DisplayError("second engine does not play this", 0);
14352 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14353 SendToProgram("force\n", &second);
14355 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14358 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14359 if(appData.matchPause>10000 || appData.matchPause<10)
14360 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14361 wait = SubtractTimeMarks(&now, &pauseStart);
14362 if(wait < appData.matchPause) {
14363 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14366 // we are now committed to starting the game
14368 DisplayMessage("", "");
14369 if (startedFromSetupPosition) {
14370 SendBoard(&second, backwardMostMove);
14371 if (appData.debugMode) {
14372 fprintf(debugFP, "Two Machines\n");
14375 for (i = backwardMostMove; i < forwardMostMove; i++) {
14376 SendMoveToProgram(i, &second);
14379 gameMode = TwoMachinesPlay;
14380 pausing = startingEngine = FALSE;
14381 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14383 DisplayTwoMachinesTitle();
14385 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14390 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14391 SendToProgram(first.computerString, &first);
14392 if (first.sendName) {
14393 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14394 SendToProgram(buf, &first);
14396 SendToProgram(second.computerString, &second);
14397 if (second.sendName) {
14398 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14399 SendToProgram(buf, &second);
14403 if (!first.sendTime || !second.sendTime) {
14404 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14405 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14407 if (onmove->sendTime) {
14408 if (onmove->useColors) {
14409 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14411 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14413 if (onmove->useColors) {
14414 SendToProgram(onmove->twoMachinesColor, onmove);
14416 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14417 // SendToProgram("go\n", onmove);
14418 onmove->maybeThinking = TRUE;
14419 SetMachineThinkingEnables();
14423 if(bookHit) { // [HGM] book: simulate book reply
14424 static char bookMove[MSG_SIZ]; // a bit generous?
14426 programStats.nodes = programStats.depth = programStats.time =
14427 programStats.score = programStats.got_only_move = 0;
14428 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14430 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14431 strcat(bookMove, bookHit);
14432 savedMessage = bookMove; // args for deferred call
14433 savedState = onmove;
14434 ScheduleDelayedEvent(DeferredBookMove, 1);
14441 if (gameMode == Training) {
14442 SetTrainingModeOff();
14443 gameMode = PlayFromGameFile;
14444 DisplayMessage("", _("Training mode off"));
14446 gameMode = Training;
14447 animateTraining = appData.animate;
14449 /* make sure we are not already at the end of the game */
14450 if (currentMove < forwardMostMove) {
14451 SetTrainingModeOn();
14452 DisplayMessage("", _("Training mode on"));
14454 gameMode = PlayFromGameFile;
14455 DisplayError(_("Already at end of game"), 0);
14464 if (!appData.icsActive) return;
14465 switch (gameMode) {
14466 case IcsPlayingWhite:
14467 case IcsPlayingBlack:
14470 case BeginningOfGame:
14478 EditPositionDone(TRUE);
14491 gameMode = IcsIdle;
14501 switch (gameMode) {
14503 SetTrainingModeOff();
14505 case MachinePlaysWhite:
14506 case MachinePlaysBlack:
14507 case BeginningOfGame:
14508 SendToProgram("force\n", &first);
14509 SetUserThinkingEnables();
14511 case PlayFromGameFile:
14512 (void) StopLoadGameTimer();
14513 if (gameFileFP != NULL) {
14518 EditPositionDone(TRUE);
14523 SendToProgram("force\n", &first);
14525 case TwoMachinesPlay:
14526 GameEnds(EndOfFile, NULL, GE_PLAYER);
14527 ResurrectChessProgram();
14528 SetUserThinkingEnables();
14531 ResurrectChessProgram();
14533 case IcsPlayingBlack:
14534 case IcsPlayingWhite:
14535 DisplayError(_("Warning: You are still playing a game"), 0);
14538 DisplayError(_("Warning: You are still observing a game"), 0);
14541 DisplayError(_("Warning: You are still examining a game"), 0);
14552 first.offeredDraw = second.offeredDraw = 0;
14554 if (gameMode == PlayFromGameFile) {
14555 whiteTimeRemaining = timeRemaining[0][currentMove];
14556 blackTimeRemaining = timeRemaining[1][currentMove];
14560 if (gameMode == MachinePlaysWhite ||
14561 gameMode == MachinePlaysBlack ||
14562 gameMode == TwoMachinesPlay ||
14563 gameMode == EndOfGame) {
14564 i = forwardMostMove;
14565 while (i > currentMove) {
14566 SendToProgram("undo\n", &first);
14569 if(!adjustedClock) {
14570 whiteTimeRemaining = timeRemaining[0][currentMove];
14571 blackTimeRemaining = timeRemaining[1][currentMove];
14572 DisplayBothClocks();
14574 if (whiteFlag || blackFlag) {
14575 whiteFlag = blackFlag = 0;
14580 gameMode = EditGame;
14587 EditPositionEvent ()
14589 if (gameMode == EditPosition) {
14595 if (gameMode != EditGame) return;
14597 gameMode = EditPosition;
14600 if (currentMove > 0)
14601 CopyBoard(boards[0], boards[currentMove]);
14603 blackPlaysFirst = !WhiteOnMove(currentMove);
14605 currentMove = forwardMostMove = backwardMostMove = 0;
14606 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14608 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14614 /* [DM] icsEngineAnalyze - possible call from other functions */
14615 if (appData.icsEngineAnalyze) {
14616 appData.icsEngineAnalyze = FALSE;
14618 DisplayMessage("",_("Close ICS engine analyze..."));
14620 if (first.analysisSupport && first.analyzing) {
14621 SendToBoth("exit\n");
14622 first.analyzing = second.analyzing = FALSE;
14624 thinkOutput[0] = NULLCHAR;
14628 EditPositionDone (Boolean fakeRights)
14630 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14632 startedFromSetupPosition = TRUE;
14633 InitChessProgram(&first, FALSE);
14634 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14635 boards[0][EP_STATUS] = EP_NONE;
14636 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14637 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14638 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14639 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14640 } else boards[0][CASTLING][2] = NoRights;
14641 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14642 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14643 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14644 } else boards[0][CASTLING][5] = NoRights;
14645 if(gameInfo.variant == VariantSChess) {
14647 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14648 boards[0][VIRGIN][i] = 0;
14649 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14650 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14654 SendToProgram("force\n", &first);
14655 if (blackPlaysFirst) {
14656 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14657 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14658 currentMove = forwardMostMove = backwardMostMove = 1;
14659 CopyBoard(boards[1], boards[0]);
14661 currentMove = forwardMostMove = backwardMostMove = 0;
14663 SendBoard(&first, forwardMostMove);
14664 if (appData.debugMode) {
14665 fprintf(debugFP, "EditPosDone\n");
14668 DisplayMessage("", "");
14669 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14670 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14671 gameMode = EditGame;
14673 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14674 ClearHighlights(); /* [AS] */
14677 /* Pause for `ms' milliseconds */
14678 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14680 TimeDelay (long ms)
14687 } while (SubtractTimeMarks(&m2, &m1) < ms);
14690 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14692 SendMultiLineToICS (char *buf)
14694 char temp[MSG_SIZ+1], *p;
14701 strncpy(temp, buf, len);
14706 if (*p == '\n' || *p == '\r')
14711 strcat(temp, "\n");
14713 SendToPlayer(temp, strlen(temp));
14717 SetWhiteToPlayEvent ()
14719 if (gameMode == EditPosition) {
14720 blackPlaysFirst = FALSE;
14721 DisplayBothClocks(); /* works because currentMove is 0 */
14722 } else if (gameMode == IcsExamining) {
14723 SendToICS(ics_prefix);
14724 SendToICS("tomove white\n");
14729 SetBlackToPlayEvent ()
14731 if (gameMode == EditPosition) {
14732 blackPlaysFirst = TRUE;
14733 currentMove = 1; /* kludge */
14734 DisplayBothClocks();
14736 } else if (gameMode == IcsExamining) {
14737 SendToICS(ics_prefix);
14738 SendToICS("tomove black\n");
14743 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14746 ChessSquare piece = boards[0][y][x];
14747 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14748 static int lastVariant;
14750 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14752 switch (selection) {
14754 CopyBoard(currentBoard, boards[0]);
14755 CopyBoard(menuBoard, initialPosition);
14756 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14757 SendToICS(ics_prefix);
14758 SendToICS("bsetup clear\n");
14759 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14760 SendToICS(ics_prefix);
14761 SendToICS("clearboard\n");
14764 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14765 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14766 for (y = 0; y < BOARD_HEIGHT; y++) {
14767 if (gameMode == IcsExamining) {
14768 if (boards[currentMove][y][x] != EmptySquare) {
14769 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14774 if(boards[0][y][x] != p) nonEmpty++;
14775 boards[0][y][x] = p;
14778 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14780 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14781 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
14782 ChessSquare p = menuBoard[0][x];
14783 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14784 p = menuBoard[BOARD_HEIGHT-1][x];
14785 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14787 DisplayMessage("Clicking clock again restores position", "");
14788 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14789 if(!nonEmpty) { // asked to clear an empty board
14790 CopyBoard(boards[0], menuBoard);
14792 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14793 CopyBoard(boards[0], initialPosition);
14795 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14796 && !CompareBoards(nullBoard, erasedBoard)) {
14797 CopyBoard(boards[0], erasedBoard);
14799 CopyBoard(erasedBoard, currentBoard);
14803 if (gameMode == EditPosition) {
14804 DrawPosition(FALSE, boards[0]);
14809 SetWhiteToPlayEvent();
14813 SetBlackToPlayEvent();
14817 if (gameMode == IcsExamining) {
14818 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14819 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14822 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14823 if(x == BOARD_LEFT-2) {
14824 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14825 boards[0][y][1] = 0;
14827 if(x == BOARD_RGHT+1) {
14828 if(y >= gameInfo.holdingsSize) break;
14829 boards[0][y][BOARD_WIDTH-2] = 0;
14832 boards[0][y][x] = EmptySquare;
14833 DrawPosition(FALSE, boards[0]);
14838 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14839 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14840 selection = (ChessSquare) (PROMOTED piece);
14841 } else if(piece == EmptySquare) selection = WhiteSilver;
14842 else selection = (ChessSquare)((int)piece - 1);
14846 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14847 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14848 selection = (ChessSquare) (DEMOTED piece);
14849 } else if(piece == EmptySquare) selection = BlackSilver;
14850 else selection = (ChessSquare)((int)piece + 1);
14855 if(gameInfo.variant == VariantShatranj ||
14856 gameInfo.variant == VariantXiangqi ||
14857 gameInfo.variant == VariantCourier ||
14858 gameInfo.variant == VariantASEAN ||
14859 gameInfo.variant == VariantMakruk )
14860 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14865 if(gameInfo.variant == VariantXiangqi)
14866 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14867 if(gameInfo.variant == VariantKnightmate)
14868 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14871 if (gameMode == IcsExamining) {
14872 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14873 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14874 PieceToChar(selection), AAA + x, ONE + y);
14877 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14879 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14880 n = PieceToNumber(selection - BlackPawn);
14881 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14882 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14883 boards[0][BOARD_HEIGHT-1-n][1]++;
14885 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14886 n = PieceToNumber(selection);
14887 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14888 boards[0][n][BOARD_WIDTH-1] = selection;
14889 boards[0][n][BOARD_WIDTH-2]++;
14892 boards[0][y][x] = selection;
14893 DrawPosition(TRUE, boards[0]);
14895 fromX = fromY = -1;
14903 DropMenuEvent (ChessSquare selection, int x, int y)
14905 ChessMove moveType;
14907 switch (gameMode) {
14908 case IcsPlayingWhite:
14909 case MachinePlaysBlack:
14910 if (!WhiteOnMove(currentMove)) {
14911 DisplayMoveError(_("It is Black's turn"));
14914 moveType = WhiteDrop;
14916 case IcsPlayingBlack:
14917 case MachinePlaysWhite:
14918 if (WhiteOnMove(currentMove)) {
14919 DisplayMoveError(_("It is White's turn"));
14922 moveType = BlackDrop;
14925 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14931 if (moveType == BlackDrop && selection < BlackPawn) {
14932 selection = (ChessSquare) ((int) selection
14933 + (int) BlackPawn - (int) WhitePawn);
14935 if (boards[currentMove][y][x] != EmptySquare) {
14936 DisplayMoveError(_("That square is occupied"));
14940 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14946 /* Accept a pending offer of any kind from opponent */
14948 if (appData.icsActive) {
14949 SendToICS(ics_prefix);
14950 SendToICS("accept\n");
14951 } else if (cmailMsgLoaded) {
14952 if (currentMove == cmailOldMove &&
14953 commentList[cmailOldMove] != NULL &&
14954 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14955 "Black offers a draw" : "White offers a draw")) {
14957 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14958 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14960 DisplayError(_("There is no pending offer on this move"), 0);
14961 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14964 /* Not used for offers from chess program */
14971 /* Decline a pending offer of any kind from opponent */
14973 if (appData.icsActive) {
14974 SendToICS(ics_prefix);
14975 SendToICS("decline\n");
14976 } else if (cmailMsgLoaded) {
14977 if (currentMove == cmailOldMove &&
14978 commentList[cmailOldMove] != NULL &&
14979 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14980 "Black offers a draw" : "White offers a draw")) {
14982 AppendComment(cmailOldMove, "Draw declined", TRUE);
14983 DisplayComment(cmailOldMove - 1, "Draw declined");
14986 DisplayError(_("There is no pending offer on this move"), 0);
14989 /* Not used for offers from chess program */
14996 /* Issue ICS rematch command */
14997 if (appData.icsActive) {
14998 SendToICS(ics_prefix);
14999 SendToICS("rematch\n");
15006 /* Call your opponent's flag (claim a win on time) */
15007 if (appData.icsActive) {
15008 SendToICS(ics_prefix);
15009 SendToICS("flag\n");
15011 switch (gameMode) {
15014 case MachinePlaysWhite:
15017 GameEnds(GameIsDrawn, "Both players ran out of time",
15020 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15022 DisplayError(_("Your opponent is not out of time"), 0);
15025 case MachinePlaysBlack:
15028 GameEnds(GameIsDrawn, "Both players ran out of time",
15031 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15033 DisplayError(_("Your opponent is not out of time"), 0);
15041 ClockClick (int which)
15042 { // [HGM] code moved to back-end from winboard.c
15043 if(which) { // black clock
15044 if (gameMode == EditPosition || gameMode == IcsExamining) {
15045 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15046 SetBlackToPlayEvent();
15047 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15048 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15049 } else if (shiftKey) {
15050 AdjustClock(which, -1);
15051 } else if (gameMode == IcsPlayingWhite ||
15052 gameMode == MachinePlaysBlack) {
15055 } else { // white clock
15056 if (gameMode == EditPosition || gameMode == IcsExamining) {
15057 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15058 SetWhiteToPlayEvent();
15059 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15060 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15061 } else if (shiftKey) {
15062 AdjustClock(which, -1);
15063 } else if (gameMode == IcsPlayingBlack ||
15064 gameMode == MachinePlaysWhite) {
15073 /* Offer draw or accept pending draw offer from opponent */
15075 if (appData.icsActive) {
15076 /* Note: tournament rules require draw offers to be
15077 made after you make your move but before you punch
15078 your clock. Currently ICS doesn't let you do that;
15079 instead, you immediately punch your clock after making
15080 a move, but you can offer a draw at any time. */
15082 SendToICS(ics_prefix);
15083 SendToICS("draw\n");
15084 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15085 } else if (cmailMsgLoaded) {
15086 if (currentMove == cmailOldMove &&
15087 commentList[cmailOldMove] != NULL &&
15088 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15089 "Black offers a draw" : "White offers a draw")) {
15090 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15091 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15092 } else if (currentMove == cmailOldMove + 1) {
15093 char *offer = WhiteOnMove(cmailOldMove) ?
15094 "White offers a draw" : "Black offers a draw";
15095 AppendComment(currentMove, offer, TRUE);
15096 DisplayComment(currentMove - 1, offer);
15097 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15099 DisplayError(_("You must make your move before offering a draw"), 0);
15100 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15102 } else if (first.offeredDraw) {
15103 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15105 if (first.sendDrawOffers) {
15106 SendToProgram("draw\n", &first);
15107 userOfferedDraw = TRUE;
15115 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15117 if (appData.icsActive) {
15118 SendToICS(ics_prefix);
15119 SendToICS("adjourn\n");
15121 /* Currently GNU Chess doesn't offer or accept Adjourns */
15129 /* Offer Abort or accept pending Abort offer from opponent */
15131 if (appData.icsActive) {
15132 SendToICS(ics_prefix);
15133 SendToICS("abort\n");
15135 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15142 /* Resign. You can do this even if it's not your turn. */
15144 if (appData.icsActive) {
15145 SendToICS(ics_prefix);
15146 SendToICS("resign\n");
15148 switch (gameMode) {
15149 case MachinePlaysWhite:
15150 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15152 case MachinePlaysBlack:
15153 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15156 if (cmailMsgLoaded) {
15158 if (WhiteOnMove(cmailOldMove)) {
15159 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15161 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15163 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15174 StopObservingEvent ()
15176 /* Stop observing current games */
15177 SendToICS(ics_prefix);
15178 SendToICS("unobserve\n");
15182 StopExaminingEvent ()
15184 /* Stop observing current game */
15185 SendToICS(ics_prefix);
15186 SendToICS("unexamine\n");
15190 ForwardInner (int target)
15192 int limit; int oldSeekGraphUp = seekGraphUp;
15194 if (appData.debugMode)
15195 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15196 target, currentMove, forwardMostMove);
15198 if (gameMode == EditPosition)
15201 seekGraphUp = FALSE;
15202 MarkTargetSquares(1);
15204 if (gameMode == PlayFromGameFile && !pausing)
15207 if (gameMode == IcsExamining && pausing)
15208 limit = pauseExamForwardMostMove;
15210 limit = forwardMostMove;
15212 if (target > limit) target = limit;
15214 if (target > 0 && moveList[target - 1][0]) {
15215 int fromX, fromY, toX, toY;
15216 toX = moveList[target - 1][2] - AAA;
15217 toY = moveList[target - 1][3] - ONE;
15218 if (moveList[target - 1][1] == '@') {
15219 if (appData.highlightLastMove) {
15220 SetHighlights(-1, -1, toX, toY);
15223 fromX = moveList[target - 1][0] - AAA;
15224 fromY = moveList[target - 1][1] - ONE;
15225 if (target == currentMove + 1) {
15226 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15228 if (appData.highlightLastMove) {
15229 SetHighlights(fromX, fromY, toX, toY);
15233 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15234 gameMode == Training || gameMode == PlayFromGameFile ||
15235 gameMode == AnalyzeFile) {
15236 while (currentMove < target) {
15237 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15238 SendMoveToProgram(currentMove++, &first);
15241 currentMove = target;
15244 if (gameMode == EditGame || gameMode == EndOfGame) {
15245 whiteTimeRemaining = timeRemaining[0][currentMove];
15246 blackTimeRemaining = timeRemaining[1][currentMove];
15248 DisplayBothClocks();
15249 DisplayMove(currentMove - 1);
15250 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15251 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15252 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15253 DisplayComment(currentMove - 1, commentList[currentMove]);
15255 ClearMap(); // [HGM] exclude: invalidate map
15262 if (gameMode == IcsExamining && !pausing) {
15263 SendToICS(ics_prefix);
15264 SendToICS("forward\n");
15266 ForwardInner(currentMove + 1);
15273 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15274 /* to optimze, we temporarily turn off analysis mode while we feed
15275 * the remaining moves to the engine. Otherwise we get analysis output
15278 if (first.analysisSupport) {
15279 SendToProgram("exit\nforce\n", &first);
15280 first.analyzing = FALSE;
15284 if (gameMode == IcsExamining && !pausing) {
15285 SendToICS(ics_prefix);
15286 SendToICS("forward 999999\n");
15288 ForwardInner(forwardMostMove);
15291 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15292 /* we have fed all the moves, so reactivate analysis mode */
15293 SendToProgram("analyze\n", &first);
15294 first.analyzing = TRUE;
15295 /*first.maybeThinking = TRUE;*/
15296 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15301 BackwardInner (int target)
15303 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15305 if (appData.debugMode)
15306 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15307 target, currentMove, forwardMostMove);
15309 if (gameMode == EditPosition) return;
15310 seekGraphUp = FALSE;
15311 MarkTargetSquares(1);
15312 if (currentMove <= backwardMostMove) {
15314 DrawPosition(full_redraw, boards[currentMove]);
15317 if (gameMode == PlayFromGameFile && !pausing)
15320 if (moveList[target][0]) {
15321 int fromX, fromY, toX, toY;
15322 toX = moveList[target][2] - AAA;
15323 toY = moveList[target][3] - ONE;
15324 if (moveList[target][1] == '@') {
15325 if (appData.highlightLastMove) {
15326 SetHighlights(-1, -1, toX, toY);
15329 fromX = moveList[target][0] - AAA;
15330 fromY = moveList[target][1] - ONE;
15331 if (target == currentMove - 1) {
15332 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15334 if (appData.highlightLastMove) {
15335 SetHighlights(fromX, fromY, toX, toY);
15339 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15340 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15341 while (currentMove > target) {
15342 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15343 // null move cannot be undone. Reload program with move history before it.
15345 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15346 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15348 SendBoard(&first, i);
15349 if(second.analyzing) SendBoard(&second, i);
15350 for(currentMove=i; currentMove<target; currentMove++) {
15351 SendMoveToProgram(currentMove, &first);
15352 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15356 SendToBoth("undo\n");
15360 currentMove = target;
15363 if (gameMode == EditGame || gameMode == EndOfGame) {
15364 whiteTimeRemaining = timeRemaining[0][currentMove];
15365 blackTimeRemaining = timeRemaining[1][currentMove];
15367 DisplayBothClocks();
15368 DisplayMove(currentMove - 1);
15369 DrawPosition(full_redraw, boards[currentMove]);
15370 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15371 // [HGM] PV info: routine tests if comment empty
15372 DisplayComment(currentMove - 1, commentList[currentMove]);
15373 ClearMap(); // [HGM] exclude: invalidate map
15379 if (gameMode == IcsExamining && !pausing) {
15380 SendToICS(ics_prefix);
15381 SendToICS("backward\n");
15383 BackwardInner(currentMove - 1);
15390 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15391 /* to optimize, we temporarily turn off analysis mode while we undo
15392 * all the moves. Otherwise we get analysis output after each undo.
15394 if (first.analysisSupport) {
15395 SendToProgram("exit\nforce\n", &first);
15396 first.analyzing = FALSE;
15400 if (gameMode == IcsExamining && !pausing) {
15401 SendToICS(ics_prefix);
15402 SendToICS("backward 999999\n");
15404 BackwardInner(backwardMostMove);
15407 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15408 /* we have fed all the moves, so reactivate analysis mode */
15409 SendToProgram("analyze\n", &first);
15410 first.analyzing = TRUE;
15411 /*first.maybeThinking = TRUE;*/
15412 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15419 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15420 if (to >= forwardMostMove) to = forwardMostMove;
15421 if (to <= backwardMostMove) to = backwardMostMove;
15422 if (to < currentMove) {
15430 RevertEvent (Boolean annotate)
15432 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15435 if (gameMode != IcsExamining) {
15436 DisplayError(_("You are not examining a game"), 0);
15440 DisplayError(_("You can't revert while pausing"), 0);
15443 SendToICS(ics_prefix);
15444 SendToICS("revert\n");
15448 RetractMoveEvent ()
15450 switch (gameMode) {
15451 case MachinePlaysWhite:
15452 case MachinePlaysBlack:
15453 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15454 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15457 if (forwardMostMove < 2) return;
15458 currentMove = forwardMostMove = forwardMostMove - 2;
15459 whiteTimeRemaining = timeRemaining[0][currentMove];
15460 blackTimeRemaining = timeRemaining[1][currentMove];
15461 DisplayBothClocks();
15462 DisplayMove(currentMove - 1);
15463 ClearHighlights();/*!! could figure this out*/
15464 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15465 SendToProgram("remove\n", &first);
15466 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15469 case BeginningOfGame:
15473 case IcsPlayingWhite:
15474 case IcsPlayingBlack:
15475 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15476 SendToICS(ics_prefix);
15477 SendToICS("takeback 2\n");
15479 SendToICS(ics_prefix);
15480 SendToICS("takeback 1\n");
15489 ChessProgramState *cps;
15491 switch (gameMode) {
15492 case MachinePlaysWhite:
15493 if (!WhiteOnMove(forwardMostMove)) {
15494 DisplayError(_("It is your turn"), 0);
15499 case MachinePlaysBlack:
15500 if (WhiteOnMove(forwardMostMove)) {
15501 DisplayError(_("It is your turn"), 0);
15506 case TwoMachinesPlay:
15507 if (WhiteOnMove(forwardMostMove) ==
15508 (first.twoMachinesColor[0] == 'w')) {
15514 case BeginningOfGame:
15518 SendToProgram("?\n", cps);
15522 TruncateGameEvent ()
15525 if (gameMode != EditGame) return;
15532 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15533 if (forwardMostMove > currentMove) {
15534 if (gameInfo.resultDetails != NULL) {
15535 free(gameInfo.resultDetails);
15536 gameInfo.resultDetails = NULL;
15537 gameInfo.result = GameUnfinished;
15539 forwardMostMove = currentMove;
15540 HistorySet(parseList, backwardMostMove, forwardMostMove,
15548 if (appData.noChessProgram) return;
15549 switch (gameMode) {
15550 case MachinePlaysWhite:
15551 if (WhiteOnMove(forwardMostMove)) {
15552 DisplayError(_("Wait until your turn."), 0);
15556 case BeginningOfGame:
15557 case MachinePlaysBlack:
15558 if (!WhiteOnMove(forwardMostMove)) {
15559 DisplayError(_("Wait until your turn."), 0);
15564 DisplayError(_("No hint available"), 0);
15567 SendToProgram("hint\n", &first);
15568 hintRequested = TRUE;
15574 ListGame * lg = (ListGame *) gameList.head;
15577 static int secondTime = FALSE;
15579 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15580 DisplayError(_("Game list not loaded or empty"), 0);
15584 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15587 DisplayNote(_("Book file exists! Try again for overwrite."));
15591 creatingBook = TRUE;
15592 secondTime = FALSE;
15594 /* Get list size */
15595 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15596 LoadGame(f, nItem, "", TRUE);
15597 AddGameToBook(TRUE);
15598 lg = (ListGame *) lg->node.succ;
15601 creatingBook = FALSE;
15608 if (appData.noChessProgram) return;
15609 switch (gameMode) {
15610 case MachinePlaysWhite:
15611 if (WhiteOnMove(forwardMostMove)) {
15612 DisplayError(_("Wait until your turn."), 0);
15616 case BeginningOfGame:
15617 case MachinePlaysBlack:
15618 if (!WhiteOnMove(forwardMostMove)) {
15619 DisplayError(_("Wait until your turn."), 0);
15624 EditPositionDone(TRUE);
15626 case TwoMachinesPlay:
15631 SendToProgram("bk\n", &first);
15632 bookOutput[0] = NULLCHAR;
15633 bookRequested = TRUE;
15639 char *tags = PGNTags(&gameInfo);
15640 TagsPopUp(tags, CmailMsg());
15644 /* end button procedures */
15647 PrintPosition (FILE *fp, int move)
15651 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15652 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15653 char c = PieceToChar(boards[move][i][j]);
15654 fputc(c == 'x' ? '.' : c, fp);
15655 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15658 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15659 fprintf(fp, "white to play\n");
15661 fprintf(fp, "black to play\n");
15665 PrintOpponents (FILE *fp)
15667 if (gameInfo.white != NULL) {
15668 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15674 /* Find last component of program's own name, using some heuristics */
15676 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15679 int local = (strcmp(host, "localhost") == 0);
15680 while (!local && (p = strchr(prog, ';')) != NULL) {
15682 while (*p == ' ') p++;
15685 if (*prog == '"' || *prog == '\'') {
15686 q = strchr(prog + 1, *prog);
15688 q = strchr(prog, ' ');
15690 if (q == NULL) q = prog + strlen(prog);
15692 while (p >= prog && *p != '/' && *p != '\\') p--;
15694 if(p == prog && *p == '"') p++;
15696 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15697 memcpy(buf, p, q - p);
15698 buf[q - p] = NULLCHAR;
15706 TimeControlTagValue ()
15709 if (!appData.clockMode) {
15710 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15711 } else if (movesPerSession > 0) {
15712 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15713 } else if (timeIncrement == 0) {
15714 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15716 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15718 return StrSave(buf);
15724 /* This routine is used only for certain modes */
15725 VariantClass v = gameInfo.variant;
15726 ChessMove r = GameUnfinished;
15729 if(keepInfo) return;
15731 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15732 r = gameInfo.result;
15733 p = gameInfo.resultDetails;
15734 gameInfo.resultDetails = NULL;
15736 ClearGameInfo(&gameInfo);
15737 gameInfo.variant = v;
15739 switch (gameMode) {
15740 case MachinePlaysWhite:
15741 gameInfo.event = StrSave( appData.pgnEventHeader );
15742 gameInfo.site = StrSave(HostName());
15743 gameInfo.date = PGNDate();
15744 gameInfo.round = StrSave("-");
15745 gameInfo.white = StrSave(first.tidy);
15746 gameInfo.black = StrSave(UserName());
15747 gameInfo.timeControl = TimeControlTagValue();
15750 case MachinePlaysBlack:
15751 gameInfo.event = StrSave( appData.pgnEventHeader );
15752 gameInfo.site = StrSave(HostName());
15753 gameInfo.date = PGNDate();
15754 gameInfo.round = StrSave("-");
15755 gameInfo.white = StrSave(UserName());
15756 gameInfo.black = StrSave(first.tidy);
15757 gameInfo.timeControl = TimeControlTagValue();
15760 case TwoMachinesPlay:
15761 gameInfo.event = StrSave( appData.pgnEventHeader );
15762 gameInfo.site = StrSave(HostName());
15763 gameInfo.date = PGNDate();
15766 snprintf(buf, MSG_SIZ, "%d", roundNr);
15767 gameInfo.round = StrSave(buf);
15769 gameInfo.round = StrSave("-");
15771 if (first.twoMachinesColor[0] == 'w') {
15772 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15773 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15775 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15776 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15778 gameInfo.timeControl = TimeControlTagValue();
15782 gameInfo.event = StrSave("Edited game");
15783 gameInfo.site = StrSave(HostName());
15784 gameInfo.date = PGNDate();
15785 gameInfo.round = StrSave("-");
15786 gameInfo.white = StrSave("-");
15787 gameInfo.black = StrSave("-");
15788 gameInfo.result = r;
15789 gameInfo.resultDetails = p;
15793 gameInfo.event = StrSave("Edited position");
15794 gameInfo.site = StrSave(HostName());
15795 gameInfo.date = PGNDate();
15796 gameInfo.round = StrSave("-");
15797 gameInfo.white = StrSave("-");
15798 gameInfo.black = StrSave("-");
15801 case IcsPlayingWhite:
15802 case IcsPlayingBlack:
15807 case PlayFromGameFile:
15808 gameInfo.event = StrSave("Game from non-PGN file");
15809 gameInfo.site = StrSave(HostName());
15810 gameInfo.date = PGNDate();
15811 gameInfo.round = StrSave("-");
15812 gameInfo.white = StrSave("?");
15813 gameInfo.black = StrSave("?");
15822 ReplaceComment (int index, char *text)
15828 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15829 pvInfoList[index-1].depth == len &&
15830 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15831 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15832 while (*text == '\n') text++;
15833 len = strlen(text);
15834 while (len > 0 && text[len - 1] == '\n') len--;
15836 if (commentList[index] != NULL)
15837 free(commentList[index]);
15840 commentList[index] = NULL;
15843 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15844 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15845 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15846 commentList[index] = (char *) malloc(len + 2);
15847 strncpy(commentList[index], text, len);
15848 commentList[index][len] = '\n';
15849 commentList[index][len + 1] = NULLCHAR;
15851 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15853 commentList[index] = (char *) malloc(len + 7);
15854 safeStrCpy(commentList[index], "{\n", 3);
15855 safeStrCpy(commentList[index]+2, text, len+1);
15856 commentList[index][len+2] = NULLCHAR;
15857 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15858 strcat(commentList[index], "\n}\n");
15863 CrushCRs (char *text)
15871 if (ch == '\r') continue;
15873 } while (ch != '\0');
15877 AppendComment (int index, char *text, Boolean addBraces)
15878 /* addBraces tells if we should add {} */
15883 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15884 if(addBraces == 3) addBraces = 0; else // force appending literally
15885 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15888 while (*text == '\n') text++;
15889 len = strlen(text);
15890 while (len > 0 && text[len - 1] == '\n') len--;
15891 text[len] = NULLCHAR;
15893 if (len == 0) return;
15895 if (commentList[index] != NULL) {
15896 Boolean addClosingBrace = addBraces;
15897 old = commentList[index];
15898 oldlen = strlen(old);
15899 while(commentList[index][oldlen-1] == '\n')
15900 commentList[index][--oldlen] = NULLCHAR;
15901 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15902 safeStrCpy(commentList[index], old, oldlen + len + 6);
15904 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15905 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15906 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15907 while (*text == '\n') { text++; len--; }
15908 commentList[index][--oldlen] = NULLCHAR;
15910 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15911 else strcat(commentList[index], "\n");
15912 strcat(commentList[index], text);
15913 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15914 else strcat(commentList[index], "\n");
15916 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15918 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15919 else commentList[index][0] = NULLCHAR;
15920 strcat(commentList[index], text);
15921 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15922 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15927 FindStr (char * text, char * sub_text)
15929 char * result = strstr( text, sub_text );
15931 if( result != NULL ) {
15932 result += strlen( sub_text );
15938 /* [AS] Try to extract PV info from PGN comment */
15939 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15941 GetInfoFromComment (int index, char * text)
15943 char * sep = text, *p;
15945 if( text != NULL && index > 0 ) {
15948 int time = -1, sec = 0, deci;
15949 char * s_eval = FindStr( text, "[%eval " );
15950 char * s_emt = FindStr( text, "[%emt " );
15952 if( s_eval != NULL || s_emt != NULL ) {
15954 if(0) { // [HGM] this code is not finished, and could actually be detrimental
15959 if( s_eval != NULL ) {
15960 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15964 if( delim != ']' ) {
15969 if( s_emt != NULL ) {
15974 /* We expect something like: [+|-]nnn.nn/dd */
15977 if(*text != '{') return text; // [HGM] braces: must be normal comment
15979 sep = strchr( text, '/' );
15980 if( sep == NULL || sep < (text+4) ) {
15985 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15986 if(p[1] == '(') { // comment starts with PV
15987 p = strchr(p, ')'); // locate end of PV
15988 if(p == NULL || sep < p+5) return text;
15989 // at this point we have something like "{(.*) +0.23/6 ..."
15990 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15991 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15992 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15994 time = -1; sec = -1; deci = -1;
15995 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15996 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15997 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15998 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16002 if( score_lo < 0 || score_lo >= 100 ) {
16006 if(sec >= 0) time = 600*time + 10*sec; else
16007 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16009 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16011 /* [HGM] PV time: now locate end of PV info */
16012 while( *++sep >= '0' && *sep <= '9'); // strip depth
16014 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16016 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16018 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16019 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16030 pvInfoList[index-1].depth = depth;
16031 pvInfoList[index-1].score = score;
16032 pvInfoList[index-1].time = 10*time; // centi-sec
16033 if(*sep == '}') *sep = 0; else *--sep = '{';
16034 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16040 SendToProgram (char *message, ChessProgramState *cps)
16042 int count, outCount, error;
16045 if (cps->pr == NoProc) return;
16048 if (appData.debugMode) {
16051 fprintf(debugFP, "%ld >%-6s: %s",
16052 SubtractTimeMarks(&now, &programStartTime),
16053 cps->which, message);
16055 fprintf(serverFP, "%ld >%-6s: %s",
16056 SubtractTimeMarks(&now, &programStartTime),
16057 cps->which, message), fflush(serverFP);
16060 count = strlen(message);
16061 outCount = OutputToProcess(cps->pr, message, count, &error);
16062 if (outCount < count && !exiting
16063 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16064 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16065 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16066 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16067 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16068 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16069 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16070 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16072 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16073 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16074 gameInfo.result = res;
16076 gameInfo.resultDetails = StrSave(buf);
16078 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16079 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16084 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16088 ChessProgramState *cps = (ChessProgramState *)closure;
16090 if (isr != cps->isr) return; /* Killed intentionally */
16093 RemoveInputSource(cps->isr);
16094 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16095 _(cps->which), cps->program);
16096 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16097 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16098 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16099 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16100 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16101 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16103 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16104 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16105 gameInfo.result = res;
16107 gameInfo.resultDetails = StrSave(buf);
16109 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16110 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16112 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16113 _(cps->which), cps->program);
16114 RemoveInputSource(cps->isr);
16116 /* [AS] Program is misbehaving badly... kill it */
16117 if( count == -2 ) {
16118 DestroyChildProcess( cps->pr, 9 );
16122 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16127 if ((end_str = strchr(message, '\r')) != NULL)
16128 *end_str = NULLCHAR;
16129 if ((end_str = strchr(message, '\n')) != NULL)
16130 *end_str = NULLCHAR;
16132 if (appData.debugMode) {
16133 TimeMark now; int print = 1;
16134 char *quote = ""; char c; int i;
16136 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16137 char start = message[0];
16138 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16139 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16140 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16141 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16142 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16143 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16144 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16145 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16146 sscanf(message, "hint: %c", &c)!=1 &&
16147 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16148 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16149 print = (appData.engineComments >= 2);
16151 message[0] = start; // restore original message
16155 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16156 SubtractTimeMarks(&now, &programStartTime), cps->which,
16160 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16161 SubtractTimeMarks(&now, &programStartTime), cps->which,
16163 message), fflush(serverFP);
16167 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16168 if (appData.icsEngineAnalyze) {
16169 if (strstr(message, "whisper") != NULL ||
16170 strstr(message, "kibitz") != NULL ||
16171 strstr(message, "tellics") != NULL) return;
16174 HandleMachineMove(message, cps);
16179 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16184 if( timeControl_2 > 0 ) {
16185 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16186 tc = timeControl_2;
16189 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16190 inc /= cps->timeOdds;
16191 st /= cps->timeOdds;
16193 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16196 /* Set exact time per move, normally using st command */
16197 if (cps->stKludge) {
16198 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16200 if (seconds == 0) {
16201 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16203 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16206 snprintf(buf, MSG_SIZ, "st %d\n", st);
16209 /* Set conventional or incremental time control, using level command */
16210 if (seconds == 0) {
16211 /* Note old gnuchess bug -- minutes:seconds used to not work.
16212 Fixed in later versions, but still avoid :seconds
16213 when seconds is 0. */
16214 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16216 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16217 seconds, inc/1000.);
16220 SendToProgram(buf, cps);
16222 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16223 /* Orthogonally, limit search to given depth */
16225 if (cps->sdKludge) {
16226 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16228 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16230 SendToProgram(buf, cps);
16233 if(cps->nps >= 0) { /* [HGM] nps */
16234 if(cps->supportsNPS == FALSE)
16235 cps->nps = -1; // don't use if engine explicitly says not supported!
16237 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16238 SendToProgram(buf, cps);
16243 ChessProgramState *
16245 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16247 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16248 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16254 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16256 char message[MSG_SIZ];
16259 /* Note: this routine must be called when the clocks are stopped
16260 or when they have *just* been set or switched; otherwise
16261 it will be off by the time since the current tick started.
16263 if (machineWhite) {
16264 time = whiteTimeRemaining / 10;
16265 otime = blackTimeRemaining / 10;
16267 time = blackTimeRemaining / 10;
16268 otime = whiteTimeRemaining / 10;
16270 /* [HGM] translate opponent's time by time-odds factor */
16271 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16273 if (time <= 0) time = 1;
16274 if (otime <= 0) otime = 1;
16276 snprintf(message, MSG_SIZ, "time %ld\n", time);
16277 SendToProgram(message, cps);
16279 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16280 SendToProgram(message, cps);
16284 EngineDefinedVariant (ChessProgramState *cps, int n)
16285 { // return name of n-th unknown variant that engine supports
16286 static char buf[MSG_SIZ];
16287 char *p, *s = cps->variants;
16288 if(!s) return NULL;
16289 do { // parse string from variants feature
16291 p = strchr(s, ',');
16292 if(p) *p = NULLCHAR;
16293 v = StringToVariant(s);
16294 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16295 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16296 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16299 if(n < 0) return buf;
16305 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16308 int len = strlen(name);
16311 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16313 sscanf(*p, "%d", &val);
16315 while (**p && **p != ' ')
16317 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16318 SendToProgram(buf, cps);
16325 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16328 int len = strlen(name);
16329 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16331 sscanf(*p, "%d", loc);
16332 while (**p && **p != ' ') (*p)++;
16333 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16334 SendToProgram(buf, cps);
16341 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16344 int len = strlen(name);
16345 if (strncmp((*p), name, len) == 0
16346 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16348 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16349 sscanf(*p, "%[^\"]", *loc);
16350 while (**p && **p != '\"') (*p)++;
16351 if (**p == '\"') (*p)++;
16352 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16353 SendToProgram(buf, cps);
16360 ParseOption (Option *opt, ChessProgramState *cps)
16361 // [HGM] options: process the string that defines an engine option, and determine
16362 // name, type, default value, and allowed value range
16364 char *p, *q, buf[MSG_SIZ];
16365 int n, min = (-1)<<31, max = 1<<31, def;
16367 if(p = strstr(opt->name, " -spin ")) {
16368 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16369 if(max < min) max = min; // enforce consistency
16370 if(def < min) def = min;
16371 if(def > max) def = max;
16376 } else if((p = strstr(opt->name, " -slider "))) {
16377 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16378 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16379 if(max < min) max = min; // enforce consistency
16380 if(def < min) def = min;
16381 if(def > max) def = max;
16385 opt->type = Spin; // Slider;
16386 } else if((p = strstr(opt->name, " -string "))) {
16387 opt->textValue = p+9;
16388 opt->type = TextBox;
16389 } else if((p = strstr(opt->name, " -file "))) {
16390 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16391 opt->textValue = p+7;
16392 opt->type = FileName; // FileName;
16393 } else if((p = strstr(opt->name, " -path "))) {
16394 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16395 opt->textValue = p+7;
16396 opt->type = PathName; // PathName;
16397 } else if(p = strstr(opt->name, " -check ")) {
16398 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16399 opt->value = (def != 0);
16400 opt->type = CheckBox;
16401 } else if(p = strstr(opt->name, " -combo ")) {
16402 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16403 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16404 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16405 opt->value = n = 0;
16406 while(q = StrStr(q, " /// ")) {
16407 n++; *q = 0; // count choices, and null-terminate each of them
16409 if(*q == '*') { // remember default, which is marked with * prefix
16413 cps->comboList[cps->comboCnt++] = q;
16415 cps->comboList[cps->comboCnt++] = NULL;
16417 opt->type = ComboBox;
16418 } else if(p = strstr(opt->name, " -button")) {
16419 opt->type = Button;
16420 } else if(p = strstr(opt->name, " -save")) {
16421 opt->type = SaveButton;
16422 } else return FALSE;
16423 *p = 0; // terminate option name
16424 // now look if the command-line options define a setting for this engine option.
16425 if(cps->optionSettings && cps->optionSettings[0])
16426 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16427 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16428 snprintf(buf, MSG_SIZ, "option %s", p);
16429 if(p = strstr(buf, ",")) *p = 0;
16430 if(q = strchr(buf, '=')) switch(opt->type) {
16432 for(n=0; n<opt->max; n++)
16433 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16436 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16440 opt->value = atoi(q+1);
16445 SendToProgram(buf, cps);
16451 FeatureDone (ChessProgramState *cps, int val)
16453 DelayedEventCallback cb = GetDelayedEvent();
16454 if ((cb == InitBackEnd3 && cps == &first) ||
16455 (cb == SettingsMenuIfReady && cps == &second) ||
16456 (cb == LoadEngine) ||
16457 (cb == TwoMachinesEventIfReady)) {
16458 CancelDelayedEvent();
16459 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16461 cps->initDone = val;
16462 if(val) cps->reload = FALSE;
16465 /* Parse feature command from engine */
16467 ParseFeatures (char *args, ChessProgramState *cps)
16475 while (*p == ' ') p++;
16476 if (*p == NULLCHAR) return;
16478 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16479 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16480 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16481 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16482 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16483 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16484 if (BoolFeature(&p, "reuse", &val, cps)) {
16485 /* Engine can disable reuse, but can't enable it if user said no */
16486 if (!val) cps->reuse = FALSE;
16489 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16490 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16491 if (gameMode == TwoMachinesPlay) {
16492 DisplayTwoMachinesTitle();
16498 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16499 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16500 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16501 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16502 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16503 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16504 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16505 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16506 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16507 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16508 if (IntFeature(&p, "done", &val, cps)) {
16509 FeatureDone(cps, val);
16512 /* Added by Tord: */
16513 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16514 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16515 /* End of additions by Tord */
16517 /* [HGM] added features: */
16518 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16519 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16520 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16521 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16522 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16523 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16524 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16525 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16526 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16527 FREE(cps->option[cps->nrOptions].name);
16528 cps->option[cps->nrOptions].name = q; q = NULL;
16529 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16530 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16531 SendToProgram(buf, cps);
16534 if(cps->nrOptions >= MAX_OPTIONS) {
16536 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16537 DisplayError(buf, 0);
16541 /* End of additions by HGM */
16543 /* unknown feature: complain and skip */
16545 while (*q && *q != '=') q++;
16546 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16547 SendToProgram(buf, cps);
16553 while (*p && *p != '\"') p++;
16554 if (*p == '\"') p++;
16556 while (*p && *p != ' ') p++;
16564 PeriodicUpdatesEvent (int newState)
16566 if (newState == appData.periodicUpdates)
16569 appData.periodicUpdates=newState;
16571 /* Display type changes, so update it now */
16572 // DisplayAnalysis();
16574 /* Get the ball rolling again... */
16576 AnalysisPeriodicEvent(1);
16577 StartAnalysisClock();
16582 PonderNextMoveEvent (int newState)
16584 if (newState == appData.ponderNextMove) return;
16585 if (gameMode == EditPosition) EditPositionDone(TRUE);
16587 SendToProgram("hard\n", &first);
16588 if (gameMode == TwoMachinesPlay) {
16589 SendToProgram("hard\n", &second);
16592 SendToProgram("easy\n", &first);
16593 thinkOutput[0] = NULLCHAR;
16594 if (gameMode == TwoMachinesPlay) {
16595 SendToProgram("easy\n", &second);
16598 appData.ponderNextMove = newState;
16602 NewSettingEvent (int option, int *feature, char *command, int value)
16606 if (gameMode == EditPosition) EditPositionDone(TRUE);
16607 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16608 if(feature == NULL || *feature) SendToProgram(buf, &first);
16609 if (gameMode == TwoMachinesPlay) {
16610 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16615 ShowThinkingEvent ()
16616 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16618 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16619 int newState = appData.showThinking
16620 // [HGM] thinking: other features now need thinking output as well
16621 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16623 if (oldState == newState) return;
16624 oldState = newState;
16625 if (gameMode == EditPosition) EditPositionDone(TRUE);
16627 SendToProgram("post\n", &first);
16628 if (gameMode == TwoMachinesPlay) {
16629 SendToProgram("post\n", &second);
16632 SendToProgram("nopost\n", &first);
16633 thinkOutput[0] = NULLCHAR;
16634 if (gameMode == TwoMachinesPlay) {
16635 SendToProgram("nopost\n", &second);
16638 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16642 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16644 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16645 if (pr == NoProc) return;
16646 AskQuestion(title, question, replyPrefix, pr);
16650 TypeInEvent (char firstChar)
16652 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16653 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16654 gameMode == AnalyzeMode || gameMode == EditGame ||
16655 gameMode == EditPosition || gameMode == IcsExamining ||
16656 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16657 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16658 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16659 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16660 gameMode == Training) PopUpMoveDialog(firstChar);
16664 TypeInDoneEvent (char *move)
16667 int n, fromX, fromY, toX, toY;
16669 ChessMove moveType;
16672 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16673 EditPositionPasteFEN(move);
16676 // [HGM] movenum: allow move number to be typed in any mode
16677 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16681 // undocumented kludge: allow command-line option to be typed in!
16682 // (potentially fatal, and does not implement the effect of the option.)
16683 // should only be used for options that are values on which future decisions will be made,
16684 // and definitely not on options that would be used during initialization.
16685 if(strstr(move, "!!! -") == move) {
16686 ParseArgsFromString(move+4);
16690 if (gameMode != EditGame && currentMove != forwardMostMove &&
16691 gameMode != Training) {
16692 DisplayMoveError(_("Displayed move is not current"));
16694 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16695 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16696 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16697 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16698 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16699 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16701 DisplayMoveError(_("Could not parse move"));
16707 DisplayMove (int moveNumber)
16709 char message[MSG_SIZ];
16711 char cpThinkOutput[MSG_SIZ];
16713 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16715 if (moveNumber == forwardMostMove - 1 ||
16716 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16718 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16720 if (strchr(cpThinkOutput, '\n')) {
16721 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16724 *cpThinkOutput = NULLCHAR;
16727 /* [AS] Hide thinking from human user */
16728 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16729 *cpThinkOutput = NULLCHAR;
16730 if( thinkOutput[0] != NULLCHAR ) {
16733 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16734 cpThinkOutput[i] = '.';
16736 cpThinkOutput[i] = NULLCHAR;
16737 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16741 if (moveNumber == forwardMostMove - 1 &&
16742 gameInfo.resultDetails != NULL) {
16743 if (gameInfo.resultDetails[0] == NULLCHAR) {
16744 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16746 snprintf(res, MSG_SIZ, " {%s} %s",
16747 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16753 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16754 DisplayMessage(res, cpThinkOutput);
16756 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16757 WhiteOnMove(moveNumber) ? " " : ".. ",
16758 parseList[moveNumber], res);
16759 DisplayMessage(message, cpThinkOutput);
16764 DisplayComment (int moveNumber, char *text)
16766 char title[MSG_SIZ];
16768 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16769 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16771 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16772 WhiteOnMove(moveNumber) ? " " : ".. ",
16773 parseList[moveNumber]);
16775 if (text != NULL && (appData.autoDisplayComment || commentUp))
16776 CommentPopUp(title, text);
16779 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16780 * might be busy thinking or pondering. It can be omitted if your
16781 * gnuchess is configured to stop thinking immediately on any user
16782 * input. However, that gnuchess feature depends on the FIONREAD
16783 * ioctl, which does not work properly on some flavors of Unix.
16786 Attention (ChessProgramState *cps)
16789 if (!cps->useSigint) return;
16790 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16791 switch (gameMode) {
16792 case MachinePlaysWhite:
16793 case MachinePlaysBlack:
16794 case TwoMachinesPlay:
16795 case IcsPlayingWhite:
16796 case IcsPlayingBlack:
16799 /* Skip if we know it isn't thinking */
16800 if (!cps->maybeThinking) return;
16801 if (appData.debugMode)
16802 fprintf(debugFP, "Interrupting %s\n", cps->which);
16803 InterruptChildProcess(cps->pr);
16804 cps->maybeThinking = FALSE;
16809 #endif /*ATTENTION*/
16815 if (whiteTimeRemaining <= 0) {
16818 if (appData.icsActive) {
16819 if (appData.autoCallFlag &&
16820 gameMode == IcsPlayingBlack && !blackFlag) {
16821 SendToICS(ics_prefix);
16822 SendToICS("flag\n");
16826 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16828 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16829 if (appData.autoCallFlag) {
16830 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16837 if (blackTimeRemaining <= 0) {
16840 if (appData.icsActive) {
16841 if (appData.autoCallFlag &&
16842 gameMode == IcsPlayingWhite && !whiteFlag) {
16843 SendToICS(ics_prefix);
16844 SendToICS("flag\n");
16848 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16850 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16851 if (appData.autoCallFlag) {
16852 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16863 CheckTimeControl ()
16865 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16866 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16869 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16871 if ( !WhiteOnMove(forwardMostMove) ) {
16872 /* White made time control */
16873 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16874 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16875 /* [HGM] time odds: correct new time quota for time odds! */
16876 / WhitePlayer()->timeOdds;
16877 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16879 lastBlack -= blackTimeRemaining;
16880 /* Black made time control */
16881 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16882 / WhitePlayer()->other->timeOdds;
16883 lastWhite = whiteTimeRemaining;
16888 DisplayBothClocks ()
16890 int wom = gameMode == EditPosition ?
16891 !blackPlaysFirst : WhiteOnMove(currentMove);
16892 DisplayWhiteClock(whiteTimeRemaining, wom);
16893 DisplayBlackClock(blackTimeRemaining, !wom);
16897 /* Timekeeping seems to be a portability nightmare. I think everyone
16898 has ftime(), but I'm really not sure, so I'm including some ifdefs
16899 to use other calls if you don't. Clocks will be less accurate if
16900 you have neither ftime nor gettimeofday.
16903 /* VS 2008 requires the #include outside of the function */
16904 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16905 #include <sys/timeb.h>
16908 /* Get the current time as a TimeMark */
16910 GetTimeMark (TimeMark *tm)
16912 #if HAVE_GETTIMEOFDAY
16914 struct timeval timeVal;
16915 struct timezone timeZone;
16917 gettimeofday(&timeVal, &timeZone);
16918 tm->sec = (long) timeVal.tv_sec;
16919 tm->ms = (int) (timeVal.tv_usec / 1000L);
16921 #else /*!HAVE_GETTIMEOFDAY*/
16924 // include <sys/timeb.h> / moved to just above start of function
16925 struct timeb timeB;
16928 tm->sec = (long) timeB.time;
16929 tm->ms = (int) timeB.millitm;
16931 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16932 tm->sec = (long) time(NULL);
16938 /* Return the difference in milliseconds between two
16939 time marks. We assume the difference will fit in a long!
16942 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16944 return 1000L*(tm2->sec - tm1->sec) +
16945 (long) (tm2->ms - tm1->ms);
16950 * Code to manage the game clocks.
16952 * In tournament play, black starts the clock and then white makes a move.
16953 * We give the human user a slight advantage if he is playing white---the
16954 * clocks don't run until he makes his first move, so it takes zero time.
16955 * Also, we don't account for network lag, so we could get out of sync
16956 * with GNU Chess's clock -- but then, referees are always right.
16959 static TimeMark tickStartTM;
16960 static long intendedTickLength;
16963 NextTickLength (long timeRemaining)
16965 long nominalTickLength, nextTickLength;
16967 if (timeRemaining > 0L && timeRemaining <= 10000L)
16968 nominalTickLength = 100L;
16970 nominalTickLength = 1000L;
16971 nextTickLength = timeRemaining % nominalTickLength;
16972 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16974 return nextTickLength;
16977 /* Adjust clock one minute up or down */
16979 AdjustClock (Boolean which, int dir)
16981 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16982 if(which) blackTimeRemaining += 60000*dir;
16983 else whiteTimeRemaining += 60000*dir;
16984 DisplayBothClocks();
16985 adjustedClock = TRUE;
16988 /* Stop clocks and reset to a fresh time control */
16992 (void) StopClockTimer();
16993 if (appData.icsActive) {
16994 whiteTimeRemaining = blackTimeRemaining = 0;
16995 } else if (searchTime) {
16996 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16997 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16998 } else { /* [HGM] correct new time quote for time odds */
16999 whiteTC = blackTC = fullTimeControlString;
17000 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17001 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17003 if (whiteFlag || blackFlag) {
17005 whiteFlag = blackFlag = FALSE;
17007 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17008 DisplayBothClocks();
17009 adjustedClock = FALSE;
17012 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17014 /* Decrement running clock by amount of time that has passed */
17018 long timeRemaining;
17019 long lastTickLength, fudge;
17022 if (!appData.clockMode) return;
17023 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17027 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17029 /* Fudge if we woke up a little too soon */
17030 fudge = intendedTickLength - lastTickLength;
17031 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17033 if (WhiteOnMove(forwardMostMove)) {
17034 if(whiteNPS >= 0) lastTickLength = 0;
17035 timeRemaining = whiteTimeRemaining -= lastTickLength;
17036 if(timeRemaining < 0 && !appData.icsActive) {
17037 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17038 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17039 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17040 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17043 DisplayWhiteClock(whiteTimeRemaining - fudge,
17044 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17046 if(blackNPS >= 0) lastTickLength = 0;
17047 timeRemaining = blackTimeRemaining -= lastTickLength;
17048 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17049 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17051 blackStartMove = forwardMostMove;
17052 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17055 DisplayBlackClock(blackTimeRemaining - fudge,
17056 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17058 if (CheckFlags()) return;
17060 if(twoBoards) { // count down secondary board's clocks as well
17061 activePartnerTime -= lastTickLength;
17063 if(activePartner == 'W')
17064 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17066 DisplayBlackClock(activePartnerTime, TRUE);
17071 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17072 StartClockTimer(intendedTickLength);
17074 /* if the time remaining has fallen below the alarm threshold, sound the
17075 * alarm. if the alarm has sounded and (due to a takeback or time control
17076 * with increment) the time remaining has increased to a level above the
17077 * threshold, reset the alarm so it can sound again.
17080 if (appData.icsActive && appData.icsAlarm) {
17082 /* make sure we are dealing with the user's clock */
17083 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17084 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17087 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17088 alarmSounded = FALSE;
17089 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17091 alarmSounded = TRUE;
17097 /* A player has just moved, so stop the previously running
17098 clock and (if in clock mode) start the other one.
17099 We redisplay both clocks in case we're in ICS mode, because
17100 ICS gives us an update to both clocks after every move.
17101 Note that this routine is called *after* forwardMostMove
17102 is updated, so the last fractional tick must be subtracted
17103 from the color that is *not* on move now.
17106 SwitchClocks (int newMoveNr)
17108 long lastTickLength;
17110 int flagged = FALSE;
17114 if (StopClockTimer() && appData.clockMode) {
17115 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17116 if (!WhiteOnMove(forwardMostMove)) {
17117 if(blackNPS >= 0) lastTickLength = 0;
17118 blackTimeRemaining -= lastTickLength;
17119 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17120 // if(pvInfoList[forwardMostMove].time == -1)
17121 pvInfoList[forwardMostMove].time = // use GUI time
17122 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17124 if(whiteNPS >= 0) lastTickLength = 0;
17125 whiteTimeRemaining -= lastTickLength;
17126 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17127 // if(pvInfoList[forwardMostMove].time == -1)
17128 pvInfoList[forwardMostMove].time =
17129 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17131 flagged = CheckFlags();
17133 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17134 CheckTimeControl();
17136 if (flagged || !appData.clockMode) return;
17138 switch (gameMode) {
17139 case MachinePlaysBlack:
17140 case MachinePlaysWhite:
17141 case BeginningOfGame:
17142 if (pausing) return;
17146 case PlayFromGameFile:
17154 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17155 if(WhiteOnMove(forwardMostMove))
17156 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17157 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17161 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17162 whiteTimeRemaining : blackTimeRemaining);
17163 StartClockTimer(intendedTickLength);
17167 /* Stop both clocks */
17171 long lastTickLength;
17174 if (!StopClockTimer()) return;
17175 if (!appData.clockMode) return;
17179 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17180 if (WhiteOnMove(forwardMostMove)) {
17181 if(whiteNPS >= 0) lastTickLength = 0;
17182 whiteTimeRemaining -= lastTickLength;
17183 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17185 if(blackNPS >= 0) lastTickLength = 0;
17186 blackTimeRemaining -= lastTickLength;
17187 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17192 /* Start clock of player on move. Time may have been reset, so
17193 if clock is already running, stop and restart it. */
17197 (void) StopClockTimer(); /* in case it was running already */
17198 DisplayBothClocks();
17199 if (CheckFlags()) return;
17201 if (!appData.clockMode) return;
17202 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17204 GetTimeMark(&tickStartTM);
17205 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17206 whiteTimeRemaining : blackTimeRemaining);
17208 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17209 whiteNPS = blackNPS = -1;
17210 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17211 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17212 whiteNPS = first.nps;
17213 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17214 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17215 blackNPS = first.nps;
17216 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17217 whiteNPS = second.nps;
17218 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17219 blackNPS = second.nps;
17220 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17222 StartClockTimer(intendedTickLength);
17226 TimeString (long ms)
17228 long second, minute, hour, day;
17230 static char buf[32];
17232 if (ms > 0 && ms <= 9900) {
17233 /* convert milliseconds to tenths, rounding up */
17234 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17236 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17240 /* convert milliseconds to seconds, rounding up */
17241 /* use floating point to avoid strangeness of integer division
17242 with negative dividends on many machines */
17243 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17250 day = second / (60 * 60 * 24);
17251 second = second % (60 * 60 * 24);
17252 hour = second / (60 * 60);
17253 second = second % (60 * 60);
17254 minute = second / 60;
17255 second = second % 60;
17258 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17259 sign, day, hour, minute, second);
17261 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17263 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17270 * This is necessary because some C libraries aren't ANSI C compliant yet.
17273 StrStr (char *string, char *match)
17277 length = strlen(match);
17279 for (i = strlen(string) - length; i >= 0; i--, string++)
17280 if (!strncmp(match, string, length))
17287 StrCaseStr (char *string, char *match)
17291 length = strlen(match);
17293 for (i = strlen(string) - length; i >= 0; i--, string++) {
17294 for (j = 0; j < length; j++) {
17295 if (ToLower(match[j]) != ToLower(string[j]))
17298 if (j == length) return string;
17306 StrCaseCmp (char *s1, char *s2)
17311 c1 = ToLower(*s1++);
17312 c2 = ToLower(*s2++);
17313 if (c1 > c2) return 1;
17314 if (c1 < c2) return -1;
17315 if (c1 == NULLCHAR) return 0;
17323 return isupper(c) ? tolower(c) : c;
17330 return islower(c) ? toupper(c) : c;
17332 #endif /* !_amigados */
17339 if ((ret = (char *) malloc(strlen(s) + 1)))
17341 safeStrCpy(ret, s, strlen(s)+1);
17347 StrSavePtr (char *s, char **savePtr)
17352 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17353 safeStrCpy(*savePtr, s, strlen(s)+1);
17365 clock = time((time_t *)NULL);
17366 tm = localtime(&clock);
17367 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17368 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17369 return StrSave(buf);
17374 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17376 int i, j, fromX, fromY, toX, toY;
17383 whiteToPlay = (gameMode == EditPosition) ?
17384 !blackPlaysFirst : (move % 2 == 0);
17387 /* Piece placement data */
17388 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17389 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17391 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17392 if (boards[move][i][j] == EmptySquare) {
17394 } else { ChessSquare piece = boards[move][i][j];
17395 if (emptycount > 0) {
17396 if(emptycount<10) /* [HGM] can be >= 10 */
17397 *p++ = '0' + emptycount;
17398 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17401 if(PieceToChar(piece) == '+') {
17402 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17404 piece = (ChessSquare)(DEMOTED piece);
17406 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17408 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17409 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17414 if (emptycount > 0) {
17415 if(emptycount<10) /* [HGM] can be >= 10 */
17416 *p++ = '0' + emptycount;
17417 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17424 /* [HGM] print Crazyhouse or Shogi holdings */
17425 if( gameInfo.holdingsWidth ) {
17426 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17428 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17429 piece = boards[move][i][BOARD_WIDTH-1];
17430 if( piece != EmptySquare )
17431 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17432 *p++ = PieceToChar(piece);
17434 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17435 piece = boards[move][BOARD_HEIGHT-i-1][0];
17436 if( piece != EmptySquare )
17437 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17438 *p++ = PieceToChar(piece);
17441 if( q == p ) *p++ = '-';
17447 *p++ = whiteToPlay ? 'w' : 'b';
17450 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17451 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17453 if(nrCastlingRights) {
17455 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17456 /* [HGM] write directly from rights */
17457 if(boards[move][CASTLING][2] != NoRights &&
17458 boards[move][CASTLING][0] != NoRights )
17459 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17460 if(boards[move][CASTLING][2] != NoRights &&
17461 boards[move][CASTLING][1] != NoRights )
17462 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17463 if(boards[move][CASTLING][5] != NoRights &&
17464 boards[move][CASTLING][3] != NoRights )
17465 *p++ = boards[move][CASTLING][3] + AAA;
17466 if(boards[move][CASTLING][5] != NoRights &&
17467 boards[move][CASTLING][4] != NoRights )
17468 *p++ = boards[move][CASTLING][4] + AAA;
17471 /* [HGM] write true castling rights */
17472 if( nrCastlingRights == 6 ) {
17474 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17475 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17476 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17477 boards[move][CASTLING][2] != NoRights );
17478 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17479 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17480 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17481 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17482 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17486 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17487 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17488 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17489 boards[move][CASTLING][5] != NoRights );
17490 if(gameInfo.variant == VariantSChess) {
17491 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17492 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17493 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17494 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17499 if (q == p) *p++ = '-'; /* No castling rights */
17503 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17504 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17505 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17506 /* En passant target square */
17507 if (move > backwardMostMove) {
17508 fromX = moveList[move - 1][0] - AAA;
17509 fromY = moveList[move - 1][1] - ONE;
17510 toX = moveList[move - 1][2] - AAA;
17511 toY = moveList[move - 1][3] - ONE;
17512 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17513 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17514 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17516 /* 2-square pawn move just happened */
17518 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17522 } else if(move == backwardMostMove) {
17523 // [HGM] perhaps we should always do it like this, and forget the above?
17524 if((signed char)boards[move][EP_STATUS] >= 0) {
17525 *p++ = boards[move][EP_STATUS] + AAA;
17526 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17538 { int i = 0, j=move;
17540 /* [HGM] find reversible plies */
17541 if (appData.debugMode) { int k;
17542 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17543 for(k=backwardMostMove; k<=forwardMostMove; k++)
17544 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17548 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17549 if( j == backwardMostMove ) i += initialRulePlies;
17550 sprintf(p, "%d ", i);
17551 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17553 /* Fullmove number */
17554 sprintf(p, "%d", (move / 2) + 1);
17555 } else *--p = NULLCHAR;
17557 return StrSave(buf);
17561 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17565 int emptycount, virgin[BOARD_FILES];
17570 /* Piece placement data */
17571 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17574 if (*p == '/' || *p == ' ' || *p == '[' ) {
17576 emptycount = gameInfo.boardWidth - j;
17577 while (emptycount--)
17578 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17579 if (*p == '/') p++;
17580 else if(autoSize) { // we stumbled unexpectedly into end of board
17581 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17582 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17584 appData.NrRanks = gameInfo.boardHeight - i; i=0;
17587 #if(BOARD_FILES >= 10)
17588 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17589 p++; emptycount=10;
17590 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17591 while (emptycount--)
17592 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17594 } else if (*p == '*') {
17595 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17596 } else if (isdigit(*p)) {
17597 emptycount = *p++ - '0';
17598 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17599 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17600 while (emptycount--)
17601 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17602 } else if (*p == '+' || isalpha(*p)) {
17603 if (j >= gameInfo.boardWidth) return FALSE;
17605 piece = CharToPiece(*++p);
17606 if(piece == EmptySquare) return FALSE; /* unknown piece */
17607 piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17608 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17609 } else piece = CharToPiece(*p++);
17611 if(piece==EmptySquare) return FALSE; /* unknown piece */
17612 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17613 piece = (ChessSquare) (PROMOTED piece);
17614 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17617 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17623 while (*p == '/' || *p == ' ') p++;
17625 if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17627 /* [HGM] by default clear Crazyhouse holdings, if present */
17628 if(gameInfo.holdingsWidth) {
17629 for(i=0; i<BOARD_HEIGHT; i++) {
17630 board[i][0] = EmptySquare; /* black holdings */
17631 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17632 board[i][1] = (ChessSquare) 0; /* black counts */
17633 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17637 /* [HGM] look for Crazyhouse holdings here */
17638 while(*p==' ') p++;
17639 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17641 if(*p == '-' ) p++; /* empty holdings */ else {
17642 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17643 /* if we would allow FEN reading to set board size, we would */
17644 /* have to add holdings and shift the board read so far here */
17645 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17647 if((int) piece >= (int) BlackPawn ) {
17648 i = (int)piece - (int)BlackPawn;
17649 i = PieceToNumber((ChessSquare)i);
17650 if( i >= gameInfo.holdingsSize ) return FALSE;
17651 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17652 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17654 i = (int)piece - (int)WhitePawn;
17655 i = PieceToNumber((ChessSquare)i);
17656 if( i >= gameInfo.holdingsSize ) return FALSE;
17657 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17658 board[i][BOARD_WIDTH-2]++; /* black holdings */
17665 while(*p == ' ') p++;
17669 if(appData.colorNickNames) {
17670 if( c == appData.colorNickNames[0] ) c = 'w'; else
17671 if( c == appData.colorNickNames[1] ) c = 'b';
17675 *blackPlaysFirst = FALSE;
17678 *blackPlaysFirst = TRUE;
17684 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17685 /* return the extra info in global variiables */
17687 /* set defaults in case FEN is incomplete */
17688 board[EP_STATUS] = EP_UNKNOWN;
17689 for(i=0; i<nrCastlingRights; i++ ) {
17690 board[CASTLING][i] =
17691 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17692 } /* assume possible unless obviously impossible */
17693 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17694 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17695 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17696 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17697 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17698 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17699 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17700 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17703 while(*p==' ') p++;
17704 if(nrCastlingRights) {
17705 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17706 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17707 /* castling indicator present, so default becomes no castlings */
17708 for(i=0; i<nrCastlingRights; i++ ) {
17709 board[CASTLING][i] = NoRights;
17712 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17713 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17714 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17715 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17716 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17718 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17719 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17720 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17722 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17723 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17724 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17725 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17726 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17727 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17730 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17731 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17732 board[CASTLING][2] = whiteKingFile;
17733 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17734 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17737 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17738 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17739 board[CASTLING][2] = whiteKingFile;
17740 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17741 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17744 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17745 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17746 board[CASTLING][5] = blackKingFile;
17747 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17748 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17751 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17752 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17753 board[CASTLING][5] = blackKingFile;
17754 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17755 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17758 default: /* FRC castlings */
17759 if(c >= 'a') { /* black rights */
17760 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17761 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17762 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17763 if(i == BOARD_RGHT) break;
17764 board[CASTLING][5] = i;
17766 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17767 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17769 board[CASTLING][3] = c;
17771 board[CASTLING][4] = c;
17772 } else { /* white rights */
17773 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17774 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17775 if(board[0][i] == WhiteKing) break;
17776 if(i == BOARD_RGHT) break;
17777 board[CASTLING][2] = i;
17778 c -= AAA - 'a' + 'A';
17779 if(board[0][c] >= WhiteKing) break;
17781 board[CASTLING][0] = c;
17783 board[CASTLING][1] = c;
17787 for(i=0; i<nrCastlingRights; i++)
17788 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17789 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17790 if (appData.debugMode) {
17791 fprintf(debugFP, "FEN castling rights:");
17792 for(i=0; i<nrCastlingRights; i++)
17793 fprintf(debugFP, " %d", board[CASTLING][i]);
17794 fprintf(debugFP, "\n");
17797 while(*p==' ') p++;
17800 /* read e.p. field in games that know e.p. capture */
17801 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17802 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17803 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17805 p++; board[EP_STATUS] = EP_NONE;
17807 char c = *p++ - AAA;
17809 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17810 if(*p >= '0' && *p <='9') p++;
17811 board[EP_STATUS] = c;
17816 if(sscanf(p, "%d", &i) == 1) {
17817 FENrulePlies = i; /* 50-move ply counter */
17818 /* (The move number is still ignored) */
17825 EditPositionPasteFEN (char *fen)
17828 Board initial_position;
17830 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17831 DisplayError(_("Bad FEN position in clipboard"), 0);
17834 int savedBlackPlaysFirst = blackPlaysFirst;
17835 EditPositionEvent();
17836 blackPlaysFirst = savedBlackPlaysFirst;
17837 CopyBoard(boards[0], initial_position);
17838 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17839 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17840 DisplayBothClocks();
17841 DrawPosition(FALSE, boards[currentMove]);
17846 static char cseq[12] = "\\ ";
17849 set_cont_sequence (char *new_seq)
17854 // handle bad attempts to set the sequence
17856 return 0; // acceptable error - no debug
17858 len = strlen(new_seq);
17859 ret = (len > 0) && (len < sizeof(cseq));
17861 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17862 else if (appData.debugMode)
17863 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17868 reformat a source message so words don't cross the width boundary. internal
17869 newlines are not removed. returns the wrapped size (no null character unless
17870 included in source message). If dest is NULL, only calculate the size required
17871 for the dest buffer. lp argument indicats line position upon entry, and it's
17872 passed back upon exit.
17875 wrap (char *dest, char *src, int count, int width, int *lp)
17877 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17879 cseq_len = strlen(cseq);
17880 old_line = line = *lp;
17881 ansi = len = clen = 0;
17883 for (i=0; i < count; i++)
17885 if (src[i] == '\033')
17888 // if we hit the width, back up
17889 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17891 // store i & len in case the word is too long
17892 old_i = i, old_len = len;
17894 // find the end of the last word
17895 while (i && src[i] != ' ' && src[i] != '\n')
17901 // word too long? restore i & len before splitting it
17902 if ((old_i-i+clen) >= width)
17909 if (i && src[i-1] == ' ')
17912 if (src[i] != ' ' && src[i] != '\n')
17919 // now append the newline and continuation sequence
17924 strncpy(dest+len, cseq, cseq_len);
17932 dest[len] = src[i];
17936 if (src[i] == '\n')
17941 if (dest && appData.debugMode)
17943 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17944 count, width, line, len, *lp);
17945 show_bytes(debugFP, src, count);
17946 fprintf(debugFP, "\ndest: ");
17947 show_bytes(debugFP, dest, len);
17948 fprintf(debugFP, "\n");
17950 *lp = dest ? line : old_line;
17955 // [HGM] vari: routines for shelving variations
17956 Boolean modeRestore = FALSE;
17959 PushInner (int firstMove, int lastMove)
17961 int i, j, nrMoves = lastMove - firstMove;
17963 // push current tail of game on stack
17964 savedResult[storedGames] = gameInfo.result;
17965 savedDetails[storedGames] = gameInfo.resultDetails;
17966 gameInfo.resultDetails = NULL;
17967 savedFirst[storedGames] = firstMove;
17968 savedLast [storedGames] = lastMove;
17969 savedFramePtr[storedGames] = framePtr;
17970 framePtr -= nrMoves; // reserve space for the boards
17971 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17972 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17973 for(j=0; j<MOVE_LEN; j++)
17974 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17975 for(j=0; j<2*MOVE_LEN; j++)
17976 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17977 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17978 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17979 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17980 pvInfoList[firstMove+i-1].depth = 0;
17981 commentList[framePtr+i] = commentList[firstMove+i];
17982 commentList[firstMove+i] = NULL;
17986 forwardMostMove = firstMove; // truncate game so we can start variation
17990 PushTail (int firstMove, int lastMove)
17992 if(appData.icsActive) { // only in local mode
17993 forwardMostMove = currentMove; // mimic old ICS behavior
17996 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17998 PushInner(firstMove, lastMove);
17999 if(storedGames == 1) GreyRevert(FALSE);
18000 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18004 PopInner (Boolean annotate)
18007 char buf[8000], moveBuf[20];
18009 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18010 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18011 nrMoves = savedLast[storedGames] - currentMove;
18014 if(!WhiteOnMove(currentMove))
18015 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18016 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18017 for(i=currentMove; i<forwardMostMove; i++) {
18019 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18020 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18021 strcat(buf, moveBuf);
18022 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18023 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18027 for(i=1; i<=nrMoves; i++) { // copy last variation back
18028 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18029 for(j=0; j<MOVE_LEN; j++)
18030 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18031 for(j=0; j<2*MOVE_LEN; j++)
18032 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18033 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18034 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18035 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18036 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18037 commentList[currentMove+i] = commentList[framePtr+i];
18038 commentList[framePtr+i] = NULL;
18040 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18041 framePtr = savedFramePtr[storedGames];
18042 gameInfo.result = savedResult[storedGames];
18043 if(gameInfo.resultDetails != NULL) {
18044 free(gameInfo.resultDetails);
18046 gameInfo.resultDetails = savedDetails[storedGames];
18047 forwardMostMove = currentMove + nrMoves;
18051 PopTail (Boolean annotate)
18053 if(appData.icsActive) return FALSE; // only in local mode
18054 if(!storedGames) return FALSE; // sanity
18055 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18057 PopInner(annotate);
18058 if(currentMove < forwardMostMove) ForwardEvent(); else
18059 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18061 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18067 { // remove all shelved variations
18069 for(i=0; i<storedGames; i++) {
18070 if(savedDetails[i])
18071 free(savedDetails[i]);
18072 savedDetails[i] = NULL;
18074 for(i=framePtr; i<MAX_MOVES; i++) {
18075 if(commentList[i]) free(commentList[i]);
18076 commentList[i] = NULL;
18078 framePtr = MAX_MOVES-1;
18083 LoadVariation (int index, char *text)
18084 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18085 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18086 int level = 0, move;
18088 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18089 // first find outermost bracketing variation
18090 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18091 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18092 if(*p == '{') wait = '}'; else
18093 if(*p == '[') wait = ']'; else
18094 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18095 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18097 if(*p == wait) wait = NULLCHAR; // closing ]} found
18100 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18101 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18102 end[1] = NULLCHAR; // clip off comment beyond variation
18103 ToNrEvent(currentMove-1);
18104 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18105 // kludge: use ParsePV() to append variation to game
18106 move = currentMove;
18107 ParsePV(start, TRUE, TRUE);
18108 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18109 ClearPremoveHighlights();
18111 ToNrEvent(currentMove+1);
18117 char *p, *q, buf[MSG_SIZ];
18118 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18119 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18120 ParseArgsFromString(buf);
18121 ActivateTheme(TRUE); // also redo colors
18125 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18128 q = appData.themeNames;
18129 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18130 if(appData.useBitmaps) {
18131 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18132 appData.liteBackTextureFile, appData.darkBackTextureFile,
18133 appData.liteBackTextureMode,
18134 appData.darkBackTextureMode );
18136 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18137 Col2Text(2), // lightSquareColor
18138 Col2Text(3) ); // darkSquareColor
18140 if(appData.useBorder) {
18141 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18144 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18146 if(appData.useFont) {
18147 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18148 appData.renderPiecesWithFont,
18149 appData.fontToPieceTable,
18150 Col2Text(9), // appData.fontBackColorWhite
18151 Col2Text(10) ); // appData.fontForeColorBlack
18153 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18154 appData.pieceDirectory);
18155 if(!appData.pieceDirectory[0])
18156 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18157 Col2Text(0), // whitePieceColor
18158 Col2Text(1) ); // blackPieceColor
18160 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18161 Col2Text(4), // highlightSquareColor
18162 Col2Text(5) ); // premoveHighlightColor
18163 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18164 if(insert != q) insert[-1] = NULLCHAR;
18165 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18168 ActivateTheme(FALSE);