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 }
570 #if (BOARD_FILES>=10)
571 ChessSquare ShogiArray[2][BOARD_FILES] = {
572 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
573 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
574 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
575 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
578 ChessSquare XiangqiArray[2][BOARD_FILES] = {
579 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
580 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
582 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
585 ChessSquare CapablancaArray[2][BOARD_FILES] = {
586 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
587 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
588 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
589 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
592 ChessSquare GreatArray[2][BOARD_FILES] = {
593 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
594 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
595 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
596 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
599 ChessSquare JanusArray[2][BOARD_FILES] = {
600 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
601 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
602 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
603 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
606 ChessSquare GrandArray[2][BOARD_FILES] = {
607 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
608 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
609 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
610 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
614 ChessSquare GothicArray[2][BOARD_FILES] = {
615 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
616 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
617 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
618 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
621 #define GothicArray CapablancaArray
625 ChessSquare FalconArray[2][BOARD_FILES] = {
626 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
627 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
628 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
629 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
632 #define FalconArray CapablancaArray
635 #else // !(BOARD_FILES>=10)
636 #define XiangqiPosition FIDEArray
637 #define CapablancaArray FIDEArray
638 #define GothicArray FIDEArray
639 #define GreatArray FIDEArray
640 #endif // !(BOARD_FILES>=10)
642 #if (BOARD_FILES>=12)
643 ChessSquare CourierArray[2][BOARD_FILES] = {
644 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
645 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
646 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
647 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
649 #else // !(BOARD_FILES>=12)
650 #define CourierArray CapablancaArray
651 #endif // !(BOARD_FILES>=12)
654 Board initialPosition;
657 /* Convert str to a rating. Checks for special cases of "----",
659 "++++", etc. Also strips ()'s */
661 string_to_rating (char *str)
663 while(*str && !isdigit(*str)) ++str;
665 return 0; /* One of the special "no rating" cases */
673 /* Init programStats */
674 programStats.movelist[0] = 0;
675 programStats.depth = 0;
676 programStats.nr_moves = 0;
677 programStats.moves_left = 0;
678 programStats.nodes = 0;
679 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
680 programStats.score = 0;
681 programStats.got_only_move = 0;
682 programStats.got_fail = 0;
683 programStats.line_is_book = 0;
688 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
689 if (appData.firstPlaysBlack) {
690 first.twoMachinesColor = "black\n";
691 second.twoMachinesColor = "white\n";
693 first.twoMachinesColor = "white\n";
694 second.twoMachinesColor = "black\n";
697 first.other = &second;
698 second.other = &first;
701 if(appData.timeOddsMode) {
702 norm = appData.timeOdds[0];
703 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
705 first.timeOdds = appData.timeOdds[0]/norm;
706 second.timeOdds = appData.timeOdds[1]/norm;
709 if(programVersion) free(programVersion);
710 if (appData.noChessProgram) {
711 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
712 sprintf(programVersion, "%s", PACKAGE_STRING);
714 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
715 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
716 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
721 UnloadEngine (ChessProgramState *cps)
723 /* Kill off first chess program */
724 if (cps->isr != NULL)
725 RemoveInputSource(cps->isr);
728 if (cps->pr != NoProc) {
730 DoSleep( appData.delayBeforeQuit );
731 SendToProgram("quit\n", cps);
732 DoSleep( appData.delayAfterQuit );
733 DestroyChildProcess(cps->pr, cps->useSigterm);
736 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
740 ClearOptions (ChessProgramState *cps)
743 cps->nrOptions = cps->comboCnt = 0;
744 for(i=0; i<MAX_OPTIONS; i++) {
745 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
746 cps->option[i].textValue = 0;
750 char *engineNames[] = {
751 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
752 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
754 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
755 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
760 InitEngine (ChessProgramState *cps, int n)
761 { // [HGM] all engine initialiation put in a function that does one engine
765 cps->which = engineNames[n];
766 cps->maybeThinking = FALSE;
770 cps->sendDrawOffers = 1;
772 cps->program = appData.chessProgram[n];
773 cps->host = appData.host[n];
774 cps->dir = appData.directory[n];
775 cps->initString = appData.engInitString[n];
776 cps->computerString = appData.computerString[n];
777 cps->useSigint = TRUE;
778 cps->useSigterm = TRUE;
779 cps->reuse = appData.reuse[n];
780 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
781 cps->useSetboard = FALSE;
783 cps->usePing = FALSE;
786 cps->usePlayother = FALSE;
787 cps->useColors = TRUE;
788 cps->useUsermove = FALSE;
789 cps->sendICS = FALSE;
790 cps->sendName = appData.icsActive;
791 cps->sdKludge = FALSE;
792 cps->stKludge = FALSE;
793 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
794 TidyProgramName(cps->program, cps->host, cps->tidy);
796 ASSIGN(cps->variants, appData.variant);
797 cps->analysisSupport = 2; /* detect */
798 cps->analyzing = FALSE;
799 cps->initDone = FALSE;
802 /* New features added by Tord: */
803 cps->useFEN960 = FALSE;
804 cps->useOOCastle = TRUE;
805 /* End of new features added by Tord. */
806 cps->fenOverride = appData.fenOverride[n];
808 /* [HGM] time odds: set factor for each machine */
809 cps->timeOdds = appData.timeOdds[n];
811 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
812 cps->accumulateTC = appData.accumulateTC[n];
813 cps->maxNrOfSessions = 1;
818 cps->supportsNPS = UNKNOWN;
819 cps->memSize = FALSE;
820 cps->maxCores = FALSE;
821 ASSIGN(cps->egtFormats, "");
824 cps->optionSettings = appData.engOptions[n];
826 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
827 cps->isUCI = appData.isUCI[n]; /* [AS] */
828 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
831 if (appData.protocolVersion[n] > PROTOVER
832 || appData.protocolVersion[n] < 1)
837 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
838 appData.protocolVersion[n]);
839 if( (len >= MSG_SIZ) && appData.debugMode )
840 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
842 DisplayFatalError(buf, 0, 2);
846 cps->protocolVersion = appData.protocolVersion[n];
849 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
850 ParseFeatures(appData.featureDefaults, cps);
853 ChessProgramState *savCps;
861 if(WaitForEngine(savCps, LoadEngine)) return;
862 CommonEngineInit(); // recalculate time odds
863 if(gameInfo.variant != StringToVariant(appData.variant)) {
864 // we changed variant when loading the engine; this forces us to reset
865 Reset(TRUE, savCps != &first);
866 oldMode = BeginningOfGame; // to prevent restoring old mode
868 InitChessProgram(savCps, FALSE);
869 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
870 DisplayMessage("", "");
871 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
872 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
875 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
879 ReplaceEngine (ChessProgramState *cps, int n)
881 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
883 if(oldMode != BeginningOfGame) EditGameEvent();
886 appData.noChessProgram = FALSE;
887 appData.clockMode = TRUE;
890 if(n) return; // only startup first engine immediately; second can wait
891 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
895 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
896 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
898 static char resetOptions[] =
899 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
900 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
901 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
902 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
905 FloatToFront(char **list, char *engineLine)
907 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
909 if(appData.recentEngines <= 0) return;
910 TidyProgramName(engineLine, "localhost", tidy+1);
911 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
912 strncpy(buf+1, *list, MSG_SIZ-50);
913 if(p = strstr(buf, tidy)) { // tidy name appears in list
914 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
915 while(*p++ = *++q); // squeeze out
917 strcat(tidy, buf+1); // put list behind tidy name
918 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
919 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
920 ASSIGN(*list, tidy+1);
923 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
926 Load (ChessProgramState *cps, int i)
928 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
929 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
930 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
931 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
932 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
933 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
934 appData.firstProtocolVersion = PROTOVER;
935 ParseArgsFromString(buf);
937 ReplaceEngine(cps, i);
938 FloatToFront(&appData.recentEngineList, engineLine);
942 while(q = strchr(p, SLASH)) p = q+1;
943 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
944 if(engineDir[0] != NULLCHAR) {
945 ASSIGN(appData.directory[i], engineDir); p = engineName;
946 } else if(p != engineName) { // derive directory from engine path, when not given
948 ASSIGN(appData.directory[i], engineName);
950 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
951 } else { ASSIGN(appData.directory[i], "."); }
953 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
954 snprintf(command, MSG_SIZ, "%s %s", p, params);
957 ASSIGN(appData.chessProgram[i], p);
958 appData.isUCI[i] = isUCI;
959 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
960 appData.hasOwnBookUCI[i] = hasBook;
961 if(!nickName[0]) useNick = FALSE;
962 if(useNick) ASSIGN(appData.pgnName[i], nickName);
966 q = firstChessProgramNames;
967 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
968 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
969 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
970 quote, p, quote, appData.directory[i],
971 useNick ? " -fn \"" : "",
972 useNick ? nickName : "",
974 v1 ? " -firstProtocolVersion 1" : "",
975 hasBook ? "" : " -fNoOwnBookUCI",
976 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
977 storeVariant ? " -variant " : "",
978 storeVariant ? VariantName(gameInfo.variant) : "");
979 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
980 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
981 if(insert != q) insert[-1] = NULLCHAR;
982 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
984 FloatToFront(&appData.recentEngineList, buf);
986 ReplaceEngine(cps, i);
992 int matched, min, sec;
994 * Parse timeControl resource
996 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
997 appData.movesPerSession)) {
999 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1000 DisplayFatalError(buf, 0, 2);
1004 * Parse searchTime resource
1006 if (*appData.searchTime != NULLCHAR) {
1007 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1009 searchTime = min * 60;
1010 } else if (matched == 2) {
1011 searchTime = min * 60 + sec;
1014 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1015 DisplayFatalError(buf, 0, 2);
1024 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1025 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1027 GetTimeMark(&programStartTime);
1028 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1029 appData.seedBase = random() + (random()<<15);
1030 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1032 ClearProgramStats();
1033 programStats.ok_to_send = 1;
1034 programStats.seen_stat = 0;
1037 * Initialize game list
1043 * Internet chess server status
1045 if (appData.icsActive) {
1046 appData.matchMode = FALSE;
1047 appData.matchGames = 0;
1049 appData.noChessProgram = !appData.zippyPlay;
1051 appData.zippyPlay = FALSE;
1052 appData.zippyTalk = FALSE;
1053 appData.noChessProgram = TRUE;
1055 if (*appData.icsHelper != NULLCHAR) {
1056 appData.useTelnet = TRUE;
1057 appData.telnetProgram = appData.icsHelper;
1060 appData.zippyTalk = appData.zippyPlay = FALSE;
1063 /* [AS] Initialize pv info list [HGM] and game state */
1067 for( i=0; i<=framePtr; i++ ) {
1068 pvInfoList[i].depth = -1;
1069 boards[i][EP_STATUS] = EP_NONE;
1070 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1076 /* [AS] Adjudication threshold */
1077 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1079 InitEngine(&first, 0);
1080 InitEngine(&second, 1);
1083 pairing.which = "pairing"; // pairing engine
1084 pairing.pr = NoProc;
1086 pairing.program = appData.pairingEngine;
1087 pairing.host = "localhost";
1090 if (appData.icsActive) {
1091 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1092 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1093 appData.clockMode = FALSE;
1094 first.sendTime = second.sendTime = 0;
1098 /* Override some settings from environment variables, for backward
1099 compatibility. Unfortunately it's not feasible to have the env
1100 vars just set defaults, at least in xboard. Ugh.
1102 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1107 if (!appData.icsActive) {
1111 /* Check for variants that are supported only in ICS mode,
1112 or not at all. Some that are accepted here nevertheless
1113 have bugs; see comments below.
1115 VariantClass variant = StringToVariant(appData.variant);
1117 case VariantBughouse: /* need four players and two boards */
1118 case VariantKriegspiel: /* need to hide pieces and move details */
1119 /* case VariantFischeRandom: (Fabien: moved below) */
1120 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1121 if( (len >= MSG_SIZ) && appData.debugMode )
1122 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1124 DisplayFatalError(buf, 0, 2);
1127 case VariantUnknown:
1128 case VariantLoadable:
1138 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1139 if( (len >= MSG_SIZ) && appData.debugMode )
1140 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1142 DisplayFatalError(buf, 0, 2);
1145 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1146 case VariantFairy: /* [HGM] TestLegality definitely off! */
1147 case VariantGothic: /* [HGM] should work */
1148 case VariantCapablanca: /* [HGM] should work */
1149 case VariantCourier: /* [HGM] initial forced moves not implemented */
1150 case VariantShogi: /* [HGM] could still mate with pawn drop */
1151 case VariantKnightmate: /* [HGM] should work */
1152 case VariantCylinder: /* [HGM] untested */
1153 case VariantFalcon: /* [HGM] untested */
1154 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1155 offboard interposition not understood */
1156 case VariantNormal: /* definitely works! */
1157 case VariantWildCastle: /* pieces not automatically shuffled */
1158 case VariantNoCastle: /* pieces not automatically shuffled */
1159 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1160 case VariantLosers: /* should work except for win condition,
1161 and doesn't know captures are mandatory */
1162 case VariantSuicide: /* should work except for win condition,
1163 and doesn't know captures are mandatory */
1164 case VariantGiveaway: /* should work except for win condition,
1165 and doesn't know captures are mandatory */
1166 case VariantTwoKings: /* should work */
1167 case VariantAtomic: /* should work except for win condition */
1168 case Variant3Check: /* should work except for win condition */
1169 case VariantShatranj: /* should work except for all win conditions */
1170 case VariantMakruk: /* should work except for draw countdown */
1171 case VariantASEAN : /* should work except for draw countdown */
1172 case VariantBerolina: /* might work if TestLegality is off */
1173 case VariantCapaRandom: /* should work */
1174 case VariantJanus: /* should work */
1175 case VariantSuper: /* experimental */
1176 case VariantGreat: /* experimental, requires legality testing to be off */
1177 case VariantSChess: /* S-Chess, should work */
1178 case VariantGrand: /* should work */
1179 case VariantSpartan: /* should work */
1187 NextIntegerFromString (char ** str, long * value)
1192 while( *s == ' ' || *s == '\t' ) {
1198 if( *s >= '0' && *s <= '9' ) {
1199 while( *s >= '0' && *s <= '9' ) {
1200 *value = *value * 10 + (*s - '0');
1213 NextTimeControlFromString (char ** str, long * value)
1216 int result = NextIntegerFromString( str, &temp );
1219 *value = temp * 60; /* Minutes */
1220 if( **str == ':' ) {
1222 result = NextIntegerFromString( str, &temp );
1223 *value += temp; /* Seconds */
1231 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1232 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1233 int result = -1, type = 0; long temp, temp2;
1235 if(**str != ':') return -1; // old params remain in force!
1237 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1238 if( NextIntegerFromString( str, &temp ) ) return -1;
1239 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1242 /* time only: incremental or sudden-death time control */
1243 if(**str == '+') { /* increment follows; read it */
1245 if(**str == '!') type = *(*str)++; // Bronstein TC
1246 if(result = NextIntegerFromString( str, &temp2)) return -1;
1247 *inc = temp2 * 1000;
1248 if(**str == '.') { // read fraction of increment
1249 char *start = ++(*str);
1250 if(result = NextIntegerFromString( str, &temp2)) return -1;
1252 while(start++ < *str) temp2 /= 10;
1256 *moves = 0; *tc = temp * 1000; *incType = type;
1260 (*str)++; /* classical time control */
1261 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1273 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1274 { /* [HGM] get time to add from the multi-session time-control string */
1275 int incType, moves=1; /* kludge to force reading of first session */
1276 long time, increment;
1279 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1281 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1282 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1283 if(movenr == -1) return time; /* last move before new session */
1284 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1285 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1286 if(!moves) return increment; /* current session is incremental */
1287 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1288 } while(movenr >= -1); /* try again for next session */
1290 return 0; // no new time quota on this move
1294 ParseTimeControl (char *tc, float ti, int mps)
1298 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1301 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1302 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1303 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1307 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1309 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1312 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1314 snprintf(buf, MSG_SIZ, ":%s", mytc);
1316 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1318 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1323 /* Parse second time control */
1326 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1334 timeControl_2 = tc2 * 1000;
1344 timeControl = tc1 * 1000;
1347 timeIncrement = ti * 1000; /* convert to ms */
1348 movesPerSession = 0;
1351 movesPerSession = mps;
1359 if (appData.debugMode) {
1360 # ifdef __GIT_VERSION
1361 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1363 fprintf(debugFP, "Version: %s\n", programVersion);
1366 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1368 set_cont_sequence(appData.wrapContSeq);
1369 if (appData.matchGames > 0) {
1370 appData.matchMode = TRUE;
1371 } else if (appData.matchMode) {
1372 appData.matchGames = 1;
1374 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1375 appData.matchGames = appData.sameColorGames;
1376 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1377 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1378 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1381 if (appData.noChessProgram || first.protocolVersion == 1) {
1384 /* kludge: allow timeout for initial "feature" commands */
1386 DisplayMessage("", _("Starting chess program"));
1387 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1392 CalculateIndex (int index, int gameNr)
1393 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1395 if(index > 0) return index; // fixed nmber
1396 if(index == 0) return 1;
1397 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1398 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1403 LoadGameOrPosition (int gameNr)
1404 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1405 if (*appData.loadGameFile != NULLCHAR) {
1406 if (!LoadGameFromFile(appData.loadGameFile,
1407 CalculateIndex(appData.loadGameIndex, gameNr),
1408 appData.loadGameFile, FALSE)) {
1409 DisplayFatalError(_("Bad game file"), 0, 1);
1412 } else if (*appData.loadPositionFile != NULLCHAR) {
1413 if (!LoadPositionFromFile(appData.loadPositionFile,
1414 CalculateIndex(appData.loadPositionIndex, gameNr),
1415 appData.loadPositionFile)) {
1416 DisplayFatalError(_("Bad position file"), 0, 1);
1424 ReserveGame (int gameNr, char resChar)
1426 FILE *tf = fopen(appData.tourneyFile, "r+");
1427 char *p, *q, c, buf[MSG_SIZ];
1428 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1429 safeStrCpy(buf, lastMsg, MSG_SIZ);
1430 DisplayMessage(_("Pick new game"), "");
1431 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1432 ParseArgsFromFile(tf);
1433 p = q = appData.results;
1434 if(appData.debugMode) {
1435 char *r = appData.participants;
1436 fprintf(debugFP, "results = '%s'\n", p);
1437 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1438 fprintf(debugFP, "\n");
1440 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1442 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1443 safeStrCpy(q, p, strlen(p) + 2);
1444 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1445 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1446 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1447 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1450 fseek(tf, -(strlen(p)+4), SEEK_END);
1452 if(c != '"') // depending on DOS or Unix line endings we can be one off
1453 fseek(tf, -(strlen(p)+2), SEEK_END);
1454 else fseek(tf, -(strlen(p)+3), SEEK_END);
1455 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1456 DisplayMessage(buf, "");
1457 free(p); appData.results = q;
1458 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1459 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1460 int round = appData.defaultMatchGames * appData.tourneyType;
1461 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1462 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1463 UnloadEngine(&first); // next game belongs to other pairing;
1464 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1466 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1470 MatchEvent (int mode)
1471 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1473 if(matchMode) { // already in match mode: switch it off
1475 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1478 // if(gameMode != BeginningOfGame) {
1479 // DisplayError(_("You can only start a match from the initial position."), 0);
1483 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1484 /* Set up machine vs. machine match */
1486 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1487 if(appData.tourneyFile[0]) {
1489 if(nextGame > appData.matchGames) {
1491 if(strchr(appData.results, '*') == NULL) {
1493 appData.tourneyCycles++;
1494 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1496 NextTourneyGame(-1, &dummy);
1498 if(nextGame <= appData.matchGames) {
1499 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1501 ScheduleDelayedEvent(NextMatchGame, 10000);
1506 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1507 DisplayError(buf, 0);
1508 appData.tourneyFile[0] = 0;
1512 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1513 DisplayFatalError(_("Can't have a match with no chess programs"),
1518 matchGame = roundNr = 1;
1519 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1523 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1526 InitBackEnd3 P((void))
1528 GameMode initialMode;
1532 InitChessProgram(&first, startedFromSetupPosition);
1534 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1535 free(programVersion);
1536 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1537 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1538 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1541 if (appData.icsActive) {
1543 /* [DM] Make a console window if needed [HGM] merged ifs */
1549 if (*appData.icsCommPort != NULLCHAR)
1550 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1551 appData.icsCommPort);
1553 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1554 appData.icsHost, appData.icsPort);
1556 if( (len >= MSG_SIZ) && appData.debugMode )
1557 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1559 DisplayFatalError(buf, err, 1);
1564 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1566 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1567 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1568 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1569 } else if (appData.noChessProgram) {
1575 if (*appData.cmailGameName != NULLCHAR) {
1577 OpenLoopback(&cmailPR);
1579 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1583 DisplayMessage("", "");
1584 if (StrCaseCmp(appData.initialMode, "") == 0) {
1585 initialMode = BeginningOfGame;
1586 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1587 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1588 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1589 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1592 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1593 initialMode = TwoMachinesPlay;
1594 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1595 initialMode = AnalyzeFile;
1596 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1597 initialMode = AnalyzeMode;
1598 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1599 initialMode = MachinePlaysWhite;
1600 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1601 initialMode = MachinePlaysBlack;
1602 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1603 initialMode = EditGame;
1604 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1605 initialMode = EditPosition;
1606 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1607 initialMode = Training;
1609 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1610 if( (len >= MSG_SIZ) && appData.debugMode )
1611 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1613 DisplayFatalError(buf, 0, 2);
1617 if (appData.matchMode) {
1618 if(appData.tourneyFile[0]) { // start tourney from command line
1620 if(f = fopen(appData.tourneyFile, "r")) {
1621 ParseArgsFromFile(f); // make sure tourney parmeters re known
1623 appData.clockMode = TRUE;
1625 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1628 } else if (*appData.cmailGameName != NULLCHAR) {
1629 /* Set up cmail mode */
1630 ReloadCmailMsgEvent(TRUE);
1632 /* Set up other modes */
1633 if (initialMode == AnalyzeFile) {
1634 if (*appData.loadGameFile == NULLCHAR) {
1635 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1639 if (*appData.loadGameFile != NULLCHAR) {
1640 (void) LoadGameFromFile(appData.loadGameFile,
1641 appData.loadGameIndex,
1642 appData.loadGameFile, TRUE);
1643 } else if (*appData.loadPositionFile != NULLCHAR) {
1644 (void) LoadPositionFromFile(appData.loadPositionFile,
1645 appData.loadPositionIndex,
1646 appData.loadPositionFile);
1647 /* [HGM] try to make self-starting even after FEN load */
1648 /* to allow automatic setup of fairy variants with wtm */
1649 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1650 gameMode = BeginningOfGame;
1651 setboardSpoiledMachineBlack = 1;
1653 /* [HGM] loadPos: make that every new game uses the setup */
1654 /* from file as long as we do not switch variant */
1655 if(!blackPlaysFirst) {
1656 startedFromPositionFile = TRUE;
1657 CopyBoard(filePosition, boards[0]);
1660 if (initialMode == AnalyzeMode) {
1661 if (appData.noChessProgram) {
1662 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1665 if (appData.icsActive) {
1666 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1670 } else if (initialMode == AnalyzeFile) {
1671 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1672 ShowThinkingEvent();
1674 AnalysisPeriodicEvent(1);
1675 } else if (initialMode == MachinePlaysWhite) {
1676 if (appData.noChessProgram) {
1677 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1681 if (appData.icsActive) {
1682 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1686 MachineWhiteEvent();
1687 } else if (initialMode == MachinePlaysBlack) {
1688 if (appData.noChessProgram) {
1689 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1693 if (appData.icsActive) {
1694 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1698 MachineBlackEvent();
1699 } else if (initialMode == TwoMachinesPlay) {
1700 if (appData.noChessProgram) {
1701 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1705 if (appData.icsActive) {
1706 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1711 } else if (initialMode == EditGame) {
1713 } else if (initialMode == EditPosition) {
1714 EditPositionEvent();
1715 } else if (initialMode == Training) {
1716 if (*appData.loadGameFile == NULLCHAR) {
1717 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1726 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1728 DisplayBook(current+1);
1730 MoveHistorySet( movelist, first, last, current, pvInfoList );
1732 EvalGraphSet( first, last, current, pvInfoList );
1734 MakeEngineOutputTitle();
1738 * Establish will establish a contact to a remote host.port.
1739 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1740 * used to talk to the host.
1741 * Returns 0 if okay, error code if not.
1748 if (*appData.icsCommPort != NULLCHAR) {
1749 /* Talk to the host through a serial comm port */
1750 return OpenCommPort(appData.icsCommPort, &icsPR);
1752 } else if (*appData.gateway != NULLCHAR) {
1753 if (*appData.remoteShell == NULLCHAR) {
1754 /* Use the rcmd protocol to run telnet program on a gateway host */
1755 snprintf(buf, sizeof(buf), "%s %s %s",
1756 appData.telnetProgram, appData.icsHost, appData.icsPort);
1757 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1760 /* Use the rsh program to run telnet program on a gateway host */
1761 if (*appData.remoteUser == NULLCHAR) {
1762 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1763 appData.gateway, appData.telnetProgram,
1764 appData.icsHost, appData.icsPort);
1766 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1767 appData.remoteShell, appData.gateway,
1768 appData.remoteUser, appData.telnetProgram,
1769 appData.icsHost, appData.icsPort);
1771 return StartChildProcess(buf, "", &icsPR);
1774 } else if (appData.useTelnet) {
1775 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1778 /* TCP socket interface differs somewhat between
1779 Unix and NT; handle details in the front end.
1781 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1786 EscapeExpand (char *p, char *q)
1787 { // [HGM] initstring: routine to shape up string arguments
1788 while(*p++ = *q++) if(p[-1] == '\\')
1790 case 'n': p[-1] = '\n'; break;
1791 case 'r': p[-1] = '\r'; break;
1792 case 't': p[-1] = '\t'; break;
1793 case '\\': p[-1] = '\\'; break;
1794 case 0: *p = 0; return;
1795 default: p[-1] = q[-1]; break;
1800 show_bytes (FILE *fp, char *buf, int count)
1803 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1804 fprintf(fp, "\\%03o", *buf & 0xff);
1813 /* Returns an errno value */
1815 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1817 char buf[8192], *p, *q, *buflim;
1818 int left, newcount, outcount;
1820 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1821 *appData.gateway != NULLCHAR) {
1822 if (appData.debugMode) {
1823 fprintf(debugFP, ">ICS: ");
1824 show_bytes(debugFP, message, count);
1825 fprintf(debugFP, "\n");
1827 return OutputToProcess(pr, message, count, outError);
1830 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1837 if (appData.debugMode) {
1838 fprintf(debugFP, ">ICS: ");
1839 show_bytes(debugFP, buf, newcount);
1840 fprintf(debugFP, "\n");
1842 outcount = OutputToProcess(pr, buf, newcount, outError);
1843 if (outcount < newcount) return -1; /* to be sure */
1850 } else if (((unsigned char) *p) == TN_IAC) {
1851 *q++ = (char) TN_IAC;
1858 if (appData.debugMode) {
1859 fprintf(debugFP, ">ICS: ");
1860 show_bytes(debugFP, buf, newcount);
1861 fprintf(debugFP, "\n");
1863 outcount = OutputToProcess(pr, buf, newcount, outError);
1864 if (outcount < newcount) return -1; /* to be sure */
1869 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1871 int outError, outCount;
1872 static int gotEof = 0;
1875 /* Pass data read from player on to ICS */
1878 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1879 if (outCount < count) {
1880 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1882 if(have_sent_ICS_logon == 2) {
1883 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1884 fprintf(ini, "%s", message);
1885 have_sent_ICS_logon = 3;
1887 have_sent_ICS_logon = 1;
1888 } else if(have_sent_ICS_logon == 3) {
1889 fprintf(ini, "%s", message);
1891 have_sent_ICS_logon = 1;
1893 } else if (count < 0) {
1894 RemoveInputSource(isr);
1895 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1896 } else if (gotEof++ > 0) {
1897 RemoveInputSource(isr);
1898 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1904 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1905 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1906 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1907 SendToICS("date\n");
1908 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1911 /* added routine for printf style output to ics */
1913 ics_printf (char *format, ...)
1915 char buffer[MSG_SIZ];
1918 va_start(args, format);
1919 vsnprintf(buffer, sizeof(buffer), format, args);
1920 buffer[sizeof(buffer)-1] = '\0';
1928 int count, outCount, outError;
1930 if (icsPR == NoProc) return;
1933 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1934 if (outCount < count) {
1935 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1939 /* This is used for sending logon scripts to the ICS. Sending
1940 without a delay causes problems when using timestamp on ICC
1941 (at least on my machine). */
1943 SendToICSDelayed (char *s, long msdelay)
1945 int count, outCount, outError;
1947 if (icsPR == NoProc) return;
1950 if (appData.debugMode) {
1951 fprintf(debugFP, ">ICS: ");
1952 show_bytes(debugFP, s, count);
1953 fprintf(debugFP, "\n");
1955 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1957 if (outCount < count) {
1958 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1963 /* Remove all highlighting escape sequences in s
1964 Also deletes any suffix starting with '('
1967 StripHighlightAndTitle (char *s)
1969 static char retbuf[MSG_SIZ];
1972 while (*s != NULLCHAR) {
1973 while (*s == '\033') {
1974 while (*s != NULLCHAR && !isalpha(*s)) s++;
1975 if (*s != NULLCHAR) s++;
1977 while (*s != NULLCHAR && *s != '\033') {
1978 if (*s == '(' || *s == '[') {
1989 /* Remove all highlighting escape sequences in s */
1991 StripHighlight (char *s)
1993 static char retbuf[MSG_SIZ];
1996 while (*s != NULLCHAR) {
1997 while (*s == '\033') {
1998 while (*s != NULLCHAR && !isalpha(*s)) s++;
1999 if (*s != NULLCHAR) s++;
2001 while (*s != NULLCHAR && *s != '\033') {
2009 char engineVariant[MSG_SIZ];
2010 char *variantNames[] = VARIANT_NAMES;
2012 VariantName (VariantClass v)
2014 if(v == VariantUnknown || *engineVariant) return engineVariant;
2015 return variantNames[v];
2019 /* Identify a variant from the strings the chess servers use or the
2020 PGN Variant tag names we use. */
2022 StringToVariant (char *e)
2026 VariantClass v = VariantNormal;
2027 int i, found = FALSE;
2033 /* [HGM] skip over optional board-size prefixes */
2034 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2035 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2036 while( *e++ != '_');
2039 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2043 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2044 if (StrCaseStr(e, variantNames[i])) {
2045 v = (VariantClass) i;
2052 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2053 || StrCaseStr(e, "wild/fr")
2054 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2055 v = VariantFischeRandom;
2056 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2057 (i = 1, p = StrCaseStr(e, "w"))) {
2059 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2066 case 0: /* FICS only, actually */
2068 /* Castling legal even if K starts on d-file */
2069 v = VariantWildCastle;
2074 /* Castling illegal even if K & R happen to start in
2075 normal positions. */
2076 v = VariantNoCastle;
2089 /* Castling legal iff K & R start in normal positions */
2095 /* Special wilds for position setup; unclear what to do here */
2096 v = VariantLoadable;
2099 /* Bizarre ICC game */
2100 v = VariantTwoKings;
2103 v = VariantKriegspiel;
2109 v = VariantFischeRandom;
2112 v = VariantCrazyhouse;
2115 v = VariantBughouse;
2121 /* Not quite the same as FICS suicide! */
2122 v = VariantGiveaway;
2128 v = VariantShatranj;
2131 /* Temporary names for future ICC types. The name *will* change in
2132 the next xboard/WinBoard release after ICC defines it. */
2170 v = VariantCapablanca;
2173 v = VariantKnightmate;
2179 v = VariantCylinder;
2185 v = VariantCapaRandom;
2188 v = VariantBerolina;
2200 /* Found "wild" or "w" in the string but no number;
2201 must assume it's normal chess. */
2205 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2206 if( (len >= MSG_SIZ) && appData.debugMode )
2207 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2209 DisplayError(buf, 0);
2215 if (appData.debugMode) {
2216 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2217 e, wnum, VariantName(v));
2222 static int leftover_start = 0, leftover_len = 0;
2223 char star_match[STAR_MATCH_N][MSG_SIZ];
2225 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2226 advance *index beyond it, and set leftover_start to the new value of
2227 *index; else return FALSE. If pattern contains the character '*', it
2228 matches any sequence of characters not containing '\r', '\n', or the
2229 character following the '*' (if any), and the matched sequence(s) are
2230 copied into star_match.
2233 looking_at ( char *buf, int *index, char *pattern)
2235 char *bufp = &buf[*index], *patternp = pattern;
2237 char *matchp = star_match[0];
2240 if (*patternp == NULLCHAR) {
2241 *index = leftover_start = bufp - buf;
2245 if (*bufp == NULLCHAR) return FALSE;
2246 if (*patternp == '*') {
2247 if (*bufp == *(patternp + 1)) {
2249 matchp = star_match[++star_count];
2253 } else if (*bufp == '\n' || *bufp == '\r') {
2255 if (*patternp == NULLCHAR)
2260 *matchp++ = *bufp++;
2264 if (*patternp != *bufp) return FALSE;
2271 SendToPlayer (char *data, int length)
2273 int error, outCount;
2274 outCount = OutputToProcess(NoProc, data, length, &error);
2275 if (outCount < length) {
2276 DisplayFatalError(_("Error writing to display"), error, 1);
2281 PackHolding (char packed[], char *holding)
2291 switch (runlength) {
2302 sprintf(q, "%d", runlength);
2314 /* Telnet protocol requests from the front end */
2316 TelnetRequest (unsigned char ddww, unsigned char option)
2318 unsigned char msg[3];
2319 int outCount, outError;
2321 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2323 if (appData.debugMode) {
2324 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2340 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2349 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2352 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2357 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2359 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2366 if (!appData.icsActive) return;
2367 TelnetRequest(TN_DO, TN_ECHO);
2373 if (!appData.icsActive) return;
2374 TelnetRequest(TN_DONT, TN_ECHO);
2378 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2380 /* put the holdings sent to us by the server on the board holdings area */
2381 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2385 if(gameInfo.holdingsWidth < 2) return;
2386 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2387 return; // prevent overwriting by pre-board holdings
2389 if( (int)lowestPiece >= BlackPawn ) {
2392 holdingsStartRow = BOARD_HEIGHT-1;
2395 holdingsColumn = BOARD_WIDTH-1;
2396 countsColumn = BOARD_WIDTH-2;
2397 holdingsStartRow = 0;
2401 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2402 board[i][holdingsColumn] = EmptySquare;
2403 board[i][countsColumn] = (ChessSquare) 0;
2405 while( (p=*holdings++) != NULLCHAR ) {
2406 piece = CharToPiece( ToUpper(p) );
2407 if(piece == EmptySquare) continue;
2408 /*j = (int) piece - (int) WhitePawn;*/
2409 j = PieceToNumber(piece);
2410 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2411 if(j < 0) continue; /* should not happen */
2412 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2413 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2414 board[holdingsStartRow+j*direction][countsColumn]++;
2420 VariantSwitch (Board board, VariantClass newVariant)
2422 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2423 static Board oldBoard;
2425 startedFromPositionFile = FALSE;
2426 if(gameInfo.variant == newVariant) return;
2428 /* [HGM] This routine is called each time an assignment is made to
2429 * gameInfo.variant during a game, to make sure the board sizes
2430 * are set to match the new variant. If that means adding or deleting
2431 * holdings, we shift the playing board accordingly
2432 * This kludge is needed because in ICS observe mode, we get boards
2433 * of an ongoing game without knowing the variant, and learn about the
2434 * latter only later. This can be because of the move list we requested,
2435 * in which case the game history is refilled from the beginning anyway,
2436 * but also when receiving holdings of a crazyhouse game. In the latter
2437 * case we want to add those holdings to the already received position.
2441 if (appData.debugMode) {
2442 fprintf(debugFP, "Switch board from %s to %s\n",
2443 VariantName(gameInfo.variant), VariantName(newVariant));
2444 setbuf(debugFP, NULL);
2446 shuffleOpenings = 0; /* [HGM] shuffle */
2447 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2451 newWidth = 9; newHeight = 9;
2452 gameInfo.holdingsSize = 7;
2453 case VariantBughouse:
2454 case VariantCrazyhouse:
2455 newHoldingsWidth = 2; break;
2459 newHoldingsWidth = 2;
2460 gameInfo.holdingsSize = 8;
2463 case VariantCapablanca:
2464 case VariantCapaRandom:
2467 newHoldingsWidth = gameInfo.holdingsSize = 0;
2470 if(newWidth != gameInfo.boardWidth ||
2471 newHeight != gameInfo.boardHeight ||
2472 newHoldingsWidth != gameInfo.holdingsWidth ) {
2474 /* shift position to new playing area, if needed */
2475 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2476 for(i=0; i<BOARD_HEIGHT; i++)
2477 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2478 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2480 for(i=0; i<newHeight; i++) {
2481 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2482 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2484 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2485 for(i=0; i<BOARD_HEIGHT; i++)
2486 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2487 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2490 board[HOLDINGS_SET] = 0;
2491 gameInfo.boardWidth = newWidth;
2492 gameInfo.boardHeight = newHeight;
2493 gameInfo.holdingsWidth = newHoldingsWidth;
2494 gameInfo.variant = newVariant;
2495 InitDrawingSizes(-2, 0);
2496 } else gameInfo.variant = newVariant;
2497 CopyBoard(oldBoard, board); // remember correctly formatted board
2498 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2499 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2502 static int loggedOn = FALSE;
2504 /*-- Game start info cache: --*/
2506 char gs_kind[MSG_SIZ];
2507 static char player1Name[128] = "";
2508 static char player2Name[128] = "";
2509 static char cont_seq[] = "\n\\ ";
2510 static int player1Rating = -1;
2511 static int player2Rating = -1;
2512 /*----------------------------*/
2514 ColorClass curColor = ColorNormal;
2515 int suppressKibitz = 0;
2518 Boolean soughtPending = FALSE;
2519 Boolean seekGraphUp;
2520 #define MAX_SEEK_ADS 200
2522 char *seekAdList[MAX_SEEK_ADS];
2523 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2524 float tcList[MAX_SEEK_ADS];
2525 char colorList[MAX_SEEK_ADS];
2526 int nrOfSeekAds = 0;
2527 int minRating = 1010, maxRating = 2800;
2528 int hMargin = 10, vMargin = 20, h, w;
2529 extern int squareSize, lineGap;
2534 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2535 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2536 if(r < minRating+100 && r >=0 ) r = minRating+100;
2537 if(r > maxRating) r = maxRating;
2538 if(tc < 1.f) tc = 1.f;
2539 if(tc > 95.f) tc = 95.f;
2540 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2541 y = ((double)r - minRating)/(maxRating - minRating)
2542 * (h-vMargin-squareSize/8-1) + vMargin;
2543 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2544 if(strstr(seekAdList[i], " u ")) color = 1;
2545 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2546 !strstr(seekAdList[i], "bullet") &&
2547 !strstr(seekAdList[i], "blitz") &&
2548 !strstr(seekAdList[i], "standard") ) color = 2;
2549 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2550 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2554 PlotSingleSeekAd (int i)
2560 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2562 char buf[MSG_SIZ], *ext = "";
2563 VariantClass v = StringToVariant(type);
2564 if(strstr(type, "wild")) {
2565 ext = type + 4; // append wild number
2566 if(v == VariantFischeRandom) type = "chess960"; else
2567 if(v == VariantLoadable) type = "setup"; else
2568 type = VariantName(v);
2570 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2571 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2572 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2573 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2574 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2575 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2576 seekNrList[nrOfSeekAds] = nr;
2577 zList[nrOfSeekAds] = 0;
2578 seekAdList[nrOfSeekAds++] = StrSave(buf);
2579 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2584 EraseSeekDot (int i)
2586 int x = xList[i], y = yList[i], d=squareSize/4, k;
2587 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2588 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2589 // now replot every dot that overlapped
2590 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2591 int xx = xList[k], yy = yList[k];
2592 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2593 DrawSeekDot(xx, yy, colorList[k]);
2598 RemoveSeekAd (int nr)
2601 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2603 if(seekAdList[i]) free(seekAdList[i]);
2604 seekAdList[i] = seekAdList[--nrOfSeekAds];
2605 seekNrList[i] = seekNrList[nrOfSeekAds];
2606 ratingList[i] = ratingList[nrOfSeekAds];
2607 colorList[i] = colorList[nrOfSeekAds];
2608 tcList[i] = tcList[nrOfSeekAds];
2609 xList[i] = xList[nrOfSeekAds];
2610 yList[i] = yList[nrOfSeekAds];
2611 zList[i] = zList[nrOfSeekAds];
2612 seekAdList[nrOfSeekAds] = NULL;
2618 MatchSoughtLine (char *line)
2620 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2621 int nr, base, inc, u=0; char dummy;
2623 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2624 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2626 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2627 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2628 // match: compact and save the line
2629 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2639 if(!seekGraphUp) return FALSE;
2640 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2641 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2643 DrawSeekBackground(0, 0, w, h);
2644 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2645 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2646 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2647 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2649 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2652 snprintf(buf, MSG_SIZ, "%d", i);
2653 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2656 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2657 for(i=1; i<100; i+=(i<10?1:5)) {
2658 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2659 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2660 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2662 snprintf(buf, MSG_SIZ, "%d", i);
2663 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2666 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2671 SeekGraphClick (ClickType click, int x, int y, int moving)
2673 static int lastDown = 0, displayed = 0, lastSecond;
2674 if(y < 0) return FALSE;
2675 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2676 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2677 if(!seekGraphUp) return FALSE;
2678 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2679 DrawPosition(TRUE, NULL);
2682 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2683 if(click == Release || moving) return FALSE;
2685 soughtPending = TRUE;
2686 SendToICS(ics_prefix);
2687 SendToICS("sought\n"); // should this be "sought all"?
2688 } else { // issue challenge based on clicked ad
2689 int dist = 10000; int i, closest = 0, second = 0;
2690 for(i=0; i<nrOfSeekAds; i++) {
2691 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2692 if(d < dist) { dist = d; closest = i; }
2693 second += (d - zList[i] < 120); // count in-range ads
2694 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2698 second = (second > 1);
2699 if(displayed != closest || second != lastSecond) {
2700 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2701 lastSecond = second; displayed = closest;
2703 if(click == Press) {
2704 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2707 } // on press 'hit', only show info
2708 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2709 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2710 SendToICS(ics_prefix);
2712 return TRUE; // let incoming board of started game pop down the graph
2713 } else if(click == Release) { // release 'miss' is ignored
2714 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2715 if(moving == 2) { // right up-click
2716 nrOfSeekAds = 0; // refresh graph
2717 soughtPending = TRUE;
2718 SendToICS(ics_prefix);
2719 SendToICS("sought\n"); // should this be "sought all"?
2722 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2723 // press miss or release hit 'pop down' seek graph
2724 seekGraphUp = FALSE;
2725 DrawPosition(TRUE, NULL);
2731 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2733 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2734 #define STARTED_NONE 0
2735 #define STARTED_MOVES 1
2736 #define STARTED_BOARD 2
2737 #define STARTED_OBSERVE 3
2738 #define STARTED_HOLDINGS 4
2739 #define STARTED_CHATTER 5
2740 #define STARTED_COMMENT 6
2741 #define STARTED_MOVES_NOHIDE 7
2743 static int started = STARTED_NONE;
2744 static char parse[20000];
2745 static int parse_pos = 0;
2746 static char buf[BUF_SIZE + 1];
2747 static int firstTime = TRUE, intfSet = FALSE;
2748 static ColorClass prevColor = ColorNormal;
2749 static int savingComment = FALSE;
2750 static int cmatch = 0; // continuation sequence match
2757 int backup; /* [DM] For zippy color lines */
2759 char talker[MSG_SIZ]; // [HGM] chat
2762 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2764 if (appData.debugMode) {
2766 fprintf(debugFP, "<ICS: ");
2767 show_bytes(debugFP, data, count);
2768 fprintf(debugFP, "\n");
2772 if (appData.debugMode) { int f = forwardMostMove;
2773 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2774 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2775 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2778 /* If last read ended with a partial line that we couldn't parse,
2779 prepend it to the new read and try again. */
2780 if (leftover_len > 0) {
2781 for (i=0; i<leftover_len; i++)
2782 buf[i] = buf[leftover_start + i];
2785 /* copy new characters into the buffer */
2786 bp = buf + leftover_len;
2787 buf_len=leftover_len;
2788 for (i=0; i<count; i++)
2791 if (data[i] == '\r')
2794 // join lines split by ICS?
2795 if (!appData.noJoin)
2798 Joining just consists of finding matches against the
2799 continuation sequence, and discarding that sequence
2800 if found instead of copying it. So, until a match
2801 fails, there's nothing to do since it might be the
2802 complete sequence, and thus, something we don't want
2805 if (data[i] == cont_seq[cmatch])
2808 if (cmatch == strlen(cont_seq))
2810 cmatch = 0; // complete match. just reset the counter
2813 it's possible for the ICS to not include the space
2814 at the end of the last word, making our [correct]
2815 join operation fuse two separate words. the server
2816 does this when the space occurs at the width setting.
2818 if (!buf_len || buf[buf_len-1] != ' ')
2829 match failed, so we have to copy what matched before
2830 falling through and copying this character. In reality,
2831 this will only ever be just the newline character, but
2832 it doesn't hurt to be precise.
2834 strncpy(bp, cont_seq, cmatch);
2846 buf[buf_len] = NULLCHAR;
2847 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2852 while (i < buf_len) {
2853 /* Deal with part of the TELNET option negotiation
2854 protocol. We refuse to do anything beyond the
2855 defaults, except that we allow the WILL ECHO option,
2856 which ICS uses to turn off password echoing when we are
2857 directly connected to it. We reject this option
2858 if localLineEditing mode is on (always on in xboard)
2859 and we are talking to port 23, which might be a real
2860 telnet server that will try to keep WILL ECHO on permanently.
2862 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2863 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2864 unsigned char option;
2866 switch ((unsigned char) buf[++i]) {
2868 if (appData.debugMode)
2869 fprintf(debugFP, "\n<WILL ");
2870 switch (option = (unsigned char) buf[++i]) {
2872 if (appData.debugMode)
2873 fprintf(debugFP, "ECHO ");
2874 /* Reply only if this is a change, according
2875 to the protocol rules. */
2876 if (remoteEchoOption) break;
2877 if (appData.localLineEditing &&
2878 atoi(appData.icsPort) == TN_PORT) {
2879 TelnetRequest(TN_DONT, TN_ECHO);
2882 TelnetRequest(TN_DO, TN_ECHO);
2883 remoteEchoOption = TRUE;
2887 if (appData.debugMode)
2888 fprintf(debugFP, "%d ", option);
2889 /* Whatever this is, we don't want it. */
2890 TelnetRequest(TN_DONT, option);
2895 if (appData.debugMode)
2896 fprintf(debugFP, "\n<WONT ");
2897 switch (option = (unsigned char) buf[++i]) {
2899 if (appData.debugMode)
2900 fprintf(debugFP, "ECHO ");
2901 /* Reply only if this is a change, according
2902 to the protocol rules. */
2903 if (!remoteEchoOption) break;
2905 TelnetRequest(TN_DONT, TN_ECHO);
2906 remoteEchoOption = FALSE;
2909 if (appData.debugMode)
2910 fprintf(debugFP, "%d ", (unsigned char) option);
2911 /* Whatever this is, it must already be turned
2912 off, because we never agree to turn on
2913 anything non-default, so according to the
2914 protocol rules, we don't reply. */
2919 if (appData.debugMode)
2920 fprintf(debugFP, "\n<DO ");
2921 switch (option = (unsigned char) buf[++i]) {
2923 /* Whatever this is, we refuse to do it. */
2924 if (appData.debugMode)
2925 fprintf(debugFP, "%d ", option);
2926 TelnetRequest(TN_WONT, option);
2931 if (appData.debugMode)
2932 fprintf(debugFP, "\n<DONT ");
2933 switch (option = (unsigned char) buf[++i]) {
2935 if (appData.debugMode)
2936 fprintf(debugFP, "%d ", option);
2937 /* Whatever this is, we are already not doing
2938 it, because we never agree to do anything
2939 non-default, so according to the protocol
2940 rules, we don't reply. */
2945 if (appData.debugMode)
2946 fprintf(debugFP, "\n<IAC ");
2947 /* Doubled IAC; pass it through */
2951 if (appData.debugMode)
2952 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2953 /* Drop all other telnet commands on the floor */
2956 if (oldi > next_out)
2957 SendToPlayer(&buf[next_out], oldi - next_out);
2963 /* OK, this at least will *usually* work */
2964 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2968 if (loggedOn && !intfSet) {
2969 if (ics_type == ICS_ICC) {
2970 snprintf(str, MSG_SIZ,
2971 "/set-quietly interface %s\n/set-quietly style 12\n",
2973 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2974 strcat(str, "/set-2 51 1\n/set seek 1\n");
2975 } else if (ics_type == ICS_CHESSNET) {
2976 snprintf(str, MSG_SIZ, "/style 12\n");
2978 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2979 strcat(str, programVersion);
2980 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2981 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2982 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2984 strcat(str, "$iset nohighlight 1\n");
2986 strcat(str, "$iset lock 1\n$style 12\n");
2989 NotifyFrontendLogin();
2993 if (started == STARTED_COMMENT) {
2994 /* Accumulate characters in comment */
2995 parse[parse_pos++] = buf[i];
2996 if (buf[i] == '\n') {
2997 parse[parse_pos] = NULLCHAR;
2998 if(chattingPartner>=0) {
3000 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3001 OutputChatMessage(chattingPartner, mess);
3002 chattingPartner = -1;
3003 next_out = i+1; // [HGM] suppress printing in ICS window
3005 if(!suppressKibitz) // [HGM] kibitz
3006 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3007 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3008 int nrDigit = 0, nrAlph = 0, j;
3009 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3010 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3011 parse[parse_pos] = NULLCHAR;
3012 // try to be smart: if it does not look like search info, it should go to
3013 // ICS interaction window after all, not to engine-output window.
3014 for(j=0; j<parse_pos; j++) { // count letters and digits
3015 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3016 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3017 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3019 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3020 int depth=0; float score;
3021 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3022 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3023 pvInfoList[forwardMostMove-1].depth = depth;
3024 pvInfoList[forwardMostMove-1].score = 100*score;
3026 OutputKibitz(suppressKibitz, parse);
3029 if(gameMode == IcsObserving) // restore original ICS messages
3030 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3031 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3033 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3034 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3035 SendToPlayer(tmp, strlen(tmp));
3037 next_out = i+1; // [HGM] suppress printing in ICS window
3039 started = STARTED_NONE;
3041 /* Don't match patterns against characters in comment */
3046 if (started == STARTED_CHATTER) {
3047 if (buf[i] != '\n') {
3048 /* Don't match patterns against characters in chatter */
3052 started = STARTED_NONE;
3053 if(suppressKibitz) next_out = i+1;
3056 /* Kludge to deal with rcmd protocol */
3057 if (firstTime && looking_at(buf, &i, "\001*")) {
3058 DisplayFatalError(&buf[1], 0, 1);
3064 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3067 if (appData.debugMode)
3068 fprintf(debugFP, "ics_type %d\n", ics_type);
3071 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3072 ics_type = ICS_FICS;
3074 if (appData.debugMode)
3075 fprintf(debugFP, "ics_type %d\n", ics_type);
3078 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3079 ics_type = ICS_CHESSNET;
3081 if (appData.debugMode)
3082 fprintf(debugFP, "ics_type %d\n", ics_type);
3087 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3088 looking_at(buf, &i, "Logging you in as \"*\"") ||
3089 looking_at(buf, &i, "will be \"*\""))) {
3090 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3094 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3096 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3097 DisplayIcsInteractionTitle(buf);
3098 have_set_title = TRUE;
3101 /* skip finger notes */
3102 if (started == STARTED_NONE &&
3103 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3104 (buf[i] == '1' && buf[i+1] == '0')) &&
3105 buf[i+2] == ':' && buf[i+3] == ' ') {
3106 started = STARTED_CHATTER;
3112 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3113 if(appData.seekGraph) {
3114 if(soughtPending && MatchSoughtLine(buf+i)) {
3115 i = strstr(buf+i, "rated") - buf;
3116 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3117 next_out = leftover_start = i;
3118 started = STARTED_CHATTER;
3119 suppressKibitz = TRUE;
3122 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3123 && looking_at(buf, &i, "* ads displayed")) {
3124 soughtPending = FALSE;
3129 if(appData.autoRefresh) {
3130 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3131 int s = (ics_type == ICS_ICC); // ICC format differs
3133 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3134 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3135 looking_at(buf, &i, "*% "); // eat prompt
3136 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3137 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3138 next_out = i; // suppress
3141 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3142 char *p = star_match[0];
3144 if(seekGraphUp) RemoveSeekAd(atoi(p));
3145 while(*p && *p++ != ' '); // next
3147 looking_at(buf, &i, "*% "); // eat prompt
3148 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3155 /* skip formula vars */
3156 if (started == STARTED_NONE &&
3157 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3158 started = STARTED_CHATTER;
3163 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3164 if (appData.autoKibitz && started == STARTED_NONE &&
3165 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3166 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3167 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3168 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3169 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3170 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3171 suppressKibitz = TRUE;
3172 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3174 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3175 && (gameMode == IcsPlayingWhite)) ||
3176 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3177 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3178 started = STARTED_CHATTER; // own kibitz we simply discard
3180 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3181 parse_pos = 0; parse[0] = NULLCHAR;
3182 savingComment = TRUE;
3183 suppressKibitz = gameMode != IcsObserving ? 2 :
3184 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3188 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3189 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3190 && atoi(star_match[0])) {
3191 // suppress the acknowledgements of our own autoKibitz
3193 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3194 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3195 SendToPlayer(star_match[0], strlen(star_match[0]));
3196 if(looking_at(buf, &i, "*% ")) // eat prompt
3197 suppressKibitz = FALSE;
3201 } // [HGM] kibitz: end of patch
3203 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3205 // [HGM] chat: intercept tells by users for which we have an open chat window
3207 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3208 looking_at(buf, &i, "* whispers:") ||
3209 looking_at(buf, &i, "* kibitzes:") ||
3210 looking_at(buf, &i, "* shouts:") ||
3211 looking_at(buf, &i, "* c-shouts:") ||
3212 looking_at(buf, &i, "--> * ") ||
3213 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3214 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3215 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3216 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3218 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3219 chattingPartner = -1;
3221 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3222 for(p=0; p<MAX_CHAT; p++) {
3223 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3224 talker[0] = '['; strcat(talker, "] ");
3225 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3226 chattingPartner = p; break;
3229 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3230 for(p=0; p<MAX_CHAT; p++) {
3231 if(!strcmp("kibitzes", chatPartner[p])) {
3232 talker[0] = '['; strcat(talker, "] ");
3233 chattingPartner = p; break;
3236 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3237 for(p=0; p<MAX_CHAT; p++) {
3238 if(!strcmp("whispers", chatPartner[p])) {
3239 talker[0] = '['; strcat(talker, "] ");
3240 chattingPartner = p; break;
3243 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3244 if(buf[i-8] == '-' && buf[i-3] == 't')
3245 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3246 if(!strcmp("c-shouts", chatPartner[p])) {
3247 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3248 chattingPartner = p; break;
3251 if(chattingPartner < 0)
3252 for(p=0; p<MAX_CHAT; p++) {
3253 if(!strcmp("shouts", chatPartner[p])) {
3254 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3255 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3256 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3257 chattingPartner = p; break;
3261 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3262 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3263 talker[0] = 0; Colorize(ColorTell, FALSE);
3264 chattingPartner = p; break;
3266 if(chattingPartner<0) i = oldi; else {
3267 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3268 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3269 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3270 started = STARTED_COMMENT;
3271 parse_pos = 0; parse[0] = NULLCHAR;
3272 savingComment = 3 + chattingPartner; // counts as TRUE
3273 suppressKibitz = TRUE;
3276 } // [HGM] chat: end of patch
3279 if (appData.zippyTalk || appData.zippyPlay) {
3280 /* [DM] Backup address for color zippy lines */
3282 if (loggedOn == TRUE)
3283 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3284 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3286 } // [DM] 'else { ' deleted
3288 /* Regular tells and says */
3289 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3290 looking_at(buf, &i, "* (your partner) tells you: ") ||
3291 looking_at(buf, &i, "* says: ") ||
3292 /* Don't color "message" or "messages" output */
3293 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3294 looking_at(buf, &i, "*. * at *:*: ") ||
3295 looking_at(buf, &i, "--* (*:*): ") ||
3296 /* Message notifications (same color as tells) */
3297 looking_at(buf, &i, "* has left a message ") ||
3298 looking_at(buf, &i, "* just sent you a message:\n") ||
3299 /* Whispers and kibitzes */
3300 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3301 looking_at(buf, &i, "* kibitzes: ") ||
3303 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3305 if (tkind == 1 && strchr(star_match[0], ':')) {
3306 /* Avoid "tells you:" spoofs in channels */
3309 if (star_match[0][0] == NULLCHAR ||
3310 strchr(star_match[0], ' ') ||
3311 (tkind == 3 && strchr(star_match[1], ' '))) {
3312 /* Reject bogus matches */
3315 if (appData.colorize) {
3316 if (oldi > next_out) {
3317 SendToPlayer(&buf[next_out], oldi - next_out);
3322 Colorize(ColorTell, FALSE);
3323 curColor = ColorTell;
3326 Colorize(ColorKibitz, FALSE);
3327 curColor = ColorKibitz;
3330 p = strrchr(star_match[1], '(');
3337 Colorize(ColorChannel1, FALSE);
3338 curColor = ColorChannel1;
3340 Colorize(ColorChannel, FALSE);
3341 curColor = ColorChannel;
3345 curColor = ColorNormal;
3349 if (started == STARTED_NONE && appData.autoComment &&
3350 (gameMode == IcsObserving ||
3351 gameMode == IcsPlayingWhite ||
3352 gameMode == IcsPlayingBlack)) {
3353 parse_pos = i - oldi;
3354 memcpy(parse, &buf[oldi], parse_pos);
3355 parse[parse_pos] = NULLCHAR;
3356 started = STARTED_COMMENT;
3357 savingComment = TRUE;
3359 started = STARTED_CHATTER;
3360 savingComment = FALSE;
3367 if (looking_at(buf, &i, "* s-shouts: ") ||
3368 looking_at(buf, &i, "* c-shouts: ")) {
3369 if (appData.colorize) {
3370 if (oldi > next_out) {
3371 SendToPlayer(&buf[next_out], oldi - next_out);
3374 Colorize(ColorSShout, FALSE);
3375 curColor = ColorSShout;
3378 started = STARTED_CHATTER;
3382 if (looking_at(buf, &i, "--->")) {
3387 if (looking_at(buf, &i, "* shouts: ") ||
3388 looking_at(buf, &i, "--> ")) {
3389 if (appData.colorize) {
3390 if (oldi > next_out) {
3391 SendToPlayer(&buf[next_out], oldi - next_out);
3394 Colorize(ColorShout, FALSE);
3395 curColor = ColorShout;
3398 started = STARTED_CHATTER;
3402 if (looking_at( buf, &i, "Challenge:")) {
3403 if (appData.colorize) {
3404 if (oldi > next_out) {
3405 SendToPlayer(&buf[next_out], oldi - next_out);
3408 Colorize(ColorChallenge, FALSE);
3409 curColor = ColorChallenge;
3415 if (looking_at(buf, &i, "* offers you") ||
3416 looking_at(buf, &i, "* offers to be") ||
3417 looking_at(buf, &i, "* would like to") ||
3418 looking_at(buf, &i, "* requests to") ||
3419 looking_at(buf, &i, "Your opponent offers") ||
3420 looking_at(buf, &i, "Your opponent requests")) {
3422 if (appData.colorize) {
3423 if (oldi > next_out) {
3424 SendToPlayer(&buf[next_out], oldi - next_out);
3427 Colorize(ColorRequest, FALSE);
3428 curColor = ColorRequest;
3433 if (looking_at(buf, &i, "* (*) seeking")) {
3434 if (appData.colorize) {
3435 if (oldi > next_out) {
3436 SendToPlayer(&buf[next_out], oldi - next_out);
3439 Colorize(ColorSeek, FALSE);
3440 curColor = ColorSeek;
3445 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3447 if (looking_at(buf, &i, "\\ ")) {
3448 if (prevColor != ColorNormal) {
3449 if (oldi > next_out) {
3450 SendToPlayer(&buf[next_out], oldi - next_out);
3453 Colorize(prevColor, TRUE);
3454 curColor = prevColor;
3456 if (savingComment) {
3457 parse_pos = i - oldi;
3458 memcpy(parse, &buf[oldi], parse_pos);
3459 parse[parse_pos] = NULLCHAR;
3460 started = STARTED_COMMENT;
3461 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3462 chattingPartner = savingComment - 3; // kludge to remember the box
3464 started = STARTED_CHATTER;
3469 if (looking_at(buf, &i, "Black Strength :") ||
3470 looking_at(buf, &i, "<<< style 10 board >>>") ||
3471 looking_at(buf, &i, "<10>") ||
3472 looking_at(buf, &i, "#@#")) {
3473 /* Wrong board style */
3475 SendToICS(ics_prefix);
3476 SendToICS("set style 12\n");
3477 SendToICS(ics_prefix);
3478 SendToICS("refresh\n");
3482 if (looking_at(buf, &i, "login:")) {
3483 if (!have_sent_ICS_logon) {
3485 have_sent_ICS_logon = 1;
3486 else // no init script was found
3487 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3488 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3489 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3494 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3495 (looking_at(buf, &i, "\n<12> ") ||
3496 looking_at(buf, &i, "<12> "))) {
3498 if (oldi > next_out) {
3499 SendToPlayer(&buf[next_out], oldi - next_out);
3502 started = STARTED_BOARD;
3507 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3508 looking_at(buf, &i, "<b1> ")) {
3509 if (oldi > next_out) {
3510 SendToPlayer(&buf[next_out], oldi - next_out);
3513 started = STARTED_HOLDINGS;
3518 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3520 /* Header for a move list -- first line */
3522 switch (ics_getting_history) {
3526 case BeginningOfGame:
3527 /* User typed "moves" or "oldmoves" while we
3528 were idle. Pretend we asked for these
3529 moves and soak them up so user can step
3530 through them and/or save them.
3533 gameMode = IcsObserving;
3536 ics_getting_history = H_GOT_UNREQ_HEADER;
3538 case EditGame: /*?*/
3539 case EditPosition: /*?*/
3540 /* Should above feature work in these modes too? */
3541 /* For now it doesn't */
3542 ics_getting_history = H_GOT_UNWANTED_HEADER;
3545 ics_getting_history = H_GOT_UNWANTED_HEADER;
3550 /* Is this the right one? */
3551 if (gameInfo.white && gameInfo.black &&
3552 strcmp(gameInfo.white, star_match[0]) == 0 &&
3553 strcmp(gameInfo.black, star_match[2]) == 0) {
3555 ics_getting_history = H_GOT_REQ_HEADER;
3558 case H_GOT_REQ_HEADER:
3559 case H_GOT_UNREQ_HEADER:
3560 case H_GOT_UNWANTED_HEADER:
3561 case H_GETTING_MOVES:
3562 /* Should not happen */
3563 DisplayError(_("Error gathering move list: two headers"), 0);
3564 ics_getting_history = H_FALSE;
3568 /* Save player ratings into gameInfo if needed */
3569 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3570 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3571 (gameInfo.whiteRating == -1 ||
3572 gameInfo.blackRating == -1)) {
3574 gameInfo.whiteRating = string_to_rating(star_match[1]);
3575 gameInfo.blackRating = string_to_rating(star_match[3]);
3576 if (appData.debugMode)
3577 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3578 gameInfo.whiteRating, gameInfo.blackRating);
3583 if (looking_at(buf, &i,
3584 "* * match, initial time: * minute*, increment: * second")) {
3585 /* Header for a move list -- second line */
3586 /* Initial board will follow if this is a wild game */
3587 if (gameInfo.event != NULL) free(gameInfo.event);
3588 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3589 gameInfo.event = StrSave(str);
3590 /* [HGM] we switched variant. Translate boards if needed. */
3591 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3595 if (looking_at(buf, &i, "Move ")) {
3596 /* Beginning of a move list */
3597 switch (ics_getting_history) {
3599 /* Normally should not happen */
3600 /* Maybe user hit reset while we were parsing */
3603 /* Happens if we are ignoring a move list that is not
3604 * the one we just requested. Common if the user
3605 * tries to observe two games without turning off
3608 case H_GETTING_MOVES:
3609 /* Should not happen */
3610 DisplayError(_("Error gathering move list: nested"), 0);
3611 ics_getting_history = H_FALSE;
3613 case H_GOT_REQ_HEADER:
3614 ics_getting_history = H_GETTING_MOVES;
3615 started = STARTED_MOVES;
3617 if (oldi > next_out) {
3618 SendToPlayer(&buf[next_out], oldi - next_out);
3621 case H_GOT_UNREQ_HEADER:
3622 ics_getting_history = H_GETTING_MOVES;
3623 started = STARTED_MOVES_NOHIDE;
3626 case H_GOT_UNWANTED_HEADER:
3627 ics_getting_history = H_FALSE;
3633 if (looking_at(buf, &i, "% ") ||
3634 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3635 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3636 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3637 soughtPending = FALSE;
3641 if(suppressKibitz) next_out = i;
3642 savingComment = FALSE;
3646 case STARTED_MOVES_NOHIDE:
3647 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3648 parse[parse_pos + i - oldi] = NULLCHAR;
3649 ParseGameHistory(parse);
3651 if (appData.zippyPlay && first.initDone) {
3652 FeedMovesToProgram(&first, forwardMostMove);
3653 if (gameMode == IcsPlayingWhite) {
3654 if (WhiteOnMove(forwardMostMove)) {
3655 if (first.sendTime) {
3656 if (first.useColors) {
3657 SendToProgram("black\n", &first);
3659 SendTimeRemaining(&first, TRUE);
3661 if (first.useColors) {
3662 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3664 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3665 first.maybeThinking = TRUE;
3667 if (first.usePlayother) {
3668 if (first.sendTime) {
3669 SendTimeRemaining(&first, TRUE);
3671 SendToProgram("playother\n", &first);
3677 } else if (gameMode == IcsPlayingBlack) {
3678 if (!WhiteOnMove(forwardMostMove)) {
3679 if (first.sendTime) {
3680 if (first.useColors) {
3681 SendToProgram("white\n", &first);
3683 SendTimeRemaining(&first, FALSE);
3685 if (first.useColors) {
3686 SendToProgram("black\n", &first);
3688 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3689 first.maybeThinking = TRUE;
3691 if (first.usePlayother) {
3692 if (first.sendTime) {
3693 SendTimeRemaining(&first, FALSE);
3695 SendToProgram("playother\n", &first);
3704 if (gameMode == IcsObserving && ics_gamenum == -1) {
3705 /* Moves came from oldmoves or moves command
3706 while we weren't doing anything else.
3708 currentMove = forwardMostMove;
3709 ClearHighlights();/*!!could figure this out*/
3710 flipView = appData.flipView;
3711 DrawPosition(TRUE, boards[currentMove]);
3712 DisplayBothClocks();
3713 snprintf(str, MSG_SIZ, "%s %s %s",
3714 gameInfo.white, _("vs."), gameInfo.black);
3718 /* Moves were history of an active game */
3719 if (gameInfo.resultDetails != NULL) {
3720 free(gameInfo.resultDetails);
3721 gameInfo.resultDetails = NULL;
3724 HistorySet(parseList, backwardMostMove,
3725 forwardMostMove, currentMove-1);
3726 DisplayMove(currentMove - 1);
3727 if (started == STARTED_MOVES) next_out = i;
3728 started = STARTED_NONE;
3729 ics_getting_history = H_FALSE;
3732 case STARTED_OBSERVE:
3733 started = STARTED_NONE;
3734 SendToICS(ics_prefix);
3735 SendToICS("refresh\n");
3741 if(bookHit) { // [HGM] book: simulate book reply
3742 static char bookMove[MSG_SIZ]; // a bit generous?
3744 programStats.nodes = programStats.depth = programStats.time =
3745 programStats.score = programStats.got_only_move = 0;
3746 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3748 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3749 strcat(bookMove, bookHit);
3750 HandleMachineMove(bookMove, &first);
3755 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3756 started == STARTED_HOLDINGS ||
3757 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3758 /* Accumulate characters in move list or board */
3759 parse[parse_pos++] = buf[i];
3762 /* Start of game messages. Mostly we detect start of game
3763 when the first board image arrives. On some versions
3764 of the ICS, though, we need to do a "refresh" after starting
3765 to observe in order to get the current board right away. */
3766 if (looking_at(buf, &i, "Adding game * to observation list")) {
3767 started = STARTED_OBSERVE;
3771 /* Handle auto-observe */
3772 if (appData.autoObserve &&
3773 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3774 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3776 /* Choose the player that was highlighted, if any. */
3777 if (star_match[0][0] == '\033' ||
3778 star_match[1][0] != '\033') {
3779 player = star_match[0];
3781 player = star_match[2];
3783 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3784 ics_prefix, StripHighlightAndTitle(player));
3787 /* Save ratings from notify string */
3788 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3789 player1Rating = string_to_rating(star_match[1]);
3790 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3791 player2Rating = string_to_rating(star_match[3]);
3793 if (appData.debugMode)
3795 "Ratings from 'Game notification:' %s %d, %s %d\n",
3796 player1Name, player1Rating,
3797 player2Name, player2Rating);
3802 /* Deal with automatic examine mode after a game,
3803 and with IcsObserving -> IcsExamining transition */
3804 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3805 looking_at(buf, &i, "has made you an examiner of game *")) {
3807 int gamenum = atoi(star_match[0]);
3808 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3809 gamenum == ics_gamenum) {
3810 /* We were already playing or observing this game;
3811 no need to refetch history */
3812 gameMode = IcsExamining;
3814 pauseExamForwardMostMove = forwardMostMove;
3815 } else if (currentMove < forwardMostMove) {
3816 ForwardInner(forwardMostMove);
3819 /* I don't think this case really can happen */
3820 SendToICS(ics_prefix);
3821 SendToICS("refresh\n");
3826 /* Error messages */
3827 // if (ics_user_moved) {
3828 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3829 if (looking_at(buf, &i, "Illegal move") ||
3830 looking_at(buf, &i, "Not a legal move") ||
3831 looking_at(buf, &i, "Your king is in check") ||
3832 looking_at(buf, &i, "It isn't your turn") ||
3833 looking_at(buf, &i, "It is not your move")) {
3835 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3836 currentMove = forwardMostMove-1;
3837 DisplayMove(currentMove - 1); /* before DMError */
3838 DrawPosition(FALSE, boards[currentMove]);
3839 SwitchClocks(forwardMostMove-1); // [HGM] race
3840 DisplayBothClocks();
3842 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3848 if (looking_at(buf, &i, "still have time") ||
3849 looking_at(buf, &i, "not out of time") ||
3850 looking_at(buf, &i, "either player is out of time") ||
3851 looking_at(buf, &i, "has timeseal; checking")) {
3852 /* We must have called his flag a little too soon */
3853 whiteFlag = blackFlag = FALSE;
3857 if (looking_at(buf, &i, "added * seconds to") ||
3858 looking_at(buf, &i, "seconds were added to")) {
3859 /* Update the clocks */
3860 SendToICS(ics_prefix);
3861 SendToICS("refresh\n");
3865 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3866 ics_clock_paused = TRUE;
3871 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3872 ics_clock_paused = FALSE;
3877 /* Grab player ratings from the Creating: message.
3878 Note we have to check for the special case when
3879 the ICS inserts things like [white] or [black]. */
3880 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3881 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3883 0 player 1 name (not necessarily white)
3885 2 empty, white, or black (IGNORED)
3886 3 player 2 name (not necessarily black)
3889 The names/ratings are sorted out when the game
3890 actually starts (below).
3892 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3893 player1Rating = string_to_rating(star_match[1]);
3894 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3895 player2Rating = string_to_rating(star_match[4]);
3897 if (appData.debugMode)
3899 "Ratings from 'Creating:' %s %d, %s %d\n",
3900 player1Name, player1Rating,
3901 player2Name, player2Rating);
3906 /* Improved generic start/end-of-game messages */
3907 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3908 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3909 /* If tkind == 0: */
3910 /* star_match[0] is the game number */
3911 /* [1] is the white player's name */
3912 /* [2] is the black player's name */
3913 /* For end-of-game: */
3914 /* [3] is the reason for the game end */
3915 /* [4] is a PGN end game-token, preceded by " " */
3916 /* For start-of-game: */
3917 /* [3] begins with "Creating" or "Continuing" */
3918 /* [4] is " *" or empty (don't care). */
3919 int gamenum = atoi(star_match[0]);
3920 char *whitename, *blackname, *why, *endtoken;
3921 ChessMove endtype = EndOfFile;
3924 whitename = star_match[1];
3925 blackname = star_match[2];
3926 why = star_match[3];
3927 endtoken = star_match[4];
3929 whitename = star_match[1];
3930 blackname = star_match[3];
3931 why = star_match[5];
3932 endtoken = star_match[6];
3935 /* Game start messages */
3936 if (strncmp(why, "Creating ", 9) == 0 ||
3937 strncmp(why, "Continuing ", 11) == 0) {
3938 gs_gamenum = gamenum;
3939 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3940 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3941 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3943 if (appData.zippyPlay) {
3944 ZippyGameStart(whitename, blackname);
3947 partnerBoardValid = FALSE; // [HGM] bughouse
3951 /* Game end messages */
3952 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3953 ics_gamenum != gamenum) {
3956 while (endtoken[0] == ' ') endtoken++;
3957 switch (endtoken[0]) {
3960 endtype = GameUnfinished;
3963 endtype = BlackWins;
3966 if (endtoken[1] == '/')
3967 endtype = GameIsDrawn;
3969 endtype = WhiteWins;
3972 GameEnds(endtype, why, GE_ICS);
3974 if (appData.zippyPlay && first.initDone) {
3975 ZippyGameEnd(endtype, why);
3976 if (first.pr == NoProc) {
3977 /* Start the next process early so that we'll
3978 be ready for the next challenge */
3979 StartChessProgram(&first);
3981 /* Send "new" early, in case this command takes
3982 a long time to finish, so that we'll be ready
3983 for the next challenge. */
3984 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3988 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3992 if (looking_at(buf, &i, "Removing game * from observation") ||
3993 looking_at(buf, &i, "no longer observing game *") ||
3994 looking_at(buf, &i, "Game * (*) has no examiners")) {
3995 if (gameMode == IcsObserving &&
3996 atoi(star_match[0]) == ics_gamenum)
3998 /* icsEngineAnalyze */
3999 if (appData.icsEngineAnalyze) {
4006 ics_user_moved = FALSE;
4011 if (looking_at(buf, &i, "no longer examining game *")) {
4012 if (gameMode == IcsExamining &&
4013 atoi(star_match[0]) == ics_gamenum)
4017 ics_user_moved = FALSE;
4022 /* Advance leftover_start past any newlines we find,
4023 so only partial lines can get reparsed */
4024 if (looking_at(buf, &i, "\n")) {
4025 prevColor = curColor;
4026 if (curColor != ColorNormal) {
4027 if (oldi > next_out) {
4028 SendToPlayer(&buf[next_out], oldi - next_out);
4031 Colorize(ColorNormal, FALSE);
4032 curColor = ColorNormal;
4034 if (started == STARTED_BOARD) {
4035 started = STARTED_NONE;
4036 parse[parse_pos] = NULLCHAR;
4037 ParseBoard12(parse);
4040 /* Send premove here */
4041 if (appData.premove) {
4043 if (currentMove == 0 &&
4044 gameMode == IcsPlayingWhite &&
4045 appData.premoveWhite) {
4046 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4047 if (appData.debugMode)
4048 fprintf(debugFP, "Sending premove:\n");
4050 } else if (currentMove == 1 &&
4051 gameMode == IcsPlayingBlack &&
4052 appData.premoveBlack) {
4053 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4054 if (appData.debugMode)
4055 fprintf(debugFP, "Sending premove:\n");
4057 } else if (gotPremove) {
4059 ClearPremoveHighlights();
4060 if (appData.debugMode)
4061 fprintf(debugFP, "Sending premove:\n");
4062 UserMoveEvent(premoveFromX, premoveFromY,
4063 premoveToX, premoveToY,
4068 /* Usually suppress following prompt */
4069 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4070 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4071 if (looking_at(buf, &i, "*% ")) {
4072 savingComment = FALSE;
4077 } else if (started == STARTED_HOLDINGS) {
4079 char new_piece[MSG_SIZ];
4080 started = STARTED_NONE;
4081 parse[parse_pos] = NULLCHAR;
4082 if (appData.debugMode)
4083 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4084 parse, currentMove);
4085 if (sscanf(parse, " game %d", &gamenum) == 1) {
4086 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4087 if (gameInfo.variant == VariantNormal) {
4088 /* [HGM] We seem to switch variant during a game!
4089 * Presumably no holdings were displayed, so we have
4090 * to move the position two files to the right to
4091 * create room for them!
4093 VariantClass newVariant;
4094 switch(gameInfo.boardWidth) { // base guess on board width
4095 case 9: newVariant = VariantShogi; break;
4096 case 10: newVariant = VariantGreat; break;
4097 default: newVariant = VariantCrazyhouse; break;
4099 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4100 /* Get a move list just to see the header, which
4101 will tell us whether this is really bug or zh */
4102 if (ics_getting_history == H_FALSE) {
4103 ics_getting_history = H_REQUESTED;
4104 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4108 new_piece[0] = NULLCHAR;
4109 sscanf(parse, "game %d white [%s black [%s <- %s",
4110 &gamenum, white_holding, black_holding,
4112 white_holding[strlen(white_holding)-1] = NULLCHAR;
4113 black_holding[strlen(black_holding)-1] = NULLCHAR;
4114 /* [HGM] copy holdings to board holdings area */
4115 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4116 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4117 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4119 if (appData.zippyPlay && first.initDone) {
4120 ZippyHoldings(white_holding, black_holding,
4124 if (tinyLayout || smallLayout) {
4125 char wh[16], bh[16];
4126 PackHolding(wh, white_holding);
4127 PackHolding(bh, black_holding);
4128 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4129 gameInfo.white, gameInfo.black);
4131 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4132 gameInfo.white, white_holding, _("vs."),
4133 gameInfo.black, black_holding);
4135 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4136 DrawPosition(FALSE, boards[currentMove]);
4138 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4139 sscanf(parse, "game %d white [%s black [%s <- %s",
4140 &gamenum, white_holding, black_holding,
4142 white_holding[strlen(white_holding)-1] = NULLCHAR;
4143 black_holding[strlen(black_holding)-1] = NULLCHAR;
4144 /* [HGM] copy holdings to partner-board holdings area */
4145 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4146 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4147 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4148 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4149 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4152 /* Suppress following prompt */
4153 if (looking_at(buf, &i, "*% ")) {
4154 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4155 savingComment = FALSE;
4163 i++; /* skip unparsed character and loop back */
4166 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4167 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4168 // SendToPlayer(&buf[next_out], i - next_out);
4169 started != STARTED_HOLDINGS && leftover_start > next_out) {
4170 SendToPlayer(&buf[next_out], leftover_start - next_out);
4174 leftover_len = buf_len - leftover_start;
4175 /* if buffer ends with something we couldn't parse,
4176 reparse it after appending the next read */
4178 } else if (count == 0) {
4179 RemoveInputSource(isr);
4180 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4182 DisplayFatalError(_("Error reading from ICS"), error, 1);
4187 /* Board style 12 looks like this:
4189 <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
4191 * The "<12> " is stripped before it gets to this routine. The two
4192 * trailing 0's (flip state and clock ticking) are later addition, and
4193 * some chess servers may not have them, or may have only the first.
4194 * Additional trailing fields may be added in the future.
4197 #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"
4199 #define RELATION_OBSERVING_PLAYED 0
4200 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4201 #define RELATION_PLAYING_MYMOVE 1
4202 #define RELATION_PLAYING_NOTMYMOVE -1
4203 #define RELATION_EXAMINING 2
4204 #define RELATION_ISOLATED_BOARD -3
4205 #define RELATION_STARTING_POSITION -4 /* FICS only */
4208 ParseBoard12 (char *string)
4212 char *bookHit = NULL; // [HGM] book
4214 GameMode newGameMode;
4215 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4216 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4217 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4218 char to_play, board_chars[200];
4219 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4220 char black[32], white[32];
4222 int prevMove = currentMove;
4225 int fromX, fromY, toX, toY;
4227 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4228 Boolean weird = FALSE, reqFlag = FALSE;
4230 fromX = fromY = toX = toY = -1;
4234 if (appData.debugMode)
4235 fprintf(debugFP, "Parsing board: %s\n", string);
4237 move_str[0] = NULLCHAR;
4238 elapsed_time[0] = NULLCHAR;
4239 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4241 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4242 if(string[i] == ' ') { ranks++; files = 0; }
4244 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4247 for(j = 0; j <i; j++) board_chars[j] = string[j];
4248 board_chars[i] = '\0';
4251 n = sscanf(string, PATTERN, &to_play, &double_push,
4252 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4253 &gamenum, white, black, &relation, &basetime, &increment,
4254 &white_stren, &black_stren, &white_time, &black_time,
4255 &moveNum, str, elapsed_time, move_str, &ics_flip,
4259 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4260 DisplayError(str, 0);
4264 /* Convert the move number to internal form */
4265 moveNum = (moveNum - 1) * 2;
4266 if (to_play == 'B') moveNum++;
4267 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4268 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4274 case RELATION_OBSERVING_PLAYED:
4275 case RELATION_OBSERVING_STATIC:
4276 if (gamenum == -1) {
4277 /* Old ICC buglet */
4278 relation = RELATION_OBSERVING_STATIC;
4280 newGameMode = IcsObserving;
4282 case RELATION_PLAYING_MYMOVE:
4283 case RELATION_PLAYING_NOTMYMOVE:
4285 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4286 IcsPlayingWhite : IcsPlayingBlack;
4287 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4289 case RELATION_EXAMINING:
4290 newGameMode = IcsExamining;
4292 case RELATION_ISOLATED_BOARD:
4294 /* Just display this board. If user was doing something else,
4295 we will forget about it until the next board comes. */
4296 newGameMode = IcsIdle;
4298 case RELATION_STARTING_POSITION:
4299 newGameMode = gameMode;
4303 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4304 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4305 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4306 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4307 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4308 static int lastBgGame = -1;
4310 for (k = 0; k < ranks; k++) {
4311 for (j = 0; j < files; j++)
4312 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4313 if(gameInfo.holdingsWidth > 1) {
4314 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4315 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4318 CopyBoard(partnerBoard, board);
4319 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4320 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4321 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4322 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4323 if(toSqr = strchr(str, '-')) {
4324 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4325 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4326 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4327 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4328 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4329 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4331 DisplayWhiteClock(white_time*fac, to_play == 'W');
4332 DisplayBlackClock(black_time*fac, to_play != 'W');
4333 activePartner = to_play;
4334 if(gamenum != lastBgGame) {
4336 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4339 lastBgGame = gamenum;
4340 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4341 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4342 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4343 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4344 if(!twoBoards) DisplayMessage(partnerStatus, "");
4345 partnerBoardValid = TRUE;
4349 if(appData.dualBoard && appData.bgObserve) {
4350 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4351 SendToICS(ics_prefix), SendToICS("pobserve\n");
4352 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4354 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4359 /* Modify behavior for initial board display on move listing
4362 switch (ics_getting_history) {
4366 case H_GOT_REQ_HEADER:
4367 case H_GOT_UNREQ_HEADER:
4368 /* This is the initial position of the current game */
4369 gamenum = ics_gamenum;
4370 moveNum = 0; /* old ICS bug workaround */
4371 if (to_play == 'B') {
4372 startedFromSetupPosition = TRUE;
4373 blackPlaysFirst = TRUE;
4375 if (forwardMostMove == 0) forwardMostMove = 1;
4376 if (backwardMostMove == 0) backwardMostMove = 1;
4377 if (currentMove == 0) currentMove = 1;
4379 newGameMode = gameMode;
4380 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4382 case H_GOT_UNWANTED_HEADER:
4383 /* This is an initial board that we don't want */
4385 case H_GETTING_MOVES:
4386 /* Should not happen */
4387 DisplayError(_("Error gathering move list: extra board"), 0);
4388 ics_getting_history = H_FALSE;
4392 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4393 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4394 weird && (int)gameInfo.variant < (int)VariantShogi) {
4395 /* [HGM] We seem to have switched variant unexpectedly
4396 * Try to guess new variant from board size
4398 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4399 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4400 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4401 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4402 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4403 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4404 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4405 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4406 /* Get a move list just to see the header, which
4407 will tell us whether this is really bug or zh */
4408 if (ics_getting_history == H_FALSE) {
4409 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4410 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4415 /* Take action if this is the first board of a new game, or of a
4416 different game than is currently being displayed. */
4417 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4418 relation == RELATION_ISOLATED_BOARD) {
4420 /* Forget the old game and get the history (if any) of the new one */
4421 if (gameMode != BeginningOfGame) {
4425 if (appData.autoRaiseBoard) BoardToTop();
4427 if (gamenum == -1) {
4428 newGameMode = IcsIdle;
4429 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4430 appData.getMoveList && !reqFlag) {
4431 /* Need to get game history */
4432 ics_getting_history = H_REQUESTED;
4433 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4437 /* Initially flip the board to have black on the bottom if playing
4438 black or if the ICS flip flag is set, but let the user change
4439 it with the Flip View button. */
4440 flipView = appData.autoFlipView ?
4441 (newGameMode == IcsPlayingBlack) || ics_flip :
4444 /* Done with values from previous mode; copy in new ones */
4445 gameMode = newGameMode;
4447 ics_gamenum = gamenum;
4448 if (gamenum == gs_gamenum) {
4449 int klen = strlen(gs_kind);
4450 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4451 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4452 gameInfo.event = StrSave(str);
4454 gameInfo.event = StrSave("ICS game");
4456 gameInfo.site = StrSave(appData.icsHost);
4457 gameInfo.date = PGNDate();
4458 gameInfo.round = StrSave("-");
4459 gameInfo.white = StrSave(white);
4460 gameInfo.black = StrSave(black);
4461 timeControl = basetime * 60 * 1000;
4463 timeIncrement = increment * 1000;
4464 movesPerSession = 0;
4465 gameInfo.timeControl = TimeControlTagValue();
4466 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4467 if (appData.debugMode) {
4468 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4469 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4470 setbuf(debugFP, NULL);
4473 gameInfo.outOfBook = NULL;
4475 /* Do we have the ratings? */
4476 if (strcmp(player1Name, white) == 0 &&
4477 strcmp(player2Name, black) == 0) {
4478 if (appData.debugMode)
4479 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4480 player1Rating, player2Rating);
4481 gameInfo.whiteRating = player1Rating;
4482 gameInfo.blackRating = player2Rating;
4483 } else if (strcmp(player2Name, white) == 0 &&
4484 strcmp(player1Name, black) == 0) {
4485 if (appData.debugMode)
4486 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4487 player2Rating, player1Rating);
4488 gameInfo.whiteRating = player2Rating;
4489 gameInfo.blackRating = player1Rating;
4491 player1Name[0] = player2Name[0] = NULLCHAR;
4493 /* Silence shouts if requested */
4494 if (appData.quietPlay &&
4495 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4496 SendToICS(ics_prefix);
4497 SendToICS("set shout 0\n");
4501 /* Deal with midgame name changes */
4503 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4504 if (gameInfo.white) free(gameInfo.white);
4505 gameInfo.white = StrSave(white);
4507 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4508 if (gameInfo.black) free(gameInfo.black);
4509 gameInfo.black = StrSave(black);
4513 /* Throw away game result if anything actually changes in examine mode */
4514 if (gameMode == IcsExamining && !newGame) {
4515 gameInfo.result = GameUnfinished;
4516 if (gameInfo.resultDetails != NULL) {
4517 free(gameInfo.resultDetails);
4518 gameInfo.resultDetails = NULL;
4522 /* In pausing && IcsExamining mode, we ignore boards coming
4523 in if they are in a different variation than we are. */
4524 if (pauseExamInvalid) return;
4525 if (pausing && gameMode == IcsExamining) {
4526 if (moveNum <= pauseExamForwardMostMove) {
4527 pauseExamInvalid = TRUE;
4528 forwardMostMove = pauseExamForwardMostMove;
4533 if (appData.debugMode) {
4534 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4536 /* Parse the board */
4537 for (k = 0; k < ranks; k++) {
4538 for (j = 0; j < files; j++)
4539 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4540 if(gameInfo.holdingsWidth > 1) {
4541 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4542 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4545 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4546 board[5][BOARD_RGHT+1] = WhiteAngel;
4547 board[6][BOARD_RGHT+1] = WhiteMarshall;
4548 board[1][0] = BlackMarshall;
4549 board[2][0] = BlackAngel;
4550 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4552 CopyBoard(boards[moveNum], board);
4553 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4555 startedFromSetupPosition =
4556 !CompareBoards(board, initialPosition);
4557 if(startedFromSetupPosition)
4558 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4561 /* [HGM] Set castling rights. Take the outermost Rooks,
4562 to make it also work for FRC opening positions. Note that board12
4563 is really defective for later FRC positions, as it has no way to
4564 indicate which Rook can castle if they are on the same side of King.
4565 For the initial position we grant rights to the outermost Rooks,
4566 and remember thos rights, and we then copy them on positions
4567 later in an FRC game. This means WB might not recognize castlings with
4568 Rooks that have moved back to their original position as illegal,
4569 but in ICS mode that is not its job anyway.
4571 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4572 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4574 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4575 if(board[0][i] == WhiteRook) j = i;
4576 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4577 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4578 if(board[0][i] == WhiteRook) j = i;
4579 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4580 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4581 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4582 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4583 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4584 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4585 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4587 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4588 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4589 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4590 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4591 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4592 if(board[BOARD_HEIGHT-1][k] == bKing)
4593 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4594 if(gameInfo.variant == VariantTwoKings) {
4595 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4596 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4597 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4600 r = boards[moveNum][CASTLING][0] = initialRights[0];
4601 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4602 r = boards[moveNum][CASTLING][1] = initialRights[1];
4603 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4604 r = boards[moveNum][CASTLING][3] = initialRights[3];
4605 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4606 r = boards[moveNum][CASTLING][4] = initialRights[4];
4607 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4608 /* wildcastle kludge: always assume King has rights */
4609 r = boards[moveNum][CASTLING][2] = initialRights[2];
4610 r = boards[moveNum][CASTLING][5] = initialRights[5];
4612 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4613 boards[moveNum][EP_STATUS] = EP_NONE;
4614 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4615 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4616 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4619 if (ics_getting_history == H_GOT_REQ_HEADER ||
4620 ics_getting_history == H_GOT_UNREQ_HEADER) {
4621 /* This was an initial position from a move list, not
4622 the current position */
4626 /* Update currentMove and known move number limits */
4627 newMove = newGame || moveNum > forwardMostMove;
4630 forwardMostMove = backwardMostMove = currentMove = moveNum;
4631 if (gameMode == IcsExamining && moveNum == 0) {
4632 /* Workaround for ICS limitation: we are not told the wild
4633 type when starting to examine a game. But if we ask for
4634 the move list, the move list header will tell us */
4635 ics_getting_history = H_REQUESTED;
4636 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4639 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4640 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4642 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4643 /* [HGM] applied this also to an engine that is silently watching */
4644 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4645 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4646 gameInfo.variant == currentlyInitializedVariant) {
4647 takeback = forwardMostMove - moveNum;
4648 for (i = 0; i < takeback; i++) {
4649 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4650 SendToProgram("undo\n", &first);
4655 forwardMostMove = moveNum;
4656 if (!pausing || currentMove > forwardMostMove)
4657 currentMove = forwardMostMove;
4659 /* New part of history that is not contiguous with old part */
4660 if (pausing && gameMode == IcsExamining) {
4661 pauseExamInvalid = TRUE;
4662 forwardMostMove = pauseExamForwardMostMove;
4665 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4667 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4668 // [HGM] when we will receive the move list we now request, it will be
4669 // fed to the engine from the first move on. So if the engine is not
4670 // in the initial position now, bring it there.
4671 InitChessProgram(&first, 0);
4674 ics_getting_history = H_REQUESTED;
4675 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4678 forwardMostMove = backwardMostMove = currentMove = moveNum;
4681 /* Update the clocks */
4682 if (strchr(elapsed_time, '.')) {
4684 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4685 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4687 /* Time is in seconds */
4688 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4689 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4694 if (appData.zippyPlay && newGame &&
4695 gameMode != IcsObserving && gameMode != IcsIdle &&
4696 gameMode != IcsExamining)
4697 ZippyFirstBoard(moveNum, basetime, increment);
4700 /* Put the move on the move list, first converting
4701 to canonical algebraic form. */
4703 if (appData.debugMode) {
4704 int f = forwardMostMove;
4705 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4706 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4707 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4708 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4709 fprintf(debugFP, "moveNum = %d\n", moveNum);
4710 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4711 setbuf(debugFP, NULL);
4713 if (moveNum <= backwardMostMove) {
4714 /* We don't know what the board looked like before
4716 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4717 strcat(parseList[moveNum - 1], " ");
4718 strcat(parseList[moveNum - 1], elapsed_time);
4719 moveList[moveNum - 1][0] = NULLCHAR;
4720 } else if (strcmp(move_str, "none") == 0) {
4721 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4722 /* Again, we don't know what the board looked like;
4723 this is really the start of the game. */
4724 parseList[moveNum - 1][0] = NULLCHAR;
4725 moveList[moveNum - 1][0] = NULLCHAR;
4726 backwardMostMove = moveNum;
4727 startedFromSetupPosition = TRUE;
4728 fromX = fromY = toX = toY = -1;
4730 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4731 // So we parse the long-algebraic move string in stead of the SAN move
4732 int valid; char buf[MSG_SIZ], *prom;
4734 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4735 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4736 // str looks something like "Q/a1-a2"; kill the slash
4738 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4739 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4740 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4741 strcat(buf, prom); // long move lacks promo specification!
4742 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4743 if(appData.debugMode)
4744 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4745 safeStrCpy(move_str, buf, MSG_SIZ);
4747 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4748 &fromX, &fromY, &toX, &toY, &promoChar)
4749 || ParseOneMove(buf, moveNum - 1, &moveType,
4750 &fromX, &fromY, &toX, &toY, &promoChar);
4751 // end of long SAN patch
4753 (void) CoordsToAlgebraic(boards[moveNum - 1],
4754 PosFlags(moveNum - 1),
4755 fromY, fromX, toY, toX, promoChar,
4756 parseList[moveNum-1]);
4757 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4763 if(gameInfo.variant != VariantShogi)
4764 strcat(parseList[moveNum - 1], "+");
4767 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4768 strcat(parseList[moveNum - 1], "#");
4771 strcat(parseList[moveNum - 1], " ");
4772 strcat(parseList[moveNum - 1], elapsed_time);
4773 /* currentMoveString is set as a side-effect of ParseOneMove */
4774 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4775 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4776 strcat(moveList[moveNum - 1], "\n");
4778 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4779 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4780 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4781 ChessSquare old, new = boards[moveNum][k][j];
4782 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4783 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4784 if(old == new) continue;
4785 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4786 else if(new == WhiteWazir || new == BlackWazir) {
4787 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4788 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4789 else boards[moveNum][k][j] = old; // preserve type of Gold
4790 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4791 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4794 /* Move from ICS was illegal!? Punt. */
4795 if (appData.debugMode) {
4796 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4797 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4799 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4800 strcat(parseList[moveNum - 1], " ");
4801 strcat(parseList[moveNum - 1], elapsed_time);
4802 moveList[moveNum - 1][0] = NULLCHAR;
4803 fromX = fromY = toX = toY = -1;
4806 if (appData.debugMode) {
4807 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4808 setbuf(debugFP, NULL);
4812 /* Send move to chess program (BEFORE animating it). */
4813 if (appData.zippyPlay && !newGame && newMove &&
4814 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4816 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4817 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4818 if (moveList[moveNum - 1][0] == NULLCHAR) {
4819 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4821 DisplayError(str, 0);
4823 if (first.sendTime) {
4824 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4826 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4827 if (firstMove && !bookHit) {
4829 if (first.useColors) {
4830 SendToProgram(gameMode == IcsPlayingWhite ?
4832 "black\ngo\n", &first);
4834 SendToProgram("go\n", &first);
4836 first.maybeThinking = TRUE;
4839 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4840 if (moveList[moveNum - 1][0] == NULLCHAR) {
4841 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4842 DisplayError(str, 0);
4844 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4845 SendMoveToProgram(moveNum - 1, &first);
4852 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4853 /* If move comes from a remote source, animate it. If it
4854 isn't remote, it will have already been animated. */
4855 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4856 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4858 if (!pausing && appData.highlightLastMove) {
4859 SetHighlights(fromX, fromY, toX, toY);
4863 /* Start the clocks */
4864 whiteFlag = blackFlag = FALSE;
4865 appData.clockMode = !(basetime == 0 && increment == 0);
4867 ics_clock_paused = TRUE;
4869 } else if (ticking == 1) {
4870 ics_clock_paused = FALSE;
4872 if (gameMode == IcsIdle ||
4873 relation == RELATION_OBSERVING_STATIC ||
4874 relation == RELATION_EXAMINING ||
4876 DisplayBothClocks();
4880 /* Display opponents and material strengths */
4881 if (gameInfo.variant != VariantBughouse &&
4882 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4883 if (tinyLayout || smallLayout) {
4884 if(gameInfo.variant == VariantNormal)
4885 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4886 gameInfo.white, white_stren, gameInfo.black, black_stren,
4887 basetime, increment);
4889 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4890 gameInfo.white, white_stren, gameInfo.black, black_stren,
4891 basetime, increment, (int) gameInfo.variant);
4893 if(gameInfo.variant == VariantNormal)
4894 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4895 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4896 basetime, increment);
4898 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4899 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4900 basetime, increment, VariantName(gameInfo.variant));
4903 if (appData.debugMode) {
4904 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4909 /* Display the board */
4910 if (!pausing && !appData.noGUI) {
4912 if (appData.premove)
4914 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4915 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4916 ClearPremoveHighlights();
4918 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4919 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4920 DrawPosition(j, boards[currentMove]);
4922 DisplayMove(moveNum - 1);
4923 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4924 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4925 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4926 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4930 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4932 if(bookHit) { // [HGM] book: simulate book reply
4933 static char bookMove[MSG_SIZ]; // a bit generous?
4935 programStats.nodes = programStats.depth = programStats.time =
4936 programStats.score = programStats.got_only_move = 0;
4937 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4939 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4940 strcat(bookMove, bookHit);
4941 HandleMachineMove(bookMove, &first);
4950 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4951 ics_getting_history = H_REQUESTED;
4952 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4958 SendToBoth (char *msg)
4959 { // to make it easy to keep two engines in step in dual analysis
4960 SendToProgram(msg, &first);
4961 if(second.analyzing) SendToProgram(msg, &second);
4965 AnalysisPeriodicEvent (int force)
4967 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4968 && !force) || !appData.periodicUpdates)
4971 /* Send . command to Crafty to collect stats */
4974 /* Don't send another until we get a response (this makes
4975 us stop sending to old Crafty's which don't understand
4976 the "." command (sending illegal cmds resets node count & time,
4977 which looks bad)) */
4978 programStats.ok_to_send = 0;
4982 ics_update_width (int new_width)
4984 ics_printf("set width %d\n", new_width);
4988 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4992 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4993 // null move in variant where engine does not understand it (for analysis purposes)
4994 SendBoard(cps, moveNum + 1); // send position after move in stead.
4997 if (cps->useUsermove) {
4998 SendToProgram("usermove ", cps);
5002 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5003 int len = space - parseList[moveNum];
5004 memcpy(buf, parseList[moveNum], len);
5006 buf[len] = NULLCHAR;
5008 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5010 SendToProgram(buf, cps);
5012 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5013 AlphaRank(moveList[moveNum], 4);
5014 SendToProgram(moveList[moveNum], cps);
5015 AlphaRank(moveList[moveNum], 4); // and back
5017 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5018 * the engine. It would be nice to have a better way to identify castle
5020 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5021 && cps->useOOCastle) {
5022 int fromX = moveList[moveNum][0] - AAA;
5023 int fromY = moveList[moveNum][1] - ONE;
5024 int toX = moveList[moveNum][2] - AAA;
5025 int toY = moveList[moveNum][3] - ONE;
5026 if((boards[moveNum][fromY][fromX] == WhiteKing
5027 && boards[moveNum][toY][toX] == WhiteRook)
5028 || (boards[moveNum][fromY][fromX] == BlackKing
5029 && boards[moveNum][toY][toX] == BlackRook)) {
5030 if(toX > fromX) SendToProgram("O-O\n", cps);
5031 else SendToProgram("O-O-O\n", cps);
5033 else SendToProgram(moveList[moveNum], cps);
5035 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5036 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5037 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5038 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5039 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5041 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5042 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5043 SendToProgram(buf, cps);
5045 else SendToProgram(moveList[moveNum], cps);
5046 /* End of additions by Tord */
5049 /* [HGM] setting up the opening has brought engine in force mode! */
5050 /* Send 'go' if we are in a mode where machine should play. */
5051 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5052 (gameMode == TwoMachinesPlay ||
5054 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5056 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5057 SendToProgram("go\n", cps);
5058 if (appData.debugMode) {
5059 fprintf(debugFP, "(extra)\n");
5062 setboardSpoiledMachineBlack = 0;
5066 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5068 char user_move[MSG_SIZ];
5071 if(gameInfo.variant == VariantSChess && promoChar) {
5072 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5073 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5074 } else suffix[0] = NULLCHAR;
5078 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5079 (int)moveType, fromX, fromY, toX, toY);
5080 DisplayError(user_move + strlen("say "), 0);
5082 case WhiteKingSideCastle:
5083 case BlackKingSideCastle:
5084 case WhiteQueenSideCastleWild:
5085 case BlackQueenSideCastleWild:
5087 case WhiteHSideCastleFR:
5088 case BlackHSideCastleFR:
5090 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5092 case WhiteQueenSideCastle:
5093 case BlackQueenSideCastle:
5094 case WhiteKingSideCastleWild:
5095 case BlackKingSideCastleWild:
5097 case WhiteASideCastleFR:
5098 case BlackASideCastleFR:
5100 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5102 case WhiteNonPromotion:
5103 case BlackNonPromotion:
5104 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5106 case WhitePromotion:
5107 case BlackPromotion:
5108 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5109 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5110 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5111 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5112 PieceToChar(WhiteFerz));
5113 else if(gameInfo.variant == VariantGreat)
5114 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5115 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5116 PieceToChar(WhiteMan));
5118 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5119 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5125 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5126 ToUpper(PieceToChar((ChessSquare) fromX)),
5127 AAA + toX, ONE + toY);
5129 case IllegalMove: /* could be a variant we don't quite understand */
5130 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5132 case WhiteCapturesEnPassant:
5133 case BlackCapturesEnPassant:
5134 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5135 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5138 SendToICS(user_move);
5139 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5140 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5145 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5146 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5147 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5148 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5149 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5152 if(gameMode != IcsExamining) { // is this ever not the case?
5153 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5155 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5156 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5157 } else { // on FICS we must first go to general examine mode
5158 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5160 if(gameInfo.variant != VariantNormal) {
5161 // try figure out wild number, as xboard names are not always valid on ICS
5162 for(i=1; i<=36; i++) {
5163 snprintf(buf, MSG_SIZ, "wild/%d", i);
5164 if(StringToVariant(buf) == gameInfo.variant) break;
5166 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5167 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5168 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5169 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5170 SendToICS(ics_prefix);
5172 if(startedFromSetupPosition || backwardMostMove != 0) {
5173 fen = PositionToFEN(backwardMostMove, NULL, 1);
5174 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5175 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5177 } else { // FICS: everything has to set by separate bsetup commands
5178 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5179 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5181 if(!WhiteOnMove(backwardMostMove)) {
5182 SendToICS("bsetup tomove black\n");
5184 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5185 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5187 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5188 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5190 i = boards[backwardMostMove][EP_STATUS];
5191 if(i >= 0) { // set e.p.
5192 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5198 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5199 SendToICS("bsetup done\n"); // switch to normal examining.
5201 for(i = backwardMostMove; i<last; i++) {
5203 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5204 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5205 int len = strlen(moveList[i]);
5206 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5207 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5211 SendToICS(ics_prefix);
5212 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5216 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5218 if (rf == DROP_RANK) {
5219 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5220 sprintf(move, "%c@%c%c\n",
5221 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5223 if (promoChar == 'x' || promoChar == NULLCHAR) {
5224 sprintf(move, "%c%c%c%c\n",
5225 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5227 sprintf(move, "%c%c%c%c%c\n",
5228 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5234 ProcessICSInitScript (FILE *f)
5238 while (fgets(buf, MSG_SIZ, f)) {
5239 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5246 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5247 static ClickType lastClickType;
5252 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5253 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5254 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5255 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5256 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5257 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5260 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5261 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5262 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5263 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5264 if(!step) step = -1;
5265 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5266 appData.testLegality && (promoSweep == king ||
5267 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5269 int victim = boards[currentMove][toY][toX];
5270 boards[currentMove][toY][toX] = promoSweep;
5271 DrawPosition(FALSE, boards[currentMove]);
5272 boards[currentMove][toY][toX] = victim;
5274 ChangeDragPiece(promoSweep);
5278 PromoScroll (int x, int y)
5282 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5283 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5284 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5285 if(!step) return FALSE;
5286 lastX = x; lastY = y;
5287 if((promoSweep < BlackPawn) == flipView) step = -step;
5288 if(step > 0) selectFlag = 1;
5289 if(!selectFlag) Sweep(step);
5294 NextPiece (int step)
5296 ChessSquare piece = boards[currentMove][toY][toX];
5299 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5300 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5301 if(!step) step = -1;
5302 } while(PieceToChar(pieceSweep) == '.');
5303 boards[currentMove][toY][toX] = pieceSweep;
5304 DrawPosition(FALSE, boards[currentMove]);
5305 boards[currentMove][toY][toX] = piece;
5307 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5309 AlphaRank (char *move, int n)
5311 // char *p = move, c; int x, y;
5313 if (appData.debugMode) {
5314 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5318 move[2]>='0' && move[2]<='9' &&
5319 move[3]>='a' && move[3]<='x' ) {
5321 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5322 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5324 if(move[0]>='0' && move[0]<='9' &&
5325 move[1]>='a' && move[1]<='x' &&
5326 move[2]>='0' && move[2]<='9' &&
5327 move[3]>='a' && move[3]<='x' ) {
5328 /* input move, Shogi -> normal */
5329 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5330 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5331 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5332 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5335 move[3]>='0' && move[3]<='9' &&
5336 move[2]>='a' && move[2]<='x' ) {
5338 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5339 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5342 move[0]>='a' && move[0]<='x' &&
5343 move[3]>='0' && move[3]<='9' &&
5344 move[2]>='a' && move[2]<='x' ) {
5345 /* output move, normal -> Shogi */
5346 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5347 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5348 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5349 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5350 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5352 if (appData.debugMode) {
5353 fprintf(debugFP, " out = '%s'\n", move);
5357 char yy_textstr[8000];
5359 /* Parser for moves from gnuchess, ICS, or user typein box */
5361 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5363 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5365 switch (*moveType) {
5366 case WhitePromotion:
5367 case BlackPromotion:
5368 case WhiteNonPromotion:
5369 case BlackNonPromotion:
5371 case WhiteCapturesEnPassant:
5372 case BlackCapturesEnPassant:
5373 case WhiteKingSideCastle:
5374 case WhiteQueenSideCastle:
5375 case BlackKingSideCastle:
5376 case BlackQueenSideCastle:
5377 case WhiteKingSideCastleWild:
5378 case WhiteQueenSideCastleWild:
5379 case BlackKingSideCastleWild:
5380 case BlackQueenSideCastleWild:
5381 /* Code added by Tord: */
5382 case WhiteHSideCastleFR:
5383 case WhiteASideCastleFR:
5384 case BlackHSideCastleFR:
5385 case BlackASideCastleFR:
5386 /* End of code added by Tord */
5387 case IllegalMove: /* bug or odd chess variant */
5388 *fromX = currentMoveString[0] - AAA;
5389 *fromY = currentMoveString[1] - ONE;
5390 *toX = currentMoveString[2] - AAA;
5391 *toY = currentMoveString[3] - ONE;
5392 *promoChar = currentMoveString[4];
5393 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5394 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5395 if (appData.debugMode) {
5396 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5398 *fromX = *fromY = *toX = *toY = 0;
5401 if (appData.testLegality) {
5402 return (*moveType != IllegalMove);
5404 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5405 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5410 *fromX = *moveType == WhiteDrop ?
5411 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5412 (int) CharToPiece(ToLower(currentMoveString[0]));
5414 *toX = currentMoveString[2] - AAA;
5415 *toY = currentMoveString[3] - ONE;
5416 *promoChar = NULLCHAR;
5420 case ImpossibleMove:
5430 if (appData.debugMode) {
5431 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5434 *fromX = *fromY = *toX = *toY = 0;
5435 *promoChar = NULLCHAR;
5440 Boolean pushed = FALSE;
5441 char *lastParseAttempt;
5444 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5445 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5446 int fromX, fromY, toX, toY; char promoChar;
5451 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5452 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5453 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5456 endPV = forwardMostMove;
5458 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5459 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5460 lastParseAttempt = pv;
5461 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5462 if(!valid && nr == 0 &&
5463 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5464 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5465 // Hande case where played move is different from leading PV move
5466 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5467 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5468 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5469 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5470 endPV += 2; // if position different, keep this
5471 moveList[endPV-1][0] = fromX + AAA;
5472 moveList[endPV-1][1] = fromY + ONE;
5473 moveList[endPV-1][2] = toX + AAA;
5474 moveList[endPV-1][3] = toY + ONE;
5475 parseList[endPV-1][0] = NULLCHAR;
5476 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5479 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5480 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5481 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5482 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5483 valid++; // allow comments in PV
5487 if(endPV+1 > framePtr) break; // no space, truncate
5490 CopyBoard(boards[endPV], boards[endPV-1]);
5491 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5492 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5493 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5494 CoordsToAlgebraic(boards[endPV - 1],
5495 PosFlags(endPV - 1),
5496 fromY, fromX, toY, toX, promoChar,
5497 parseList[endPV - 1]);
5499 if(atEnd == 2) return; // used hidden, for PV conversion
5500 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5501 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5502 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5503 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5504 DrawPosition(TRUE, boards[currentMove]);
5508 MultiPV (ChessProgramState *cps)
5509 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5511 for(i=0; i<cps->nrOptions; i++)
5512 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5517 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5520 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5522 int startPV, multi, lineStart, origIndex = index;
5523 char *p, buf2[MSG_SIZ];
5524 ChessProgramState *cps = (pane ? &second : &first);
5526 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5527 lastX = x; lastY = y;
5528 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5529 lineStart = startPV = index;
5530 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5531 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5533 do{ while(buf[index] && buf[index] != '\n') index++;
5534 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5536 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5537 int n = cps->option[multi].value;
5538 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5539 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5540 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5541 cps->option[multi].value = n;
5544 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5545 ExcludeClick(origIndex - lineStart);
5548 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5549 *start = startPV; *end = index-1;
5550 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5557 static char buf[10*MSG_SIZ];
5558 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5560 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5561 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5562 for(i = forwardMostMove; i<endPV; i++){
5563 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5564 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5567 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5568 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5569 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5575 LoadPV (int x, int y)
5576 { // called on right mouse click to load PV
5577 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5578 lastX = x; lastY = y;
5579 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5587 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5588 if(endPV < 0) return;
5589 if(appData.autoCopyPV) CopyFENToClipboard();
5591 if(extendGame && currentMove > forwardMostMove) {
5592 Boolean saveAnimate = appData.animate;
5594 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5595 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5596 } else storedGames--; // abandon shelved tail of original game
5599 forwardMostMove = currentMove;
5600 currentMove = oldFMM;
5601 appData.animate = FALSE;
5602 ToNrEvent(forwardMostMove);
5603 appData.animate = saveAnimate;
5605 currentMove = forwardMostMove;
5606 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5607 ClearPremoveHighlights();
5608 DrawPosition(TRUE, boards[currentMove]);
5612 MovePV (int x, int y, int h)
5613 { // step through PV based on mouse coordinates (called on mouse move)
5614 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5616 // we must somehow check if right button is still down (might be released off board!)
5617 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5618 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5619 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5621 lastX = x; lastY = y;
5623 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5624 if(endPV < 0) return;
5625 if(y < margin) step = 1; else
5626 if(y > h - margin) step = -1;
5627 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5628 currentMove += step;
5629 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5630 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5631 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5632 DrawPosition(FALSE, boards[currentMove]);
5636 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5637 // All positions will have equal probability, but the current method will not provide a unique
5638 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5644 int piecesLeft[(int)BlackPawn];
5645 int seed, nrOfShuffles;
5648 GetPositionNumber ()
5649 { // sets global variable seed
5652 seed = appData.defaultFrcPosition;
5653 if(seed < 0) { // randomize based on time for negative FRC position numbers
5654 for(i=0; i<50; i++) seed += random();
5655 seed = random() ^ random() >> 8 ^ random() << 8;
5656 if(seed<0) seed = -seed;
5661 put (Board board, int pieceType, int rank, int n, int shade)
5662 // put the piece on the (n-1)-th empty squares of the given shade
5666 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5667 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5668 board[rank][i] = (ChessSquare) pieceType;
5669 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5671 piecesLeft[pieceType]--;
5680 AddOnePiece (Board board, int pieceType, int rank, int shade)
5681 // calculate where the next piece goes, (any empty square), and put it there
5685 i = seed % squaresLeft[shade];
5686 nrOfShuffles *= squaresLeft[shade];
5687 seed /= squaresLeft[shade];
5688 put(board, pieceType, rank, i, shade);
5692 AddTwoPieces (Board board, int pieceType, int rank)
5693 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5695 int i, n=squaresLeft[ANY], j=n-1, k;
5697 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5698 i = seed % k; // pick one
5701 while(i >= j) i -= j--;
5702 j = n - 1 - j; i += j;
5703 put(board, pieceType, rank, j, ANY);
5704 put(board, pieceType, rank, i, ANY);
5708 SetUpShuffle (Board board, int number)
5712 GetPositionNumber(); nrOfShuffles = 1;
5714 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5715 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5716 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5718 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5720 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5721 p = (int) board[0][i];
5722 if(p < (int) BlackPawn) piecesLeft[p] ++;
5723 board[0][i] = EmptySquare;
5726 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5727 // shuffles restricted to allow normal castling put KRR first
5728 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5729 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5730 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5731 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5732 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5733 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5734 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5735 put(board, WhiteRook, 0, 0, ANY);
5736 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5739 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5740 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5741 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5742 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5743 while(piecesLeft[p] >= 2) {
5744 AddOnePiece(board, p, 0, LITE);
5745 AddOnePiece(board, p, 0, DARK);
5747 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5750 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5751 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5752 // but we leave King and Rooks for last, to possibly obey FRC restriction
5753 if(p == (int)WhiteRook) continue;
5754 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5755 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5758 // now everything is placed, except perhaps King (Unicorn) and Rooks
5760 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5761 // Last King gets castling rights
5762 while(piecesLeft[(int)WhiteUnicorn]) {
5763 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5764 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5767 while(piecesLeft[(int)WhiteKing]) {
5768 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5769 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5774 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5775 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5778 // Only Rooks can be left; simply place them all
5779 while(piecesLeft[(int)WhiteRook]) {
5780 i = put(board, WhiteRook, 0, 0, ANY);
5781 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5784 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5786 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5789 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5790 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5793 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5797 SetCharTable (char *table, const char * map)
5798 /* [HGM] moved here from winboard.c because of its general usefulness */
5799 /* Basically a safe strcpy that uses the last character as King */
5801 int result = FALSE; int NrPieces;
5803 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5804 && NrPieces >= 12 && !(NrPieces&1)) {
5805 int i; /* [HGM] Accept even length from 12 to 34 */
5807 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5808 for( i=0; i<NrPieces/2-1; i++ ) {
5810 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5812 table[(int) WhiteKing] = map[NrPieces/2-1];
5813 table[(int) BlackKing] = map[NrPieces-1];
5822 Prelude (Board board)
5823 { // [HGM] superchess: random selection of exo-pieces
5824 int i, j, k; ChessSquare p;
5825 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5827 GetPositionNumber(); // use FRC position number
5829 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5830 SetCharTable(pieceToChar, appData.pieceToCharTable);
5831 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5832 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5835 j = seed%4; seed /= 4;
5836 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5837 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5838 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5839 j = seed%3 + (seed%3 >= j); seed /= 3;
5840 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5841 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5842 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5843 j = seed%3; seed /= 3;
5844 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5845 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5846 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5847 j = seed%2 + (seed%2 >= j); seed /= 2;
5848 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5849 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5850 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5851 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5852 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5853 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5854 put(board, exoPieces[0], 0, 0, ANY);
5855 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5859 InitPosition (int redraw)
5861 ChessSquare (* pieces)[BOARD_FILES];
5862 int i, j, pawnRow, overrule,
5863 oldx = gameInfo.boardWidth,
5864 oldy = gameInfo.boardHeight,
5865 oldh = gameInfo.holdingsWidth;
5868 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5870 /* [AS] Initialize pv info list [HGM] and game status */
5872 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5873 pvInfoList[i].depth = 0;
5874 boards[i][EP_STATUS] = EP_NONE;
5875 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5878 initialRulePlies = 0; /* 50-move counter start */
5880 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5881 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5885 /* [HGM] logic here is completely changed. In stead of full positions */
5886 /* the initialized data only consist of the two backranks. The switch */
5887 /* selects which one we will use, which is than copied to the Board */
5888 /* initialPosition, which for the rest is initialized by Pawns and */
5889 /* empty squares. This initial position is then copied to boards[0], */
5890 /* possibly after shuffling, so that it remains available. */
5892 gameInfo.holdingsWidth = 0; /* default board sizes */
5893 gameInfo.boardWidth = 8;
5894 gameInfo.boardHeight = 8;
5895 gameInfo.holdingsSize = 0;
5896 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5897 for(i=0; i<BOARD_FILES-2; i++)
5898 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5899 initialPosition[EP_STATUS] = EP_NONE;
5900 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5901 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5902 SetCharTable(pieceNickName, appData.pieceNickNames);
5903 else SetCharTable(pieceNickName, "............");
5906 switch (gameInfo.variant) {
5907 case VariantFischeRandom:
5908 shuffleOpenings = TRUE;
5911 case VariantShatranj:
5912 pieces = ShatranjArray;
5913 nrCastlingRights = 0;
5914 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5917 pieces = makrukArray;
5918 nrCastlingRights = 0;
5919 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5922 pieces = aseanArray;
5923 nrCastlingRights = 0;
5924 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5926 case VariantTwoKings:
5927 pieces = twoKingsArray;
5930 pieces = GrandArray;
5931 nrCastlingRights = 0;
5932 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5933 gameInfo.boardWidth = 10;
5934 gameInfo.boardHeight = 10;
5935 gameInfo.holdingsSize = 7;
5937 case VariantCapaRandom:
5938 shuffleOpenings = TRUE;
5939 case VariantCapablanca:
5940 pieces = CapablancaArray;
5941 gameInfo.boardWidth = 10;
5942 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5945 pieces = GothicArray;
5946 gameInfo.boardWidth = 10;
5947 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5950 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5951 gameInfo.holdingsSize = 7;
5952 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5955 pieces = JanusArray;
5956 gameInfo.boardWidth = 10;
5957 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5958 nrCastlingRights = 6;
5959 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5960 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5961 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5962 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5963 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5964 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5967 pieces = FalconArray;
5968 gameInfo.boardWidth = 10;
5969 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5971 case VariantXiangqi:
5972 pieces = XiangqiArray;
5973 gameInfo.boardWidth = 9;
5974 gameInfo.boardHeight = 10;
5975 nrCastlingRights = 0;
5976 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5979 pieces = ShogiArray;
5980 gameInfo.boardWidth = 9;
5981 gameInfo.boardHeight = 9;
5982 gameInfo.holdingsSize = 7;
5983 nrCastlingRights = 0;
5984 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5986 case VariantCourier:
5987 pieces = CourierArray;
5988 gameInfo.boardWidth = 12;
5989 nrCastlingRights = 0;
5990 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5992 case VariantKnightmate:
5993 pieces = KnightmateArray;
5994 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5996 case VariantSpartan:
5997 pieces = SpartanArray;
5998 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6001 pieces = fairyArray;
6002 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6005 pieces = GreatArray;
6006 gameInfo.boardWidth = 10;
6007 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6008 gameInfo.holdingsSize = 8;
6012 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6013 gameInfo.holdingsSize = 8;
6014 startedFromSetupPosition = TRUE;
6016 case VariantCrazyhouse:
6017 case VariantBughouse:
6019 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6020 gameInfo.holdingsSize = 5;
6022 case VariantWildCastle:
6024 /* !!?shuffle with kings guaranteed to be on d or e file */
6025 shuffleOpenings = 1;
6027 case VariantNoCastle:
6029 nrCastlingRights = 0;
6030 /* !!?unconstrained back-rank shuffle */
6031 shuffleOpenings = 1;
6036 if(appData.NrFiles >= 0) {
6037 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6038 gameInfo.boardWidth = appData.NrFiles;
6040 if(appData.NrRanks >= 0) {
6041 gameInfo.boardHeight = appData.NrRanks;
6043 if(appData.holdingsSize >= 0) {
6044 i = appData.holdingsSize;
6045 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6046 gameInfo.holdingsSize = i;
6048 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6049 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6050 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6052 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6053 if(pawnRow < 1) pawnRow = 1;
6054 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6056 /* User pieceToChar list overrules defaults */
6057 if(appData.pieceToCharTable != NULL)
6058 SetCharTable(pieceToChar, appData.pieceToCharTable);
6060 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6062 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6063 s = (ChessSquare) 0; /* account holding counts in guard band */
6064 for( i=0; i<BOARD_HEIGHT; i++ )
6065 initialPosition[i][j] = s;
6067 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6068 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6069 initialPosition[pawnRow][j] = WhitePawn;
6070 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6071 if(gameInfo.variant == VariantXiangqi) {
6073 initialPosition[pawnRow][j] =
6074 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6075 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6076 initialPosition[2][j] = WhiteCannon;
6077 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6081 if(gameInfo.variant == VariantGrand) {
6082 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6083 initialPosition[0][j] = WhiteRook;
6084 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6087 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
6089 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6092 initialPosition[1][j] = WhiteBishop;
6093 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6095 initialPosition[1][j] = WhiteRook;
6096 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6099 if( nrCastlingRights == -1) {
6100 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6101 /* This sets default castling rights from none to normal corners */
6102 /* Variants with other castling rights must set them themselves above */
6103 nrCastlingRights = 6;
6105 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6106 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6107 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6108 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6109 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6110 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6113 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6114 if(gameInfo.variant == VariantGreat) { // promotion commoners
6115 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6116 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6117 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6118 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6120 if( gameInfo.variant == VariantSChess ) {
6121 initialPosition[1][0] = BlackMarshall;
6122 initialPosition[2][0] = BlackAngel;
6123 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6124 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6125 initialPosition[1][1] = initialPosition[2][1] =
6126 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6128 if (appData.debugMode) {
6129 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6131 if(shuffleOpenings) {
6132 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6133 startedFromSetupPosition = TRUE;
6135 if(startedFromPositionFile) {
6136 /* [HGM] loadPos: use PositionFile for every new game */
6137 CopyBoard(initialPosition, filePosition);
6138 for(i=0; i<nrCastlingRights; i++)
6139 initialRights[i] = filePosition[CASTLING][i];
6140 startedFromSetupPosition = TRUE;
6143 CopyBoard(boards[0], initialPosition);
6145 if(oldx != gameInfo.boardWidth ||
6146 oldy != gameInfo.boardHeight ||
6147 oldv != gameInfo.variant ||
6148 oldh != gameInfo.holdingsWidth
6150 InitDrawingSizes(-2 ,0);
6152 oldv = gameInfo.variant;
6154 DrawPosition(TRUE, boards[currentMove]);
6158 SendBoard (ChessProgramState *cps, int moveNum)
6160 char message[MSG_SIZ];
6162 if (cps->useSetboard) {
6163 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6164 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6165 SendToProgram(message, cps);
6170 int i, j, left=0, right=BOARD_WIDTH;
6171 /* Kludge to set black to move, avoiding the troublesome and now
6172 * deprecated "black" command.
6174 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6175 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6177 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6179 SendToProgram("edit\n", cps);
6180 SendToProgram("#\n", cps);
6181 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6182 bp = &boards[moveNum][i][left];
6183 for (j = left; j < right; j++, bp++) {
6184 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6185 if ((int) *bp < (int) BlackPawn) {
6186 if(j == BOARD_RGHT+1)
6187 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6188 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6189 if(message[0] == '+' || message[0] == '~') {
6190 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6191 PieceToChar((ChessSquare)(DEMOTED *bp)),
6194 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6195 message[1] = BOARD_RGHT - 1 - j + '1';
6196 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6198 SendToProgram(message, cps);
6203 SendToProgram("c\n", cps);
6204 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6205 bp = &boards[moveNum][i][left];
6206 for (j = left; j < right; j++, bp++) {
6207 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6208 if (((int) *bp != (int) EmptySquare)
6209 && ((int) *bp >= (int) BlackPawn)) {
6210 if(j == BOARD_LEFT-2)
6211 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6212 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6214 if(message[0] == '+' || message[0] == '~') {
6215 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6216 PieceToChar((ChessSquare)(DEMOTED *bp)),
6219 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6220 message[1] = BOARD_RGHT - 1 - j + '1';
6221 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6223 SendToProgram(message, cps);
6228 SendToProgram(".\n", cps);
6230 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6233 char exclusionHeader[MSG_SIZ];
6234 int exCnt, excludePtr;
6235 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6236 static Exclusion excluTab[200];
6237 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6243 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6244 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6250 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6251 excludePtr = 24; exCnt = 0;
6256 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6257 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6258 char buf[2*MOVE_LEN], *p;
6259 Exclusion *e = excluTab;
6261 for(i=0; i<exCnt; i++)
6262 if(e[i].ff == fromX && e[i].fr == fromY &&
6263 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6264 if(i == exCnt) { // was not in exclude list; add it
6265 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6266 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6267 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6270 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6271 excludePtr++; e[i].mark = excludePtr++;
6272 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6275 exclusionHeader[e[i].mark] = state;
6279 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6280 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6284 if((signed char)promoChar == -1) { // kludge to indicate best move
6285 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6286 return 1; // if unparsable, abort
6288 // update exclusion map (resolving toggle by consulting existing state)
6289 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6291 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6292 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6293 excludeMap[k] |= 1<<j;
6294 else excludeMap[k] &= ~(1<<j);
6296 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6298 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6299 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6301 return (state == '+');
6305 ExcludeClick (int index)
6308 Exclusion *e = excluTab;
6309 if(index < 25) { // none, best or tail clicked
6310 if(index < 13) { // none: include all
6311 WriteMap(0); // clear map
6312 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6313 SendToBoth("include all\n"); // and inform engine
6314 } else if(index > 18) { // tail
6315 if(exclusionHeader[19] == '-') { // tail was excluded
6316 SendToBoth("include all\n");
6317 WriteMap(0); // clear map completely
6318 // now re-exclude selected moves
6319 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6320 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6321 } else { // tail was included or in mixed state
6322 SendToBoth("exclude all\n");
6323 WriteMap(0xFF); // fill map completely
6324 // now re-include selected moves
6325 j = 0; // count them
6326 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6327 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6328 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6331 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6334 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6335 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6336 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6343 DefaultPromoChoice (int white)
6346 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6347 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6348 result = WhiteFerz; // no choice
6349 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6350 result= WhiteKing; // in Suicide Q is the last thing we want
6351 else if(gameInfo.variant == VariantSpartan)
6352 result = white ? WhiteQueen : WhiteAngel;
6353 else result = WhiteQueen;
6354 if(!white) result = WHITE_TO_BLACK result;
6358 static int autoQueen; // [HGM] oneclick
6361 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6363 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6364 /* [HGM] add Shogi promotions */
6365 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6370 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6371 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6373 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6374 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6377 piece = boards[currentMove][fromY][fromX];
6378 if(gameInfo.variant == VariantShogi) {
6379 promotionZoneSize = BOARD_HEIGHT/3;
6380 highestPromotingPiece = (int)WhiteFerz;
6381 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6382 promotionZoneSize = 3;
6385 // Treat Lance as Pawn when it is not representing Amazon
6386 if(gameInfo.variant != VariantSuper) {
6387 if(piece == WhiteLance) piece = WhitePawn; else
6388 if(piece == BlackLance) piece = BlackPawn;
6391 // next weed out all moves that do not touch the promotion zone at all
6392 if((int)piece >= BlackPawn) {
6393 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6395 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6397 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6398 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6401 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6403 // weed out mandatory Shogi promotions
6404 if(gameInfo.variant == VariantShogi) {
6405 if(piece >= BlackPawn) {
6406 if(toY == 0 && piece == BlackPawn ||
6407 toY == 0 && piece == BlackQueen ||
6408 toY <= 1 && piece == BlackKnight) {
6413 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6414 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6415 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6422 // weed out obviously illegal Pawn moves
6423 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6424 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6425 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6426 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6427 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6428 // note we are not allowed to test for valid (non-)capture, due to premove
6431 // we either have a choice what to promote to, or (in Shogi) whether to promote
6432 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6433 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6434 *promoChoice = PieceToChar(BlackFerz); // no choice
6437 // no sense asking what we must promote to if it is going to explode...
6438 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6439 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6442 // give caller the default choice even if we will not make it
6443 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6444 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6445 if( sweepSelect && gameInfo.variant != VariantGreat
6446 && gameInfo.variant != VariantGrand
6447 && gameInfo.variant != VariantSuper) return FALSE;
6448 if(autoQueen) return FALSE; // predetermined
6450 // suppress promotion popup on illegal moves that are not premoves
6451 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6452 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6453 if(appData.testLegality && !premove) {
6454 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6455 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6456 if(moveType != WhitePromotion && moveType != BlackPromotion)
6464 InPalace (int row, int column)
6465 { /* [HGM] for Xiangqi */
6466 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6467 column < (BOARD_WIDTH + 4)/2 &&
6468 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6473 PieceForSquare (int x, int y)
6475 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6478 return boards[currentMove][y][x];
6482 OKToStartUserMove (int x, int y)
6484 ChessSquare from_piece;
6487 if (matchMode) return FALSE;
6488 if (gameMode == EditPosition) return TRUE;
6490 if (x >= 0 && y >= 0)
6491 from_piece = boards[currentMove][y][x];
6493 from_piece = EmptySquare;
6495 if (from_piece == EmptySquare) return FALSE;
6497 white_piece = (int)from_piece >= (int)WhitePawn &&
6498 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6502 case TwoMachinesPlay:
6510 case MachinePlaysWhite:
6511 case IcsPlayingBlack:
6512 if (appData.zippyPlay) return FALSE;
6514 DisplayMoveError(_("You are playing Black"));
6519 case MachinePlaysBlack:
6520 case IcsPlayingWhite:
6521 if (appData.zippyPlay) return FALSE;
6523 DisplayMoveError(_("You are playing White"));
6528 case PlayFromGameFile:
6529 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6531 if (!white_piece && WhiteOnMove(currentMove)) {
6532 DisplayMoveError(_("It is White's turn"));
6535 if (white_piece && !WhiteOnMove(currentMove)) {
6536 DisplayMoveError(_("It is Black's turn"));
6539 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6540 /* Editing correspondence game history */
6541 /* Could disallow this or prompt for confirmation */
6546 case BeginningOfGame:
6547 if (appData.icsActive) return FALSE;
6548 if (!appData.noChessProgram) {
6550 DisplayMoveError(_("You are playing White"));
6557 if (!white_piece && WhiteOnMove(currentMove)) {
6558 DisplayMoveError(_("It is White's turn"));
6561 if (white_piece && !WhiteOnMove(currentMove)) {
6562 DisplayMoveError(_("It is Black's turn"));
6571 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6572 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6573 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6574 && gameMode != AnalyzeFile && gameMode != Training) {
6575 DisplayMoveError(_("Displayed position is not current"));
6582 OnlyMove (int *x, int *y, Boolean captures)
6584 DisambiguateClosure cl;
6585 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6587 case MachinePlaysBlack:
6588 case IcsPlayingWhite:
6589 case BeginningOfGame:
6590 if(!WhiteOnMove(currentMove)) return FALSE;
6592 case MachinePlaysWhite:
6593 case IcsPlayingBlack:
6594 if(WhiteOnMove(currentMove)) return FALSE;
6601 cl.pieceIn = EmptySquare;
6606 cl.promoCharIn = NULLCHAR;
6607 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6608 if( cl.kind == NormalMove ||
6609 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6610 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6611 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6618 if(cl.kind != ImpossibleMove) return FALSE;
6619 cl.pieceIn = EmptySquare;
6624 cl.promoCharIn = NULLCHAR;
6625 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6626 if( cl.kind == NormalMove ||
6627 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6628 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6629 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6634 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6640 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6641 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6642 int lastLoadGameUseList = FALSE;
6643 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6644 ChessMove lastLoadGameStart = EndOfFile;
6648 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6652 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6654 /* Check if the user is playing in turn. This is complicated because we
6655 let the user "pick up" a piece before it is his turn. So the piece he
6656 tried to pick up may have been captured by the time he puts it down!
6657 Therefore we use the color the user is supposed to be playing in this
6658 test, not the color of the piece that is currently on the starting
6659 square---except in EditGame mode, where the user is playing both
6660 sides; fortunately there the capture race can't happen. (It can
6661 now happen in IcsExamining mode, but that's just too bad. The user
6662 will get a somewhat confusing message in that case.)
6667 case TwoMachinesPlay:
6671 /* We switched into a game mode where moves are not accepted,
6672 perhaps while the mouse button was down. */
6675 case MachinePlaysWhite:
6676 /* User is moving for Black */
6677 if (WhiteOnMove(currentMove)) {
6678 DisplayMoveError(_("It is White's turn"));
6683 case MachinePlaysBlack:
6684 /* User is moving for White */
6685 if (!WhiteOnMove(currentMove)) {
6686 DisplayMoveError(_("It is Black's turn"));
6691 case PlayFromGameFile:
6692 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6695 case BeginningOfGame:
6698 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6699 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6700 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6701 /* User is moving for Black */
6702 if (WhiteOnMove(currentMove)) {
6703 DisplayMoveError(_("It is White's turn"));
6707 /* User is moving for White */
6708 if (!WhiteOnMove(currentMove)) {
6709 DisplayMoveError(_("It is Black's turn"));
6715 case IcsPlayingBlack:
6716 /* User is moving for Black */
6717 if (WhiteOnMove(currentMove)) {
6718 if (!appData.premove) {
6719 DisplayMoveError(_("It is White's turn"));
6720 } else if (toX >= 0 && toY >= 0) {
6723 premoveFromX = fromX;
6724 premoveFromY = fromY;
6725 premovePromoChar = promoChar;
6727 if (appData.debugMode)
6728 fprintf(debugFP, "Got premove: fromX %d,"
6729 "fromY %d, toX %d, toY %d\n",
6730 fromX, fromY, toX, toY);
6736 case IcsPlayingWhite:
6737 /* User is moving for White */
6738 if (!WhiteOnMove(currentMove)) {
6739 if (!appData.premove) {
6740 DisplayMoveError(_("It is Black's turn"));
6741 } else if (toX >= 0 && toY >= 0) {
6744 premoveFromX = fromX;
6745 premoveFromY = fromY;
6746 premovePromoChar = promoChar;
6748 if (appData.debugMode)
6749 fprintf(debugFP, "Got premove: fromX %d,"
6750 "fromY %d, toX %d, toY %d\n",
6751 fromX, fromY, toX, toY);
6761 /* EditPosition, empty square, or different color piece;
6762 click-click move is possible */
6763 if (toX == -2 || toY == -2) {
6764 boards[0][fromY][fromX] = EmptySquare;
6765 DrawPosition(FALSE, boards[currentMove]);
6767 } else if (toX >= 0 && toY >= 0) {
6768 boards[0][toY][toX] = boards[0][fromY][fromX];
6769 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6770 if(boards[0][fromY][0] != EmptySquare) {
6771 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6772 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6775 if(fromX == BOARD_RGHT+1) {
6776 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6777 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6778 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6781 boards[0][fromY][fromX] = gatingPiece;
6782 DrawPosition(FALSE, boards[currentMove]);
6788 if(toX < 0 || toY < 0) return;
6789 pup = boards[currentMove][toY][toX];
6791 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6792 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6793 if( pup != EmptySquare ) return;
6794 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6795 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6796 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6797 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6798 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6799 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6800 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6804 /* [HGM] always test for legality, to get promotion info */
6805 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6806 fromY, fromX, toY, toX, promoChar);
6808 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6810 /* [HGM] but possibly ignore an IllegalMove result */
6811 if (appData.testLegality) {
6812 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6813 DisplayMoveError(_("Illegal move"));
6818 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6819 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6820 ClearPremoveHighlights(); // was included
6821 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6825 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6828 /* Common tail of UserMoveEvent and DropMenuEvent */
6830 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6834 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6835 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6836 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6837 if(WhiteOnMove(currentMove)) {
6838 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6840 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6844 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6845 move type in caller when we know the move is a legal promotion */
6846 if(moveType == NormalMove && promoChar)
6847 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6849 /* [HGM] <popupFix> The following if has been moved here from
6850 UserMoveEvent(). Because it seemed to belong here (why not allow
6851 piece drops in training games?), and because it can only be
6852 performed after it is known to what we promote. */
6853 if (gameMode == Training) {
6854 /* compare the move played on the board to the next move in the
6855 * game. If they match, display the move and the opponent's response.
6856 * If they don't match, display an error message.
6860 CopyBoard(testBoard, boards[currentMove]);
6861 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6863 if (CompareBoards(testBoard, boards[currentMove+1])) {
6864 ForwardInner(currentMove+1);
6866 /* Autoplay the opponent's response.
6867 * if appData.animate was TRUE when Training mode was entered,
6868 * the response will be animated.
6870 saveAnimate = appData.animate;
6871 appData.animate = animateTraining;
6872 ForwardInner(currentMove+1);
6873 appData.animate = saveAnimate;
6875 /* check for the end of the game */
6876 if (currentMove >= forwardMostMove) {
6877 gameMode = PlayFromGameFile;
6879 SetTrainingModeOff();
6880 DisplayInformation(_("End of game"));
6883 DisplayError(_("Incorrect move"), 0);
6888 /* Ok, now we know that the move is good, so we can kill
6889 the previous line in Analysis Mode */
6890 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6891 && currentMove < forwardMostMove) {
6892 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6893 else forwardMostMove = currentMove;
6898 /* If we need the chess program but it's dead, restart it */
6899 ResurrectChessProgram();
6901 /* A user move restarts a paused game*/
6905 thinkOutput[0] = NULLCHAR;
6907 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6909 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6910 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6914 if (gameMode == BeginningOfGame) {
6915 if (appData.noChessProgram) {
6916 gameMode = EditGame;
6920 gameMode = MachinePlaysBlack;
6923 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6925 if (first.sendName) {
6926 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6927 SendToProgram(buf, &first);
6934 /* Relay move to ICS or chess engine */
6935 if (appData.icsActive) {
6936 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6937 gameMode == IcsExamining) {
6938 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6939 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6941 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6943 // also send plain move, in case ICS does not understand atomic claims
6944 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6948 if (first.sendTime && (gameMode == BeginningOfGame ||
6949 gameMode == MachinePlaysWhite ||
6950 gameMode == MachinePlaysBlack)) {
6951 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6953 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6954 // [HGM] book: if program might be playing, let it use book
6955 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6956 first.maybeThinking = TRUE;
6957 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6958 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6959 SendBoard(&first, currentMove+1);
6960 if(second.analyzing) {
6961 if(!second.useSetboard) SendToProgram("undo\n", &second);
6962 SendBoard(&second, currentMove+1);
6965 SendMoveToProgram(forwardMostMove-1, &first);
6966 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6968 if (currentMove == cmailOldMove + 1) {
6969 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6973 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6977 if(appData.testLegality)
6978 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6984 if (WhiteOnMove(currentMove)) {
6985 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6987 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6991 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6996 case MachinePlaysBlack:
6997 case MachinePlaysWhite:
6998 /* disable certain menu options while machine is thinking */
6999 SetMachineThinkingEnables();
7006 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7007 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7009 if(bookHit) { // [HGM] book: simulate book reply
7010 static char bookMove[MSG_SIZ]; // a bit generous?
7012 programStats.nodes = programStats.depth = programStats.time =
7013 programStats.score = programStats.got_only_move = 0;
7014 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7016 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7017 strcat(bookMove, bookHit);
7018 HandleMachineMove(bookMove, &first);
7024 MarkByFEN(char *fen)
7027 if(!appData.markers || !appData.highlightDragging) return;
7028 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7029 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7033 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7034 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7035 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7036 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7037 if(*fen == 'T') marker[r][f++] = 0; else
7038 if(*fen == 'Y') marker[r][f++] = 1; else
7039 if(*fen == 'G') marker[r][f++] = 3; else
7040 if(*fen == 'B') marker[r][f++] = 4; else
7041 if(*fen == 'C') marker[r][f++] = 5; else
7042 if(*fen == 'M') marker[r][f++] = 6; else
7043 if(*fen == 'W') marker[r][f++] = 7; else
7044 if(*fen == 'D') marker[r][f++] = 8; else
7045 if(*fen == 'R') marker[r][f++] = 2; else {
7046 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7049 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7053 DrawPosition(TRUE, NULL);
7057 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7059 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7060 Markers *m = (Markers *) closure;
7061 if(rf == fromY && ff == fromX)
7062 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7063 || kind == WhiteCapturesEnPassant
7064 || kind == BlackCapturesEnPassant);
7065 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7069 MarkTargetSquares (int clear)
7072 if(clear) { // no reason to ever suppress clearing
7073 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7074 if(!sum) return; // nothing was cleared,no redraw needed
7077 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7078 !appData.testLegality || gameMode == EditPosition) return;
7079 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7080 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7081 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7083 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7086 DrawPosition(FALSE, NULL);
7090 Explode (Board board, int fromX, int fromY, int toX, int toY)
7092 if(gameInfo.variant == VariantAtomic &&
7093 (board[toY][toX] != EmptySquare || // capture?
7094 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7095 board[fromY][fromX] == BlackPawn )
7097 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7103 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7106 CanPromote (ChessSquare piece, int y)
7108 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7109 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7110 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
7111 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7112 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7113 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7114 return (piece == BlackPawn && y == 1 ||
7115 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7116 piece == BlackLance && y == 1 ||
7117 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7121 HoverEvent (int hiX, int hiY, int x, int y)
7123 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7125 if(!first.highlight) return;
7126 if(hiX == -1 && hiY == -1 && x == fromX && y == fromY) // record markings
7127 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7128 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7129 else if(hiX != x || hiY != y) {
7130 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7131 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7132 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7133 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7135 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7136 SendToProgram(buf, &first);
7138 SetHighlights(fromX, fromY, x, y);
7142 void ReportClick(char *action, int x, int y)
7144 char buf[MSG_SIZ]; // Inform engine of what user does
7146 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7147 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7148 if(!first.highlight || gameMode == EditPosition) return;
7149 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7150 SendToProgram(buf, &first);
7154 LeftClick (ClickType clickType, int xPix, int yPix)
7157 Boolean saveAnimate;
7158 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7159 char promoChoice = NULLCHAR;
7161 static TimeMark lastClickTime, prevClickTime;
7163 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7165 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7167 if (clickType == Press) ErrorPopDown();
7168 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7170 x = EventToSquare(xPix, BOARD_WIDTH);
7171 y = EventToSquare(yPix, BOARD_HEIGHT);
7172 if (!flipView && y >= 0) {
7173 y = BOARD_HEIGHT - 1 - y;
7175 if (flipView && x >= 0) {
7176 x = BOARD_WIDTH - 1 - x;
7179 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7180 defaultPromoChoice = promoSweep;
7181 promoSweep = EmptySquare; // terminate sweep
7182 promoDefaultAltered = TRUE;
7183 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7186 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7187 if(clickType == Release) return; // ignore upclick of click-click destination
7188 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7189 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7190 if(gameInfo.holdingsWidth &&
7191 (WhiteOnMove(currentMove)
7192 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7193 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7194 // click in right holdings, for determining promotion piece
7195 ChessSquare p = boards[currentMove][y][x];
7196 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7197 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7198 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7199 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7204 DrawPosition(FALSE, boards[currentMove]);
7208 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7209 if(clickType == Press
7210 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7211 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7212 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7215 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7216 // could be static click on premove from-square: abort premove
7218 ClearPremoveHighlights();
7221 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7222 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7224 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7225 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7226 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7227 defaultPromoChoice = DefaultPromoChoice(side);
7230 autoQueen = appData.alwaysPromoteToQueen;
7234 gatingPiece = EmptySquare;
7235 if (clickType != Press) {
7236 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7237 DragPieceEnd(xPix, yPix); dragging = 0;
7238 DrawPosition(FALSE, NULL);
7242 doubleClick = FALSE;
7243 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7244 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7246 fromX = x; fromY = y; toX = toY = -1;
7247 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7248 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7249 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7251 if (OKToStartUserMove(fromX, fromY)) {
7253 ReportClick("lift", x, y);
7254 MarkTargetSquares(0);
7255 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7256 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7257 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7258 promoSweep = defaultPromoChoice;
7259 selectFlag = 0; lastX = xPix; lastY = yPix;
7260 Sweep(0); // Pawn that is going to promote: preview promotion piece
7261 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7263 if (appData.highlightDragging) {
7264 SetHighlights(fromX, fromY, -1, -1);
7268 } else fromX = fromY = -1;
7274 if (clickType == Press && gameMode != EditPosition) {
7279 // ignore off-board to clicks
7280 if(y < 0 || x < 0) return;
7282 /* Check if clicking again on the same color piece */
7283 fromP = boards[currentMove][fromY][fromX];
7284 toP = boards[currentMove][y][x];
7285 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7286 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7287 WhitePawn <= toP && toP <= WhiteKing &&
7288 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7289 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7290 (BlackPawn <= fromP && fromP <= BlackKing &&
7291 BlackPawn <= toP && toP <= BlackKing &&
7292 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7293 !(fromP == BlackKing && toP == BlackRook && frc))) {
7294 /* Clicked again on same color piece -- changed his mind */
7295 second = (x == fromX && y == fromY);
7296 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7297 second = FALSE; // first double-click rather than scond click
7298 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7300 promoDefaultAltered = FALSE;
7301 MarkTargetSquares(1);
7302 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7303 if (appData.highlightDragging) {
7304 SetHighlights(x, y, -1, -1);
7308 if (OKToStartUserMove(x, y)) {
7309 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7310 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7311 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7312 gatingPiece = boards[currentMove][fromY][fromX];
7313 else gatingPiece = doubleClick ? fromP : EmptySquare;
7315 fromY = y; dragging = 1;
7316 ReportClick("lift", x, y);
7317 MarkTargetSquares(0);
7318 DragPieceBegin(xPix, yPix, FALSE);
7319 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7320 promoSweep = defaultPromoChoice;
7321 selectFlag = 0; lastX = xPix; lastY = yPix;
7322 Sweep(0); // Pawn that is going to promote: preview promotion piece
7326 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7329 // ignore clicks on holdings
7330 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7333 if (clickType == Release && x == fromX && y == fromY) {
7334 DragPieceEnd(xPix, yPix); dragging = 0;
7336 // a deferred attempt to click-click move an empty square on top of a piece
7337 boards[currentMove][y][x] = EmptySquare;
7339 DrawPosition(FALSE, boards[currentMove]);
7340 fromX = fromY = -1; clearFlag = 0;
7343 if (appData.animateDragging) {
7344 /* Undo animation damage if any */
7345 DrawPosition(FALSE, NULL);
7347 if (second || sweepSelecting) {
7348 /* Second up/down in same square; just abort move */
7349 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7350 second = sweepSelecting = 0;
7352 gatingPiece = EmptySquare;
7353 MarkTargetSquares(1);
7356 ClearPremoveHighlights();
7358 /* First upclick in same square; start click-click mode */
7359 SetHighlights(x, y, -1, -1);
7366 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x]) {
7367 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7368 DisplayMessage(_("only marked squares are legal"),"");
7369 DrawPosition(TRUE, NULL);
7370 return; // ignore to-click
7373 /* we now have a different from- and (possibly off-board) to-square */
7374 /* Completed move */
7375 if(!sweepSelecting) {
7378 } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7380 saveAnimate = appData.animate;
7381 if (clickType == Press) {
7382 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7383 // must be Edit Position mode with empty-square selected
7384 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7385 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7388 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7389 if(appData.sweepSelect) {
7390 ChessSquare piece = boards[currentMove][fromY][fromX];
7391 promoSweep = defaultPromoChoice;
7392 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7393 selectFlag = 0; lastX = xPix; lastY = yPix;
7394 Sweep(0); // Pawn that is going to promote: preview promotion piece
7396 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7397 MarkTargetSquares(1);
7399 return; // promo popup appears on up-click
7401 /* Finish clickclick move */
7402 if (appData.animate || appData.highlightLastMove) {
7403 SetHighlights(fromX, fromY, toX, toY);
7409 // [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
7410 /* Finish drag move */
7411 if (appData.highlightLastMove) {
7412 SetHighlights(fromX, fromY, toX, toY);
7417 DragPieceEnd(xPix, yPix); dragging = 0;
7418 /* Don't animate move and drag both */
7419 appData.animate = FALSE;
7422 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7423 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7424 ChessSquare piece = boards[currentMove][fromY][fromX];
7425 if(gameMode == EditPosition && piece != EmptySquare &&
7426 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7429 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7430 n = PieceToNumber(piece - (int)BlackPawn);
7431 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7432 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7433 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7435 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7436 n = PieceToNumber(piece);
7437 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7438 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7439 boards[currentMove][n][BOARD_WIDTH-2]++;
7441 boards[currentMove][fromY][fromX] = EmptySquare;
7445 MarkTargetSquares(1);
7446 DrawPosition(TRUE, boards[currentMove]);
7450 // off-board moves should not be highlighted
7451 if(x < 0 || y < 0) ClearHighlights();
7452 else ReportClick("put", x, y);
7454 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7456 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7457 SetHighlights(fromX, fromY, toX, toY);
7458 MarkTargetSquares(1);
7459 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7460 // [HGM] super: promotion to captured piece selected from holdings
7461 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7462 promotionChoice = TRUE;
7463 // kludge follows to temporarily execute move on display, without promoting yet
7464 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7465 boards[currentMove][toY][toX] = p;
7466 DrawPosition(FALSE, boards[currentMove]);
7467 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7468 boards[currentMove][toY][toX] = q;
7469 DisplayMessage("Click in holdings to choose piece", "");
7474 int oldMove = currentMove;
7475 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7476 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7477 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7478 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7479 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7480 DrawPosition(TRUE, boards[currentMove]);
7481 MarkTargetSquares(1);
7484 appData.animate = saveAnimate;
7485 if (appData.animate || appData.animateDragging) {
7486 /* Undo animation damage if needed */
7487 DrawPosition(FALSE, NULL);
7492 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7493 { // front-end-free part taken out of PieceMenuPopup
7494 int whichMenu; int xSqr, ySqr;
7496 if(seekGraphUp) { // [HGM] seekgraph
7497 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7498 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7502 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7503 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7504 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7505 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7506 if(action == Press) {
7507 originalFlip = flipView;
7508 flipView = !flipView; // temporarily flip board to see game from partners perspective
7509 DrawPosition(TRUE, partnerBoard);
7510 DisplayMessage(partnerStatus, "");
7512 } else if(action == Release) {
7513 flipView = originalFlip;
7514 DrawPosition(TRUE, boards[currentMove]);
7520 xSqr = EventToSquare(x, BOARD_WIDTH);
7521 ySqr = EventToSquare(y, BOARD_HEIGHT);
7522 if (action == Release) {
7523 if(pieceSweep != EmptySquare) {
7524 EditPositionMenuEvent(pieceSweep, toX, toY);
7525 pieceSweep = EmptySquare;
7526 } else UnLoadPV(); // [HGM] pv
7528 if (action != Press) return -2; // return code to be ignored
7531 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7533 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7534 if (xSqr < 0 || ySqr < 0) return -1;
7535 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7536 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7537 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7538 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7542 if(!appData.icsEngineAnalyze) return -1;
7543 case IcsPlayingWhite:
7544 case IcsPlayingBlack:
7545 if(!appData.zippyPlay) goto noZip;
7548 case MachinePlaysWhite:
7549 case MachinePlaysBlack:
7550 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7551 if (!appData.dropMenu) {
7553 return 2; // flag front-end to grab mouse events
7555 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7556 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7559 if (xSqr < 0 || ySqr < 0) return -1;
7560 if (!appData.dropMenu || appData.testLegality &&
7561 gameInfo.variant != VariantBughouse &&
7562 gameInfo.variant != VariantCrazyhouse) return -1;
7563 whichMenu = 1; // drop menu
7569 if (((*fromX = xSqr) < 0) ||
7570 ((*fromY = ySqr) < 0)) {
7571 *fromX = *fromY = -1;
7575 *fromX = BOARD_WIDTH - 1 - *fromX;
7577 *fromY = BOARD_HEIGHT - 1 - *fromY;
7583 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7585 // char * hint = lastHint;
7586 FrontEndProgramStats stats;
7588 stats.which = cps == &first ? 0 : 1;
7589 stats.depth = cpstats->depth;
7590 stats.nodes = cpstats->nodes;
7591 stats.score = cpstats->score;
7592 stats.time = cpstats->time;
7593 stats.pv = cpstats->movelist;
7594 stats.hint = lastHint;
7595 stats.an_move_index = 0;
7596 stats.an_move_count = 0;
7598 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7599 stats.hint = cpstats->move_name;
7600 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7601 stats.an_move_count = cpstats->nr_moves;
7604 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
7606 SetProgramStats( &stats );
7610 ClearEngineOutputPane (int which)
7612 static FrontEndProgramStats dummyStats;
7613 dummyStats.which = which;
7614 dummyStats.pv = "#";
7615 SetProgramStats( &dummyStats );
7618 #define MAXPLAYERS 500
7621 TourneyStandings (int display)
7623 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7624 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7625 char result, *p, *names[MAXPLAYERS];
7627 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7628 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7629 names[0] = p = strdup(appData.participants);
7630 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7632 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7634 while(result = appData.results[nr]) {
7635 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7636 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7637 wScore = bScore = 0;
7639 case '+': wScore = 2; break;
7640 case '-': bScore = 2; break;
7641 case '=': wScore = bScore = 1; break;
7643 case '*': return strdup("busy"); // tourney not finished
7651 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7652 for(w=0; w<nPlayers; w++) {
7654 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7655 ranking[w] = b; points[w] = bScore; score[b] = -2;
7657 p = malloc(nPlayers*34+1);
7658 for(w=0; w<nPlayers && w<display; w++)
7659 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7665 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7666 { // count all piece types
7668 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7669 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7670 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7673 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7674 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7675 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7676 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7677 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7678 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7683 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7685 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7686 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7688 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7689 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7690 if(myPawns == 2 && nMine == 3) // KPP
7691 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7692 if(myPawns == 1 && nMine == 2) // KP
7693 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7694 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7695 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7696 if(myPawns) return FALSE;
7697 if(pCnt[WhiteRook+side])
7698 return pCnt[BlackRook-side] ||
7699 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7700 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7701 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7702 if(pCnt[WhiteCannon+side]) {
7703 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7704 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7706 if(pCnt[WhiteKnight+side])
7707 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7712 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7714 VariantClass v = gameInfo.variant;
7716 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7717 if(v == VariantShatranj) return TRUE; // always winnable through baring
7718 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7719 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7721 if(v == VariantXiangqi) {
7722 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7724 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7725 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7726 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7727 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7728 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7729 if(stale) // we have at least one last-rank P plus perhaps C
7730 return majors // KPKX
7731 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7733 return pCnt[WhiteFerz+side] // KCAK
7734 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7735 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7736 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7738 } else if(v == VariantKnightmate) {
7739 if(nMine == 1) return FALSE;
7740 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7741 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7742 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7744 if(nMine == 1) return FALSE; // bare King
7745 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
7746 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7747 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7748 // by now we have King + 1 piece (or multiple Bishops on the same color)
7749 if(pCnt[WhiteKnight+side])
7750 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7751 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7752 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7754 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7755 if(pCnt[WhiteAlfil+side])
7756 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7757 if(pCnt[WhiteWazir+side])
7758 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7765 CompareWithRights (Board b1, Board b2)
7768 if(!CompareBoards(b1, b2)) return FALSE;
7769 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7770 /* compare castling rights */
7771 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7772 rights++; /* King lost rights, while rook still had them */
7773 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7774 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7775 rights++; /* but at least one rook lost them */
7777 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7779 if( b1[CASTLING][5] != NoRights ) {
7780 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7787 Adjudicate (ChessProgramState *cps)
7788 { // [HGM] some adjudications useful with buggy engines
7789 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7790 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7791 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7792 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7793 int k, drop, count = 0; static int bare = 1;
7794 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7795 Boolean canAdjudicate = !appData.icsActive;
7797 // most tests only when we understand the game, i.e. legality-checking on
7798 if( appData.testLegality )
7799 { /* [HGM] Some more adjudications for obstinate engines */
7800 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7801 static int moveCount = 6;
7803 char *reason = NULL;
7805 /* Count what is on board. */
7806 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7808 /* Some material-based adjudications that have to be made before stalemate test */
7809 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7810 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7811 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7812 if(canAdjudicate && appData.checkMates) {
7814 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7815 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7816 "Xboard adjudication: King destroyed", GE_XBOARD );
7821 /* Bare King in Shatranj (loses) or Losers (wins) */
7822 if( nrW == 1 || nrB == 1) {
7823 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7824 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7825 if(canAdjudicate && appData.checkMates) {
7827 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7828 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7829 "Xboard adjudication: Bare king", GE_XBOARD );
7833 if( gameInfo.variant == VariantShatranj && --bare < 0)
7835 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7836 if(canAdjudicate && appData.checkMates) {
7837 /* but only adjudicate if adjudication enabled */
7839 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7840 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7841 "Xboard adjudication: Bare king", GE_XBOARD );
7848 // don't wait for engine to announce game end if we can judge ourselves
7849 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7851 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7852 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7853 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7854 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7857 reason = "Xboard adjudication: 3rd check";
7858 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7868 reason = "Xboard adjudication: Stalemate";
7869 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7870 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7871 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7872 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7873 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7874 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7875 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7876 EP_CHECKMATE : EP_WINS);
7877 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7878 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7882 reason = "Xboard adjudication: Checkmate";
7883 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7884 if(gameInfo.variant == VariantShogi) {
7885 if(forwardMostMove > backwardMostMove
7886 && moveList[forwardMostMove-1][1] == '@'
7887 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7888 reason = "XBoard adjudication: pawn-drop mate";
7889 boards[forwardMostMove][EP_STATUS] = EP_WINS;
7895 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7897 result = GameIsDrawn; break;
7899 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7901 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7905 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7907 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7908 GameEnds( result, reason, GE_XBOARD );
7912 /* Next absolutely insufficient mating material. */
7913 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7914 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7915 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7917 /* always flag draws, for judging claims */
7918 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7920 if(canAdjudicate && appData.materialDraws) {
7921 /* but only adjudicate them if adjudication enabled */
7922 if(engineOpponent) {
7923 SendToProgram("force\n", engineOpponent); // suppress reply
7924 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7926 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7931 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7932 if(gameInfo.variant == VariantXiangqi ?
7933 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7935 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7936 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7937 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7938 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7940 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7941 { /* if the first 3 moves do not show a tactical win, declare draw */
7942 if(engineOpponent) {
7943 SendToProgram("force\n", engineOpponent); // suppress reply
7944 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7946 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7949 } else moveCount = 6;
7952 // Repetition draws and 50-move rule can be applied independently of legality testing
7954 /* Check for rep-draws */
7956 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7957 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7958 for(k = forwardMostMove-2;
7959 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7960 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7961 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7964 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7965 /* compare castling rights */
7966 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7967 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7968 rights++; /* King lost rights, while rook still had them */
7969 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7970 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7971 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7972 rights++; /* but at least one rook lost them */
7974 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7975 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7977 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7978 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7979 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7982 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7983 && appData.drawRepeats > 1) {
7984 /* adjudicate after user-specified nr of repeats */
7985 int result = GameIsDrawn;
7986 char *details = "XBoard adjudication: repetition draw";
7987 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7988 // [HGM] xiangqi: check for forbidden perpetuals
7989 int m, ourPerpetual = 1, hisPerpetual = 1;
7990 for(m=forwardMostMove; m>k; m-=2) {
7991 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7992 ourPerpetual = 0; // the current mover did not always check
7993 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7994 hisPerpetual = 0; // the opponent did not always check
7996 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7997 ourPerpetual, hisPerpetual);
7998 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7999 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8000 details = "Xboard adjudication: perpetual checking";
8002 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8003 break; // (or we would have caught him before). Abort repetition-checking loop.
8005 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8006 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8008 details = "Xboard adjudication: repetition";
8010 } else // it must be XQ
8011 // Now check for perpetual chases
8012 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8013 hisPerpetual = PerpetualChase(k, forwardMostMove);
8014 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8015 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8016 static char resdet[MSG_SIZ];
8017 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8019 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8021 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8022 break; // Abort repetition-checking loop.
8024 // if neither of us is checking or chasing all the time, or both are, it is draw
8026 if(engineOpponent) {
8027 SendToProgram("force\n", engineOpponent); // suppress reply
8028 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8030 GameEnds( result, details, GE_XBOARD );
8033 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8034 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8038 /* Now we test for 50-move draws. Determine ply count */
8039 count = forwardMostMove;
8040 /* look for last irreversble move */
8041 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8043 /* if we hit starting position, add initial plies */
8044 if( count == backwardMostMove )
8045 count -= initialRulePlies;
8046 count = forwardMostMove - count;
8047 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8048 // adjust reversible move counter for checks in Xiangqi
8049 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8050 if(i < backwardMostMove) i = backwardMostMove;
8051 while(i <= forwardMostMove) {
8052 lastCheck = inCheck; // check evasion does not count
8053 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8054 if(inCheck || lastCheck) count--; // check does not count
8059 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8060 /* this is used to judge if draw claims are legal */
8061 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8062 if(engineOpponent) {
8063 SendToProgram("force\n", engineOpponent); // suppress reply
8064 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8066 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8070 /* if draw offer is pending, treat it as a draw claim
8071 * when draw condition present, to allow engines a way to
8072 * claim draws before making their move to avoid a race
8073 * condition occurring after their move
8075 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8077 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8078 p = "Draw claim: 50-move rule";
8079 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8080 p = "Draw claim: 3-fold repetition";
8081 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8082 p = "Draw claim: insufficient mating material";
8083 if( p != NULL && canAdjudicate) {
8084 if(engineOpponent) {
8085 SendToProgram("force\n", engineOpponent); // suppress reply
8086 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8088 GameEnds( GameIsDrawn, p, GE_XBOARD );
8093 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8094 if(engineOpponent) {
8095 SendToProgram("force\n", engineOpponent); // suppress reply
8096 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8098 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8105 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8106 { // [HGM] book: this routine intercepts moves to simulate book replies
8107 char *bookHit = NULL;
8109 //first determine if the incoming move brings opponent into his book
8110 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8111 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8112 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8113 if(bookHit != NULL && !cps->bookSuspend) {
8114 // make sure opponent is not going to reply after receiving move to book position
8115 SendToProgram("force\n", cps);
8116 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8118 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8119 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8120 // now arrange restart after book miss
8122 // after a book hit we never send 'go', and the code after the call to this routine
8123 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8124 char buf[MSG_SIZ], *move = bookHit;
8126 int fromX, fromY, toX, toY;
8130 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8131 &fromX, &fromY, &toX, &toY, &promoChar)) {
8132 (void) CoordsToAlgebraic(boards[forwardMostMove],
8133 PosFlags(forwardMostMove),
8134 fromY, fromX, toY, toX, promoChar, move);
8136 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8140 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8141 SendToProgram(buf, cps);
8142 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8143 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8144 SendToProgram("go\n", cps);
8145 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8146 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8147 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8148 SendToProgram("go\n", cps);
8149 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8151 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8155 LoadError (char *errmess, ChessProgramState *cps)
8156 { // unloads engine and switches back to -ncp mode if it was first
8157 if(cps->initDone) return FALSE;
8158 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8159 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8162 appData.noChessProgram = TRUE;
8163 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8164 gameMode = BeginningOfGame; ModeHighlight();
8167 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8168 DisplayMessage("", ""); // erase waiting message
8169 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8174 ChessProgramState *savedState;
8176 DeferredBookMove (void)
8178 if(savedState->lastPing != savedState->lastPong)
8179 ScheduleDelayedEvent(DeferredBookMove, 10);
8181 HandleMachineMove(savedMessage, savedState);
8184 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8185 static ChessProgramState *stalledEngine;
8186 static char stashedInputMove[MSG_SIZ];
8189 HandleMachineMove (char *message, ChessProgramState *cps)
8191 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8192 char realname[MSG_SIZ];
8193 int fromX, fromY, toX, toY;
8197 int machineWhite, oldError;
8200 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8201 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8202 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8203 DisplayError(_("Invalid pairing from pairing engine"), 0);
8206 pairingReceived = 1;
8208 return; // Skim the pairing messages here.
8211 oldError = cps->userError; cps->userError = 0;
8213 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8215 * Kludge to ignore BEL characters
8217 while (*message == '\007') message++;
8220 * [HGM] engine debug message: ignore lines starting with '#' character
8222 if(cps->debug && *message == '#') return;
8225 * Look for book output
8227 if (cps == &first && bookRequested) {
8228 if (message[0] == '\t' || message[0] == ' ') {
8229 /* Part of the book output is here; append it */
8230 strcat(bookOutput, message);
8231 strcat(bookOutput, " \n");
8233 } else if (bookOutput[0] != NULLCHAR) {
8234 /* All of book output has arrived; display it */
8235 char *p = bookOutput;
8236 while (*p != NULLCHAR) {
8237 if (*p == '\t') *p = ' ';
8240 DisplayInformation(bookOutput);
8241 bookRequested = FALSE;
8242 /* Fall through to parse the current output */
8247 * Look for machine move.
8249 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8250 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8252 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8253 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8254 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8255 stalledEngine = cps;
8256 if(appData.ponderNextMove) { // bring opponent out of ponder
8257 if(gameMode == TwoMachinesPlay) {
8258 if(cps->other->pause)
8259 PauseEngine(cps->other);
8261 SendToProgram("easy\n", cps->other);
8268 /* This method is only useful on engines that support ping */
8269 if (cps->lastPing != cps->lastPong) {
8270 if (gameMode == BeginningOfGame) {
8271 /* Extra move from before last new; ignore */
8272 if (appData.debugMode) {
8273 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8276 if (appData.debugMode) {
8277 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8278 cps->which, gameMode);
8281 SendToProgram("undo\n", cps);
8287 case BeginningOfGame:
8288 /* Extra move from before last reset; ignore */
8289 if (appData.debugMode) {
8290 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8297 /* Extra move after we tried to stop. The mode test is
8298 not a reliable way of detecting this problem, but it's
8299 the best we can do on engines that don't support ping.
8301 if (appData.debugMode) {
8302 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8303 cps->which, gameMode);
8305 SendToProgram("undo\n", cps);
8308 case MachinePlaysWhite:
8309 case IcsPlayingWhite:
8310 machineWhite = TRUE;
8313 case MachinePlaysBlack:
8314 case IcsPlayingBlack:
8315 machineWhite = FALSE;
8318 case TwoMachinesPlay:
8319 machineWhite = (cps->twoMachinesColor[0] == 'w');
8322 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8323 if (appData.debugMode) {
8325 "Ignoring move out of turn by %s, gameMode %d"
8326 ", forwardMost %d\n",
8327 cps->which, gameMode, forwardMostMove);
8332 if(cps->alphaRank) AlphaRank(machineMove, 4);
8333 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8334 &fromX, &fromY, &toX, &toY, &promoChar)) {
8335 /* Machine move could not be parsed; ignore it. */
8336 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8337 machineMove, _(cps->which));
8338 DisplayMoveError(buf1);
8339 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8340 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8341 if (gameMode == TwoMachinesPlay) {
8342 GameEnds(machineWhite ? BlackWins : WhiteWins,
8348 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8349 /* So we have to redo legality test with true e.p. status here, */
8350 /* to make sure an illegal e.p. capture does not slip through, */
8351 /* to cause a forfeit on a justified illegal-move complaint */
8352 /* of the opponent. */
8353 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8355 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8356 fromY, fromX, toY, toX, promoChar);
8357 if(moveType == IllegalMove) {
8358 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8359 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8360 GameEnds(machineWhite ? BlackWins : WhiteWins,
8363 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8364 /* [HGM] Kludge to handle engines that send FRC-style castling
8365 when they shouldn't (like TSCP-Gothic) */
8367 case WhiteASideCastleFR:
8368 case BlackASideCastleFR:
8370 currentMoveString[2]++;
8372 case WhiteHSideCastleFR:
8373 case BlackHSideCastleFR:
8375 currentMoveString[2]--;
8377 default: ; // nothing to do, but suppresses warning of pedantic compilers
8380 hintRequested = FALSE;
8381 lastHint[0] = NULLCHAR;
8382 bookRequested = FALSE;
8383 /* Program may be pondering now */
8384 cps->maybeThinking = TRUE;
8385 if (cps->sendTime == 2) cps->sendTime = 1;
8386 if (cps->offeredDraw) cps->offeredDraw--;
8388 /* [AS] Save move info*/
8389 pvInfoList[ forwardMostMove ].score = programStats.score;
8390 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8391 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8393 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8395 /* Test suites abort the 'game' after one move */
8396 if(*appData.finger) {
8398 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8399 if(!f) f = fopen(appData.finger, "w");
8400 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8401 else { DisplayFatalError("Bad output file", errno, 0); return; }
8403 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8406 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8407 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8410 while( count < adjudicateLossPlies ) {
8411 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8414 score = -score; /* Flip score for winning side */
8417 if( score > adjudicateLossThreshold ) {
8424 if( count >= adjudicateLossPlies ) {
8425 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8427 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8428 "Xboard adjudication",
8435 if(Adjudicate(cps)) {
8436 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8437 return; // [HGM] adjudicate: for all automatic game ends
8441 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8443 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8444 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8446 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8448 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8450 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8451 char buf[3*MSG_SIZ];
8453 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8454 programStats.score / 100.,
8456 programStats.time / 100.,
8457 (unsigned int)programStats.nodes,
8458 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8459 programStats.movelist);
8461 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8466 /* [AS] Clear stats for next move */
8467 ClearProgramStats();
8468 thinkOutput[0] = NULLCHAR;
8469 hiddenThinkOutputState = 0;
8472 if (gameMode == TwoMachinesPlay) {
8473 /* [HGM] relaying draw offers moved to after reception of move */
8474 /* and interpreting offer as claim if it brings draw condition */
8475 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8476 SendToProgram("draw\n", cps->other);
8478 if (cps->other->sendTime) {
8479 SendTimeRemaining(cps->other,
8480 cps->other->twoMachinesColor[0] == 'w');
8482 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8483 if (firstMove && !bookHit) {
8485 if (cps->other->useColors) {
8486 SendToProgram(cps->other->twoMachinesColor, cps->other);
8488 SendToProgram("go\n", cps->other);
8490 cps->other->maybeThinking = TRUE;
8493 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8495 if (!pausing && appData.ringBellAfterMoves) {
8500 * Reenable menu items that were disabled while
8501 * machine was thinking
8503 if (gameMode != TwoMachinesPlay)
8504 SetUserThinkingEnables();
8506 // [HGM] book: after book hit opponent has received move and is now in force mode
8507 // force the book reply into it, and then fake that it outputted this move by jumping
8508 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8510 static char bookMove[MSG_SIZ]; // a bit generous?
8512 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8513 strcat(bookMove, bookHit);
8516 programStats.nodes = programStats.depth = programStats.time =
8517 programStats.score = programStats.got_only_move = 0;
8518 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8520 if(cps->lastPing != cps->lastPong) {
8521 savedMessage = message; // args for deferred call
8523 ScheduleDelayedEvent(DeferredBookMove, 10);
8532 /* Set special modes for chess engines. Later something general
8533 * could be added here; for now there is just one kludge feature,
8534 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8535 * when "xboard" is given as an interactive command.
8537 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8538 cps->useSigint = FALSE;
8539 cps->useSigterm = FALSE;
8541 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8542 ParseFeatures(message+8, cps);
8543 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8546 if (!strncmp(message, "setup ", 6) &&
8547 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8548 ) { // [HGM] allow first engine to define opening position
8549 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8550 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8552 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8553 if(startedFromSetupPosition) return;
8554 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8556 while(message[s] && message[s++] != ' ');
8557 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8558 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8559 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8560 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8561 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8562 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8565 ParseFEN(boards[0], &dummy, message+s);
8566 DrawPosition(TRUE, boards[0]);
8567 startedFromSetupPosition = TRUE;
8570 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8571 * want this, I was asked to put it in, and obliged.
8573 if (!strncmp(message, "setboard ", 9)) {
8574 Board initial_position;
8576 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8578 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8579 DisplayError(_("Bad FEN received from engine"), 0);
8583 CopyBoard(boards[0], initial_position);
8584 initialRulePlies = FENrulePlies;
8585 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8586 else gameMode = MachinePlaysBlack;
8587 DrawPosition(FALSE, boards[currentMove]);
8593 * Look for communication commands
8595 if (!strncmp(message, "telluser ", 9)) {
8596 if(message[9] == '\\' && message[10] == '\\')
8597 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8599 DisplayNote(message + 9);
8602 if (!strncmp(message, "tellusererror ", 14)) {
8604 if(message[14] == '\\' && message[15] == '\\')
8605 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8607 DisplayError(message + 14, 0);
8610 if (!strncmp(message, "tellopponent ", 13)) {
8611 if (appData.icsActive) {
8613 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8617 DisplayNote(message + 13);
8621 if (!strncmp(message, "tellothers ", 11)) {
8622 if (appData.icsActive) {
8624 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8627 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8630 if (!strncmp(message, "tellall ", 8)) {
8631 if (appData.icsActive) {
8633 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8637 DisplayNote(message + 8);
8641 if (strncmp(message, "warning", 7) == 0) {
8642 /* Undocumented feature, use tellusererror in new code */
8643 DisplayError(message, 0);
8646 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8647 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8648 strcat(realname, " query");
8649 AskQuestion(realname, buf2, buf1, cps->pr);
8652 /* Commands from the engine directly to ICS. We don't allow these to be
8653 * sent until we are logged on. Crafty kibitzes have been known to
8654 * interfere with the login process.
8657 if (!strncmp(message, "tellics ", 8)) {
8658 SendToICS(message + 8);
8662 if (!strncmp(message, "tellicsnoalias ", 15)) {
8663 SendToICS(ics_prefix);
8664 SendToICS(message + 15);
8668 /* The following are for backward compatibility only */
8669 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8670 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8671 SendToICS(ics_prefix);
8677 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8680 if(!strncmp(message, "highlight ", 10)) {
8681 if(appData.testLegality && appData.markers) return;
8682 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8685 if(!strncmp(message, "click ", 6)) {
8686 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8687 if(appData.testLegality || !appData.oneClick) return;
8688 sscanf(message+6, "%c%d%c", &f, &y, &c);
8689 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8690 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8691 x = x*squareSize + (x+1)*lineGap + squareSize/2;
8692 y = y*squareSize + (y+1)*lineGap + squareSize/2;
8693 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8694 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8695 LeftClick(Release, lastLeftX, lastLeftY);
8696 controlKey = (c == ',');
8697 LeftClick(Press, x, y);
8698 LeftClick(Release, x, y);
8699 first.highlight = f;
8703 * If the move is illegal, cancel it and redraw the board.
8704 * Also deal with other error cases. Matching is rather loose
8705 * here to accommodate engines written before the spec.
8707 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8708 strncmp(message, "Error", 5) == 0) {
8709 if (StrStr(message, "name") ||
8710 StrStr(message, "rating") || StrStr(message, "?") ||
8711 StrStr(message, "result") || StrStr(message, "board") ||
8712 StrStr(message, "bk") || StrStr(message, "computer") ||
8713 StrStr(message, "variant") || StrStr(message, "hint") ||
8714 StrStr(message, "random") || StrStr(message, "depth") ||
8715 StrStr(message, "accepted")) {
8718 if (StrStr(message, "protover")) {
8719 /* Program is responding to input, so it's apparently done
8720 initializing, and this error message indicates it is
8721 protocol version 1. So we don't need to wait any longer
8722 for it to initialize and send feature commands. */
8723 FeatureDone(cps, 1);
8724 cps->protocolVersion = 1;
8727 cps->maybeThinking = FALSE;
8729 if (StrStr(message, "draw")) {
8730 /* Program doesn't have "draw" command */
8731 cps->sendDrawOffers = 0;
8734 if (cps->sendTime != 1 &&
8735 (StrStr(message, "time") || StrStr(message, "otim"))) {
8736 /* Program apparently doesn't have "time" or "otim" command */
8740 if (StrStr(message, "analyze")) {
8741 cps->analysisSupport = FALSE;
8742 cps->analyzing = FALSE;
8743 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8744 EditGameEvent(); // [HGM] try to preserve loaded game
8745 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8746 DisplayError(buf2, 0);
8749 if (StrStr(message, "(no matching move)st")) {
8750 /* Special kludge for GNU Chess 4 only */
8751 cps->stKludge = TRUE;
8752 SendTimeControl(cps, movesPerSession, timeControl,
8753 timeIncrement, appData.searchDepth,
8757 if (StrStr(message, "(no matching move)sd")) {
8758 /* Special kludge for GNU Chess 4 only */
8759 cps->sdKludge = TRUE;
8760 SendTimeControl(cps, movesPerSession, timeControl,
8761 timeIncrement, appData.searchDepth,
8765 if (!StrStr(message, "llegal")) {
8768 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8769 gameMode == IcsIdle) return;
8770 if (forwardMostMove <= backwardMostMove) return;
8771 if (pausing) PauseEvent();
8772 if(appData.forceIllegal) {
8773 // [HGM] illegal: machine refused move; force position after move into it
8774 SendToProgram("force\n", cps);
8775 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8776 // we have a real problem now, as SendBoard will use the a2a3 kludge
8777 // when black is to move, while there might be nothing on a2 or black
8778 // might already have the move. So send the board as if white has the move.
8779 // But first we must change the stm of the engine, as it refused the last move
8780 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8781 if(WhiteOnMove(forwardMostMove)) {
8782 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8783 SendBoard(cps, forwardMostMove); // kludgeless board
8785 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8786 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8787 SendBoard(cps, forwardMostMove+1); // kludgeless board
8789 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8790 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8791 gameMode == TwoMachinesPlay)
8792 SendToProgram("go\n", cps);
8795 if (gameMode == PlayFromGameFile) {
8796 /* Stop reading this game file */
8797 gameMode = EditGame;
8800 /* [HGM] illegal-move claim should forfeit game when Xboard */
8801 /* only passes fully legal moves */
8802 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8803 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8804 "False illegal-move claim", GE_XBOARD );
8805 return; // do not take back move we tested as valid
8807 currentMove = forwardMostMove-1;
8808 DisplayMove(currentMove-1); /* before DisplayMoveError */
8809 SwitchClocks(forwardMostMove-1); // [HGM] race
8810 DisplayBothClocks();
8811 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8812 parseList[currentMove], _(cps->which));
8813 DisplayMoveError(buf1);
8814 DrawPosition(FALSE, boards[currentMove]);
8816 SetUserThinkingEnables();
8819 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8820 /* Program has a broken "time" command that
8821 outputs a string not ending in newline.
8827 * If chess program startup fails, exit with an error message.
8828 * Attempts to recover here are futile. [HGM] Well, we try anyway
8830 if ((StrStr(message, "unknown host") != NULL)
8831 || (StrStr(message, "No remote directory") != NULL)
8832 || (StrStr(message, "not found") != NULL)
8833 || (StrStr(message, "No such file") != NULL)
8834 || (StrStr(message, "can't alloc") != NULL)
8835 || (StrStr(message, "Permission denied") != NULL)) {
8837 cps->maybeThinking = FALSE;
8838 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8839 _(cps->which), cps->program, cps->host, message);
8840 RemoveInputSource(cps->isr);
8841 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8842 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8843 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8849 * Look for hint output
8851 if (sscanf(message, "Hint: %s", buf1) == 1) {
8852 if (cps == &first && hintRequested) {
8853 hintRequested = FALSE;
8854 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8855 &fromX, &fromY, &toX, &toY, &promoChar)) {
8856 (void) CoordsToAlgebraic(boards[forwardMostMove],
8857 PosFlags(forwardMostMove),
8858 fromY, fromX, toY, toX, promoChar, buf1);
8859 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8860 DisplayInformation(buf2);
8862 /* Hint move could not be parsed!? */
8863 snprintf(buf2, sizeof(buf2),
8864 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8865 buf1, _(cps->which));
8866 DisplayError(buf2, 0);
8869 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8875 * Ignore other messages if game is not in progress
8877 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8878 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8881 * look for win, lose, draw, or draw offer
8883 if (strncmp(message, "1-0", 3) == 0) {
8884 char *p, *q, *r = "";
8885 p = strchr(message, '{');
8893 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8895 } else if (strncmp(message, "0-1", 3) == 0) {
8896 char *p, *q, *r = "";
8897 p = strchr(message, '{');
8905 /* Kludge for Arasan 4.1 bug */
8906 if (strcmp(r, "Black resigns") == 0) {
8907 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8910 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8912 } else if (strncmp(message, "1/2", 3) == 0) {
8913 char *p, *q, *r = "";
8914 p = strchr(message, '{');
8923 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8926 } else if (strncmp(message, "White resign", 12) == 0) {
8927 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8929 } else if (strncmp(message, "Black resign", 12) == 0) {
8930 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8932 } else if (strncmp(message, "White matches", 13) == 0 ||
8933 strncmp(message, "Black matches", 13) == 0 ) {
8934 /* [HGM] ignore GNUShogi noises */
8936 } else if (strncmp(message, "White", 5) == 0 &&
8937 message[5] != '(' &&
8938 StrStr(message, "Black") == NULL) {
8939 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8941 } else if (strncmp(message, "Black", 5) == 0 &&
8942 message[5] != '(') {
8943 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8945 } else if (strcmp(message, "resign") == 0 ||
8946 strcmp(message, "computer resigns") == 0) {
8948 case MachinePlaysBlack:
8949 case IcsPlayingBlack:
8950 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8952 case MachinePlaysWhite:
8953 case IcsPlayingWhite:
8954 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8956 case TwoMachinesPlay:
8957 if (cps->twoMachinesColor[0] == 'w')
8958 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8960 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8967 } else if (strncmp(message, "opponent mates", 14) == 0) {
8969 case MachinePlaysBlack:
8970 case IcsPlayingBlack:
8971 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8973 case MachinePlaysWhite:
8974 case IcsPlayingWhite:
8975 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8977 case TwoMachinesPlay:
8978 if (cps->twoMachinesColor[0] == 'w')
8979 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8981 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8988 } else if (strncmp(message, "computer mates", 14) == 0) {
8990 case MachinePlaysBlack:
8991 case IcsPlayingBlack:
8992 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8994 case MachinePlaysWhite:
8995 case IcsPlayingWhite:
8996 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8998 case TwoMachinesPlay:
8999 if (cps->twoMachinesColor[0] == 'w')
9000 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9002 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9009 } else if (strncmp(message, "checkmate", 9) == 0) {
9010 if (WhiteOnMove(forwardMostMove)) {
9011 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9013 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9016 } else if (strstr(message, "Draw") != NULL ||
9017 strstr(message, "game is a draw") != NULL) {
9018 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9020 } else if (strstr(message, "offer") != NULL &&
9021 strstr(message, "draw") != NULL) {
9023 if (appData.zippyPlay && first.initDone) {
9024 /* Relay offer to ICS */
9025 SendToICS(ics_prefix);
9026 SendToICS("draw\n");
9029 cps->offeredDraw = 2; /* valid until this engine moves twice */
9030 if (gameMode == TwoMachinesPlay) {
9031 if (cps->other->offeredDraw) {
9032 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9033 /* [HGM] in two-machine mode we delay relaying draw offer */
9034 /* until after we also have move, to see if it is really claim */
9036 } else if (gameMode == MachinePlaysWhite ||
9037 gameMode == MachinePlaysBlack) {
9038 if (userOfferedDraw) {
9039 DisplayInformation(_("Machine accepts your draw offer"));
9040 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9042 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
9049 * Look for thinking output
9051 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9052 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9054 int plylev, mvleft, mvtot, curscore, time;
9055 char mvname[MOVE_LEN];
9059 int prefixHint = FALSE;
9060 mvname[0] = NULLCHAR;
9063 case MachinePlaysBlack:
9064 case IcsPlayingBlack:
9065 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9067 case MachinePlaysWhite:
9068 case IcsPlayingWhite:
9069 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9074 case IcsObserving: /* [DM] icsEngineAnalyze */
9075 if (!appData.icsEngineAnalyze) ignore = TRUE;
9077 case TwoMachinesPlay:
9078 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9088 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9090 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9091 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9093 if (plyext != ' ' && plyext != '\t') {
9097 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9098 if( cps->scoreIsAbsolute &&
9099 ( gameMode == MachinePlaysBlack ||
9100 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9101 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9102 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9103 !WhiteOnMove(currentMove)
9106 curscore = -curscore;
9109 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9111 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9114 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9115 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9116 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9117 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9118 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9119 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9121 } else DisplayError(_("failed writing PV"), 0);
9124 tempStats.depth = plylev;
9125 tempStats.nodes = nodes;
9126 tempStats.time = time;
9127 tempStats.score = curscore;
9128 tempStats.got_only_move = 0;
9130 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9133 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9134 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9135 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9136 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9137 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9138 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9139 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9140 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9143 /* Buffer overflow protection */
9144 if (pv[0] != NULLCHAR) {
9145 if (strlen(pv) >= sizeof(tempStats.movelist)
9146 && appData.debugMode) {
9148 "PV is too long; using the first %u bytes.\n",
9149 (unsigned) sizeof(tempStats.movelist) - 1);
9152 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9154 sprintf(tempStats.movelist, " no PV\n");
9157 if (tempStats.seen_stat) {
9158 tempStats.ok_to_send = 1;
9161 if (strchr(tempStats.movelist, '(') != NULL) {
9162 tempStats.line_is_book = 1;
9163 tempStats.nr_moves = 0;
9164 tempStats.moves_left = 0;
9166 tempStats.line_is_book = 0;
9169 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9170 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9172 SendProgramStatsToFrontend( cps, &tempStats );
9175 [AS] Protect the thinkOutput buffer from overflow... this
9176 is only useful if buf1 hasn't overflowed first!
9178 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9180 (gameMode == TwoMachinesPlay ?
9181 ToUpper(cps->twoMachinesColor[0]) : ' '),
9182 ((double) curscore) / 100.0,
9183 prefixHint ? lastHint : "",
9184 prefixHint ? " " : "" );
9186 if( buf1[0] != NULLCHAR ) {
9187 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9189 if( strlen(pv) > max_len ) {
9190 if( appData.debugMode) {
9191 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9193 pv[max_len+1] = '\0';
9196 strcat( thinkOutput, pv);
9199 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9200 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9201 DisplayMove(currentMove - 1);
9205 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9206 /* crafty (9.25+) says "(only move) <move>"
9207 * if there is only 1 legal move
9209 sscanf(p, "(only move) %s", buf1);
9210 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9211 sprintf(programStats.movelist, "%s (only move)", buf1);
9212 programStats.depth = 1;
9213 programStats.nr_moves = 1;
9214 programStats.moves_left = 1;
9215 programStats.nodes = 1;
9216 programStats.time = 1;
9217 programStats.got_only_move = 1;
9219 /* Not really, but we also use this member to
9220 mean "line isn't going to change" (Crafty
9221 isn't searching, so stats won't change) */
9222 programStats.line_is_book = 1;
9224 SendProgramStatsToFrontend( cps, &programStats );
9226 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9227 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9228 DisplayMove(currentMove - 1);
9231 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9232 &time, &nodes, &plylev, &mvleft,
9233 &mvtot, mvname) >= 5) {
9234 /* The stat01: line is from Crafty (9.29+) in response
9235 to the "." command */
9236 programStats.seen_stat = 1;
9237 cps->maybeThinking = TRUE;
9239 if (programStats.got_only_move || !appData.periodicUpdates)
9242 programStats.depth = plylev;
9243 programStats.time = time;
9244 programStats.nodes = nodes;
9245 programStats.moves_left = mvleft;
9246 programStats.nr_moves = mvtot;
9247 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9248 programStats.ok_to_send = 1;
9249 programStats.movelist[0] = '\0';
9251 SendProgramStatsToFrontend( cps, &programStats );
9255 } else if (strncmp(message,"++",2) == 0) {
9256 /* Crafty 9.29+ outputs this */
9257 programStats.got_fail = 2;
9260 } else if (strncmp(message,"--",2) == 0) {
9261 /* Crafty 9.29+ outputs this */
9262 programStats.got_fail = 1;
9265 } else if (thinkOutput[0] != NULLCHAR &&
9266 strncmp(message, " ", 4) == 0) {
9267 unsigned message_len;
9270 while (*p && *p == ' ') p++;
9272 message_len = strlen( p );
9274 /* [AS] Avoid buffer overflow */
9275 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9276 strcat(thinkOutput, " ");
9277 strcat(thinkOutput, p);
9280 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9281 strcat(programStats.movelist, " ");
9282 strcat(programStats.movelist, p);
9285 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9286 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9287 DisplayMove(currentMove - 1);
9295 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9296 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9298 ChessProgramStats cpstats;
9300 if (plyext != ' ' && plyext != '\t') {
9304 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9305 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9306 curscore = -curscore;
9309 cpstats.depth = plylev;
9310 cpstats.nodes = nodes;
9311 cpstats.time = time;
9312 cpstats.score = curscore;
9313 cpstats.got_only_move = 0;
9314 cpstats.movelist[0] = '\0';
9316 if (buf1[0] != NULLCHAR) {
9317 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9320 cpstats.ok_to_send = 0;
9321 cpstats.line_is_book = 0;
9322 cpstats.nr_moves = 0;
9323 cpstats.moves_left = 0;
9325 SendProgramStatsToFrontend( cps, &cpstats );
9332 /* Parse a game score from the character string "game", and
9333 record it as the history of the current game. The game
9334 score is NOT assumed to start from the standard position.
9335 The display is not updated in any way.
9338 ParseGameHistory (char *game)
9341 int fromX, fromY, toX, toY, boardIndex;
9346 if (appData.debugMode)
9347 fprintf(debugFP, "Parsing game history: %s\n", game);
9349 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9350 gameInfo.site = StrSave(appData.icsHost);
9351 gameInfo.date = PGNDate();
9352 gameInfo.round = StrSave("-");
9354 /* Parse out names of players */
9355 while (*game == ' ') game++;
9357 while (*game != ' ') *p++ = *game++;
9359 gameInfo.white = StrSave(buf);
9360 while (*game == ' ') game++;
9362 while (*game != ' ' && *game != '\n') *p++ = *game++;
9364 gameInfo.black = StrSave(buf);
9367 boardIndex = blackPlaysFirst ? 1 : 0;
9370 yyboardindex = boardIndex;
9371 moveType = (ChessMove) Myylex();
9373 case IllegalMove: /* maybe suicide chess, etc. */
9374 if (appData.debugMode) {
9375 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9376 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9377 setbuf(debugFP, NULL);
9379 case WhitePromotion:
9380 case BlackPromotion:
9381 case WhiteNonPromotion:
9382 case BlackNonPromotion:
9384 case WhiteCapturesEnPassant:
9385 case BlackCapturesEnPassant:
9386 case WhiteKingSideCastle:
9387 case WhiteQueenSideCastle:
9388 case BlackKingSideCastle:
9389 case BlackQueenSideCastle:
9390 case WhiteKingSideCastleWild:
9391 case WhiteQueenSideCastleWild:
9392 case BlackKingSideCastleWild:
9393 case BlackQueenSideCastleWild:
9395 case WhiteHSideCastleFR:
9396 case WhiteASideCastleFR:
9397 case BlackHSideCastleFR:
9398 case BlackASideCastleFR:
9400 fromX = currentMoveString[0] - AAA;
9401 fromY = currentMoveString[1] - ONE;
9402 toX = currentMoveString[2] - AAA;
9403 toY = currentMoveString[3] - ONE;
9404 promoChar = currentMoveString[4];
9408 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9409 fromX = moveType == WhiteDrop ?
9410 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9411 (int) CharToPiece(ToLower(currentMoveString[0]));
9413 toX = currentMoveString[2] - AAA;
9414 toY = currentMoveString[3] - ONE;
9415 promoChar = NULLCHAR;
9419 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9420 if (appData.debugMode) {
9421 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9422 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9423 setbuf(debugFP, NULL);
9425 DisplayError(buf, 0);
9427 case ImpossibleMove:
9429 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9430 if (appData.debugMode) {
9431 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9432 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9433 setbuf(debugFP, NULL);
9435 DisplayError(buf, 0);
9438 if (boardIndex < backwardMostMove) {
9439 /* Oops, gap. How did that happen? */
9440 DisplayError(_("Gap in move list"), 0);
9443 backwardMostMove = blackPlaysFirst ? 1 : 0;
9444 if (boardIndex > forwardMostMove) {
9445 forwardMostMove = boardIndex;
9449 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9450 strcat(parseList[boardIndex-1], " ");
9451 strcat(parseList[boardIndex-1], yy_text);
9463 case GameUnfinished:
9464 if (gameMode == IcsExamining) {
9465 if (boardIndex < backwardMostMove) {
9466 /* Oops, gap. How did that happen? */
9469 backwardMostMove = blackPlaysFirst ? 1 : 0;
9472 gameInfo.result = moveType;
9473 p = strchr(yy_text, '{');
9474 if (p == NULL) p = strchr(yy_text, '(');
9477 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9479 q = strchr(p, *p == '{' ? '}' : ')');
9480 if (q != NULL) *q = NULLCHAR;
9483 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9484 gameInfo.resultDetails = StrSave(p);
9487 if (boardIndex >= forwardMostMove &&
9488 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9489 backwardMostMove = blackPlaysFirst ? 1 : 0;
9492 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9493 fromY, fromX, toY, toX, promoChar,
9494 parseList[boardIndex]);
9495 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9496 /* currentMoveString is set as a side-effect of yylex */
9497 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9498 strcat(moveList[boardIndex], "\n");
9500 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9501 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9507 if(gameInfo.variant != VariantShogi)
9508 strcat(parseList[boardIndex - 1], "+");
9512 strcat(parseList[boardIndex - 1], "#");
9519 /* Apply a move to the given board */
9521 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9523 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9524 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9526 /* [HGM] compute & store e.p. status and castling rights for new position */
9527 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9529 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9530 oldEP = (signed char)board[EP_STATUS];
9531 board[EP_STATUS] = EP_NONE;
9533 if (fromY == DROP_RANK) {
9535 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9536 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9539 piece = board[toY][toX] = (ChessSquare) fromX;
9543 if( board[toY][toX] != EmptySquare )
9544 board[EP_STATUS] = EP_CAPTURE;
9546 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9547 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9548 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9550 if( board[fromY][fromX] == WhitePawn ) {
9551 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9552 board[EP_STATUS] = EP_PAWN_MOVE;
9554 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9555 gameInfo.variant != VariantBerolina || toX < fromX)
9556 board[EP_STATUS] = toX | berolina;
9557 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9558 gameInfo.variant != VariantBerolina || toX > fromX)
9559 board[EP_STATUS] = toX;
9562 if( board[fromY][fromX] == BlackPawn ) {
9563 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9564 board[EP_STATUS] = EP_PAWN_MOVE;
9565 if( toY-fromY== -2) {
9566 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9567 gameInfo.variant != VariantBerolina || toX < fromX)
9568 board[EP_STATUS] = toX | berolina;
9569 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9570 gameInfo.variant != VariantBerolina || toX > fromX)
9571 board[EP_STATUS] = toX;
9575 for(i=0; i<nrCastlingRights; i++) {
9576 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9577 board[CASTLING][i] == toX && castlingRank[i] == toY
9578 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9581 if(gameInfo.variant == VariantSChess) { // update virginity
9582 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9583 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9584 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9585 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9588 if (fromX == toX && fromY == toY) return;
9590 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9591 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9592 if(gameInfo.variant == VariantKnightmate)
9593 king += (int) WhiteUnicorn - (int) WhiteKing;
9595 /* Code added by Tord: */
9596 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9597 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9598 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9599 board[fromY][fromX] = EmptySquare;
9600 board[toY][toX] = EmptySquare;
9601 if((toX > fromX) != (piece == WhiteRook)) {
9602 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9604 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9606 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9607 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9608 board[fromY][fromX] = EmptySquare;
9609 board[toY][toX] = EmptySquare;
9610 if((toX > fromX) != (piece == BlackRook)) {
9611 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9613 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9615 /* End of code added by Tord */
9617 } else if (board[fromY][fromX] == king
9618 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9619 && toY == fromY && toX > fromX+1) {
9620 board[fromY][fromX] = EmptySquare;
9621 board[toY][toX] = king;
9622 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9623 board[fromY][BOARD_RGHT-1] = EmptySquare;
9624 } else if (board[fromY][fromX] == king
9625 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9626 && toY == fromY && toX < fromX-1) {
9627 board[fromY][fromX] = EmptySquare;
9628 board[toY][toX] = king;
9629 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9630 board[fromY][BOARD_LEFT] = EmptySquare;
9631 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9632 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9633 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9635 /* white pawn promotion */
9636 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9637 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9638 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9639 board[fromY][fromX] = EmptySquare;
9640 } else if ((fromY >= BOARD_HEIGHT>>1)
9641 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9643 && gameInfo.variant != VariantXiangqi
9644 && gameInfo.variant != VariantBerolina
9645 && (board[fromY][fromX] == WhitePawn)
9646 && (board[toY][toX] == EmptySquare)) {
9647 board[fromY][fromX] = EmptySquare;
9648 board[toY][toX] = WhitePawn;
9649 captured = board[toY - 1][toX];
9650 board[toY - 1][toX] = EmptySquare;
9651 } else if ((fromY == BOARD_HEIGHT-4)
9653 && gameInfo.variant == VariantBerolina
9654 && (board[fromY][fromX] == WhitePawn)
9655 && (board[toY][toX] == EmptySquare)) {
9656 board[fromY][fromX] = EmptySquare;
9657 board[toY][toX] = WhitePawn;
9658 if(oldEP & EP_BEROLIN_A) {
9659 captured = board[fromY][fromX-1];
9660 board[fromY][fromX-1] = EmptySquare;
9661 }else{ captured = board[fromY][fromX+1];
9662 board[fromY][fromX+1] = EmptySquare;
9664 } else if (board[fromY][fromX] == king
9665 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9666 && toY == fromY && toX > fromX+1) {
9667 board[fromY][fromX] = EmptySquare;
9668 board[toY][toX] = king;
9669 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9670 board[fromY][BOARD_RGHT-1] = EmptySquare;
9671 } else if (board[fromY][fromX] == king
9672 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9673 && toY == fromY && toX < fromX-1) {
9674 board[fromY][fromX] = EmptySquare;
9675 board[toY][toX] = king;
9676 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9677 board[fromY][BOARD_LEFT] = EmptySquare;
9678 } else if (fromY == 7 && fromX == 3
9679 && board[fromY][fromX] == BlackKing
9680 && toY == 7 && toX == 5) {
9681 board[fromY][fromX] = EmptySquare;
9682 board[toY][toX] = BlackKing;
9683 board[fromY][7] = EmptySquare;
9684 board[toY][4] = BlackRook;
9685 } else if (fromY == 7 && fromX == 3
9686 && board[fromY][fromX] == BlackKing
9687 && toY == 7 && toX == 1) {
9688 board[fromY][fromX] = EmptySquare;
9689 board[toY][toX] = BlackKing;
9690 board[fromY][0] = EmptySquare;
9691 board[toY][2] = BlackRook;
9692 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9693 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9694 && toY < promoRank && promoChar
9696 /* black pawn promotion */
9697 board[toY][toX] = CharToPiece(ToLower(promoChar));
9698 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9699 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9700 board[fromY][fromX] = EmptySquare;
9701 } else if ((fromY < BOARD_HEIGHT>>1)
9702 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9704 && gameInfo.variant != VariantXiangqi
9705 && gameInfo.variant != VariantBerolina
9706 && (board[fromY][fromX] == BlackPawn)
9707 && (board[toY][toX] == EmptySquare)) {
9708 board[fromY][fromX] = EmptySquare;
9709 board[toY][toX] = BlackPawn;
9710 captured = board[toY + 1][toX];
9711 board[toY + 1][toX] = EmptySquare;
9712 } else if ((fromY == 3)
9714 && gameInfo.variant == VariantBerolina
9715 && (board[fromY][fromX] == BlackPawn)
9716 && (board[toY][toX] == EmptySquare)) {
9717 board[fromY][fromX] = EmptySquare;
9718 board[toY][toX] = BlackPawn;
9719 if(oldEP & EP_BEROLIN_A) {
9720 captured = board[fromY][fromX-1];
9721 board[fromY][fromX-1] = EmptySquare;
9722 }else{ captured = board[fromY][fromX+1];
9723 board[fromY][fromX+1] = EmptySquare;
9726 board[toY][toX] = board[fromY][fromX];
9727 board[fromY][fromX] = EmptySquare;
9731 if (gameInfo.holdingsWidth != 0) {
9733 /* !!A lot more code needs to be written to support holdings */
9734 /* [HGM] OK, so I have written it. Holdings are stored in the */
9735 /* penultimate board files, so they are automaticlly stored */
9736 /* in the game history. */
9737 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9738 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9739 /* Delete from holdings, by decreasing count */
9740 /* and erasing image if necessary */
9741 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9742 if(p < (int) BlackPawn) { /* white drop */
9743 p -= (int)WhitePawn;
9744 p = PieceToNumber((ChessSquare)p);
9745 if(p >= gameInfo.holdingsSize) p = 0;
9746 if(--board[p][BOARD_WIDTH-2] <= 0)
9747 board[p][BOARD_WIDTH-1] = EmptySquare;
9748 if((int)board[p][BOARD_WIDTH-2] < 0)
9749 board[p][BOARD_WIDTH-2] = 0;
9750 } else { /* black drop */
9751 p -= (int)BlackPawn;
9752 p = PieceToNumber((ChessSquare)p);
9753 if(p >= gameInfo.holdingsSize) p = 0;
9754 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9755 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9756 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9757 board[BOARD_HEIGHT-1-p][1] = 0;
9760 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9761 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9762 /* [HGM] holdings: Add to holdings, if holdings exist */
9763 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9764 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9765 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9768 if (p >= (int) BlackPawn) {
9769 p -= (int)BlackPawn;
9770 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9771 /* in Shogi restore piece to its original first */
9772 captured = (ChessSquare) (DEMOTED captured);
9775 p = PieceToNumber((ChessSquare)p);
9776 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9777 board[p][BOARD_WIDTH-2]++;
9778 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9780 p -= (int)WhitePawn;
9781 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9782 captured = (ChessSquare) (DEMOTED captured);
9785 p = PieceToNumber((ChessSquare)p);
9786 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9787 board[BOARD_HEIGHT-1-p][1]++;
9788 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9791 } else if (gameInfo.variant == VariantAtomic) {
9792 if (captured != EmptySquare) {
9794 for (y = toY-1; y <= toY+1; y++) {
9795 for (x = toX-1; x <= toX+1; x++) {
9796 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9797 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9798 board[y][x] = EmptySquare;
9802 board[toY][toX] = EmptySquare;
9805 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9806 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9808 if(promoChar == '+') {
9809 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9810 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9811 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9812 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9813 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9814 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9815 board[toY][toX] = newPiece;
9817 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9818 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9819 // [HGM] superchess: take promotion piece out of holdings
9820 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9821 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9822 if(!--board[k][BOARD_WIDTH-2])
9823 board[k][BOARD_WIDTH-1] = EmptySquare;
9825 if(!--board[BOARD_HEIGHT-1-k][1])
9826 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9832 /* Updates forwardMostMove */
9834 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9836 // forwardMostMove++; // [HGM] bare: moved downstream
9838 (void) CoordsToAlgebraic(boards[forwardMostMove],
9839 PosFlags(forwardMostMove),
9840 fromY, fromX, toY, toX, promoChar,
9841 parseList[forwardMostMove]);
9843 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9844 int timeLeft; static int lastLoadFlag=0; int king, piece;
9845 piece = boards[forwardMostMove][fromY][fromX];
9846 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9847 if(gameInfo.variant == VariantKnightmate)
9848 king += (int) WhiteUnicorn - (int) WhiteKing;
9849 if(forwardMostMove == 0) {
9850 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9851 fprintf(serverMoves, "%s;", UserName());
9852 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9853 fprintf(serverMoves, "%s;", second.tidy);
9854 fprintf(serverMoves, "%s;", first.tidy);
9855 if(gameMode == MachinePlaysWhite)
9856 fprintf(serverMoves, "%s;", UserName());
9857 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9858 fprintf(serverMoves, "%s;", second.tidy);
9859 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9860 lastLoadFlag = loadFlag;
9862 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9863 // print castling suffix
9864 if( toY == fromY && piece == king ) {
9866 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9868 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9871 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9872 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9873 boards[forwardMostMove][toY][toX] == EmptySquare
9874 && fromX != toX && fromY != toY)
9875 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9877 if(promoChar != NULLCHAR) {
9878 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9879 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9880 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9881 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9884 char buf[MOVE_LEN*2], *p; int len;
9885 fprintf(serverMoves, "/%d/%d",
9886 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9887 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9888 else timeLeft = blackTimeRemaining/1000;
9889 fprintf(serverMoves, "/%d", timeLeft);
9890 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9891 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9892 if(p = strchr(buf, '=')) *p = NULLCHAR;
9893 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9894 fprintf(serverMoves, "/%s", buf);
9896 fflush(serverMoves);
9899 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9900 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9903 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9904 if (commentList[forwardMostMove+1] != NULL) {
9905 free(commentList[forwardMostMove+1]);
9906 commentList[forwardMostMove+1] = NULL;
9908 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9909 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9910 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9911 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9912 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9913 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9914 adjustedClock = FALSE;
9915 gameInfo.result = GameUnfinished;
9916 if (gameInfo.resultDetails != NULL) {
9917 free(gameInfo.resultDetails);
9918 gameInfo.resultDetails = NULL;
9920 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9921 moveList[forwardMostMove - 1]);
9922 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9928 if(gameInfo.variant != VariantShogi)
9929 strcat(parseList[forwardMostMove - 1], "+");
9933 strcat(parseList[forwardMostMove - 1], "#");
9939 /* Updates currentMove if not pausing */
9941 ShowMove (int fromX, int fromY, int toX, int toY)
9943 int instant = (gameMode == PlayFromGameFile) ?
9944 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9945 if(appData.noGUI) return;
9946 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9948 if (forwardMostMove == currentMove + 1) {
9949 AnimateMove(boards[forwardMostMove - 1],
9950 fromX, fromY, toX, toY);
9953 currentMove = forwardMostMove;
9956 if (instant) return;
9958 DisplayMove(currentMove - 1);
9959 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9960 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9961 SetHighlights(fromX, fromY, toX, toY);
9964 DrawPosition(FALSE, boards[currentMove]);
9965 DisplayBothClocks();
9966 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9970 SendEgtPath (ChessProgramState *cps)
9971 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9972 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9974 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9977 char c, *q = name+1, *r, *s;
9979 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9980 while(*p && *p != ',') *q++ = *p++;
9982 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9983 strcmp(name, ",nalimov:") == 0 ) {
9984 // take nalimov path from the menu-changeable option first, if it is defined
9985 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9986 SendToProgram(buf,cps); // send egtbpath command for nalimov
9988 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9989 (s = StrStr(appData.egtFormats, name)) != NULL) {
9990 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9991 s = r = StrStr(s, ":") + 1; // beginning of path info
9992 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9993 c = *r; *r = 0; // temporarily null-terminate path info
9994 *--q = 0; // strip of trailig ':' from name
9995 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9997 SendToProgram(buf,cps); // send egtbpath command for this format
9999 if(*p == ',') p++; // read away comma to position for next format name
10004 NonStandardBoardSize ()
10006 /* [HGM] Awkward testing. Should really be a table */
10007 int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10008 if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10009 if( gameInfo.variant == VariantXiangqi )
10010 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10011 if( gameInfo.variant == VariantShogi )
10012 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10013 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10014 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10015 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10016 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10017 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10018 if( gameInfo.variant == VariantCourier )
10019 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10020 if( gameInfo.variant == VariantSuper )
10021 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10022 if( gameInfo.variant == VariantGreat )
10023 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10024 if( gameInfo.variant == VariantSChess )
10025 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10026 if( gameInfo.variant == VariantGrand )
10027 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10032 InitChessProgram (ChessProgramState *cps, int setup)
10033 /* setup needed to setup FRC opening position */
10035 char buf[MSG_SIZ], b[MSG_SIZ];
10036 if (appData.noChessProgram) return;
10037 hintRequested = FALSE;
10038 bookRequested = FALSE;
10040 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10041 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10042 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10043 if(cps->memSize) { /* [HGM] memory */
10044 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10045 SendToProgram(buf, cps);
10047 SendEgtPath(cps); /* [HGM] EGT */
10048 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10049 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10050 SendToProgram(buf, cps);
10053 SendToProgram(cps->initString, cps);
10054 if (gameInfo.variant != VariantNormal &&
10055 gameInfo.variant != VariantLoadable
10056 /* [HGM] also send variant if board size non-standard */
10057 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10059 char *v = VariantName(gameInfo.variant);
10060 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10061 /* [HGM] in protocol 1 we have to assume all variants valid */
10062 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10063 DisplayFatalError(buf, 0, 1);
10067 if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10068 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10069 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10070 /* [HGM] varsize: try first if this defiant size variant is specifically known */
10071 if(StrStr(cps->variants, b) == NULL) {
10072 // specific sized variant not known, check if general sizing allowed
10073 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10074 if(StrStr(cps->variants, "boardsize") == NULL) {
10075 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10076 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10077 DisplayFatalError(buf, 0, 1);
10080 /* [HGM] here we really should compare with the maximum supported board size */
10083 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10084 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10085 SendToProgram(buf, cps);
10087 currentlyInitializedVariant = gameInfo.variant;
10089 /* [HGM] send opening position in FRC to first engine */
10091 SendToProgram("force\n", cps);
10093 /* engine is now in force mode! Set flag to wake it up after first move. */
10094 setboardSpoiledMachineBlack = 1;
10097 if (cps->sendICS) {
10098 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10099 SendToProgram(buf, cps);
10101 cps->maybeThinking = FALSE;
10102 cps->offeredDraw = 0;
10103 if (!appData.icsActive) {
10104 SendTimeControl(cps, movesPerSession, timeControl,
10105 timeIncrement, appData.searchDepth,
10108 if (appData.showThinking
10109 // [HGM] thinking: four options require thinking output to be sent
10110 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10112 SendToProgram("post\n", cps);
10114 SendToProgram("hard\n", cps);
10115 if (!appData.ponderNextMove) {
10116 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10117 it without being sure what state we are in first. "hard"
10118 is not a toggle, so that one is OK.
10120 SendToProgram("easy\n", cps);
10122 if (cps->usePing) {
10123 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10124 SendToProgram(buf, cps);
10126 cps->initDone = TRUE;
10127 ClearEngineOutputPane(cps == &second);
10132 ResendOptions (ChessProgramState *cps)
10133 { // send the stored value of the options
10136 Option *opt = cps->option;
10137 for(i=0; i<cps->nrOptions; i++, opt++) {
10138 switch(opt->type) {
10142 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10145 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10148 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10154 SendToProgram(buf, cps);
10159 StartChessProgram (ChessProgramState *cps)
10164 if (appData.noChessProgram) return;
10165 cps->initDone = FALSE;
10167 if (strcmp(cps->host, "localhost") == 0) {
10168 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10169 } else if (*appData.remoteShell == NULLCHAR) {
10170 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10172 if (*appData.remoteUser == NULLCHAR) {
10173 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10176 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10177 cps->host, appData.remoteUser, cps->program);
10179 err = StartChildProcess(buf, "", &cps->pr);
10183 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10184 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10185 if(cps != &first) return;
10186 appData.noChessProgram = TRUE;
10189 // DisplayFatalError(buf, err, 1);
10190 // cps->pr = NoProc;
10191 // cps->isr = NULL;
10195 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10196 if (cps->protocolVersion > 1) {
10197 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10198 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10199 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10200 cps->comboCnt = 0; // and values of combo boxes
10202 SendToProgram(buf, cps);
10203 if(cps->reload) ResendOptions(cps);
10205 SendToProgram("xboard\n", cps);
10210 TwoMachinesEventIfReady P((void))
10212 static int curMess = 0;
10213 if (first.lastPing != first.lastPong) {
10214 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10215 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10218 if (second.lastPing != second.lastPong) {
10219 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10220 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10223 DisplayMessage("", ""); curMess = 0;
10224 TwoMachinesEvent();
10228 MakeName (char *template)
10232 static char buf[MSG_SIZ];
10236 clock = time((time_t *)NULL);
10237 tm = localtime(&clock);
10239 while(*p++ = *template++) if(p[-1] == '%') {
10240 switch(*template++) {
10241 case 0: *p = 0; return buf;
10242 case 'Y': i = tm->tm_year+1900; break;
10243 case 'y': i = tm->tm_year-100; break;
10244 case 'M': i = tm->tm_mon+1; break;
10245 case 'd': i = tm->tm_mday; break;
10246 case 'h': i = tm->tm_hour; break;
10247 case 'm': i = tm->tm_min; break;
10248 case 's': i = tm->tm_sec; break;
10251 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10257 CountPlayers (char *p)
10260 while(p = strchr(p, '\n')) p++, n++; // count participants
10265 WriteTourneyFile (char *results, FILE *f)
10266 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10267 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10268 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10269 // create a file with tournament description
10270 fprintf(f, "-participants {%s}\n", appData.participants);
10271 fprintf(f, "-seedBase %d\n", appData.seedBase);
10272 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10273 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10274 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10275 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10276 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10277 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10278 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10279 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10280 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10281 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10282 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10283 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10284 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10285 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10286 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10287 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10288 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10289 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10290 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10291 fprintf(f, "-smpCores %d\n", appData.smpCores);
10293 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10295 fprintf(f, "-mps %d\n", appData.movesPerSession);
10296 fprintf(f, "-tc %s\n", appData.timeControl);
10297 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10299 fprintf(f, "-results \"%s\"\n", results);
10304 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10307 Substitute (char *participants, int expunge)
10309 int i, changed, changes=0, nPlayers=0;
10310 char *p, *q, *r, buf[MSG_SIZ];
10311 if(participants == NULL) return;
10312 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10313 r = p = participants; q = appData.participants;
10314 while(*p && *p == *q) {
10315 if(*p == '\n') r = p+1, nPlayers++;
10318 if(*p) { // difference
10319 while(*p && *p++ != '\n');
10320 while(*q && *q++ != '\n');
10321 changed = nPlayers;
10322 changes = 1 + (strcmp(p, q) != 0);
10324 if(changes == 1) { // a single engine mnemonic was changed
10325 q = r; while(*q) nPlayers += (*q++ == '\n');
10326 p = buf; while(*r && (*p = *r++) != '\n') p++;
10328 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10329 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10330 if(mnemonic[i]) { // The substitute is valid
10332 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10333 flock(fileno(f), LOCK_EX);
10334 ParseArgsFromFile(f);
10335 fseek(f, 0, SEEK_SET);
10336 FREE(appData.participants); appData.participants = participants;
10337 if(expunge) { // erase results of replaced engine
10338 int len = strlen(appData.results), w, b, dummy;
10339 for(i=0; i<len; i++) {
10340 Pairing(i, nPlayers, &w, &b, &dummy);
10341 if((w == changed || b == changed) && appData.results[i] == '*') {
10342 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10347 for(i=0; i<len; i++) {
10348 Pairing(i, nPlayers, &w, &b, &dummy);
10349 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10352 WriteTourneyFile(appData.results, f);
10353 fclose(f); // release lock
10356 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10358 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10359 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10360 free(participants);
10365 CheckPlayers (char *participants)
10368 char buf[MSG_SIZ], *p;
10369 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10370 while(p = strchr(participants, '\n')) {
10372 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10374 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10376 DisplayError(buf, 0);
10380 participants = p + 1;
10386 CreateTourney (char *name)
10389 if(matchMode && strcmp(name, appData.tourneyFile)) {
10390 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10392 if(name[0] == NULLCHAR) {
10393 if(appData.participants[0])
10394 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10397 f = fopen(name, "r");
10398 if(f) { // file exists
10399 ASSIGN(appData.tourneyFile, name);
10400 ParseArgsFromFile(f); // parse it
10402 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10403 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10404 DisplayError(_("Not enough participants"), 0);
10407 if(CheckPlayers(appData.participants)) return 0;
10408 ASSIGN(appData.tourneyFile, name);
10409 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10410 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10413 appData.noChessProgram = FALSE;
10414 appData.clockMode = TRUE;
10420 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10422 char buf[MSG_SIZ], *p, *q;
10423 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10424 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10425 skip = !all && group[0]; // if group requested, we start in skip mode
10426 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10427 p = names; q = buf; header = 0;
10428 while(*p && *p != '\n') *q++ = *p++;
10430 if(*p == '\n') p++;
10431 if(buf[0] == '#') {
10432 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10433 depth++; // we must be entering a new group
10434 if(all) continue; // suppress printing group headers when complete list requested
10436 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10438 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10439 if(engineList[i]) free(engineList[i]);
10440 engineList[i] = strdup(buf);
10441 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10442 if(engineMnemonic[i]) free(engineMnemonic[i]);
10443 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10445 sscanf(q + 8, "%s", buf + strlen(buf));
10448 engineMnemonic[i] = strdup(buf);
10451 engineList[i] = engineMnemonic[i] = NULL;
10455 // following implemented as macro to avoid type limitations
10456 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10459 SwapEngines (int n)
10460 { // swap settings for first engine and other engine (so far only some selected options)
10465 SWAP(chessProgram, p)
10467 SWAP(hasOwnBookUCI, h)
10468 SWAP(protocolVersion, h)
10470 SWAP(scoreIsAbsolute, h)
10475 SWAP(engOptions, p)
10476 SWAP(engInitString, p)
10477 SWAP(computerString, p)
10479 SWAP(fenOverride, p)
10481 SWAP(accumulateTC, h)
10486 GetEngineLine (char *s, int n)
10490 extern char *icsNames;
10491 if(!s || !*s) return 0;
10492 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10493 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10494 if(!mnemonic[i]) return 0;
10495 if(n == 11) return 1; // just testing if there was a match
10496 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10497 if(n == 1) SwapEngines(n);
10498 ParseArgsFromString(buf);
10499 if(n == 1) SwapEngines(n);
10500 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10501 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10502 ParseArgsFromString(buf);
10508 SetPlayer (int player, char *p)
10509 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10511 char buf[MSG_SIZ], *engineName;
10512 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10513 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10514 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10516 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10517 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10518 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10519 ParseArgsFromString(buf);
10520 } else { // no engine with this nickname is installed!
10521 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10522 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10523 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10525 DisplayError(buf, 0);
10532 char *recentEngines;
10535 RecentEngineEvent (int nr)
10538 // SwapEngines(1); // bump first to second
10539 // ReplaceEngine(&second, 1); // and load it there
10540 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10541 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10542 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10543 ReplaceEngine(&first, 0);
10544 FloatToFront(&appData.recentEngineList, command[n]);
10549 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10550 { // determine players from game number
10551 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10553 if(appData.tourneyType == 0) {
10554 roundsPerCycle = (nPlayers - 1) | 1;
10555 pairingsPerRound = nPlayers / 2;
10556 } else if(appData.tourneyType > 0) {
10557 roundsPerCycle = nPlayers - appData.tourneyType;
10558 pairingsPerRound = appData.tourneyType;
10560 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10561 gamesPerCycle = gamesPerRound * roundsPerCycle;
10562 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10563 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10564 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10565 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10566 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10567 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10569 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10570 if(appData.roundSync) *syncInterval = gamesPerRound;
10572 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10574 if(appData.tourneyType == 0) {
10575 if(curPairing == (nPlayers-1)/2 ) {
10576 *whitePlayer = curRound;
10577 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10579 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10580 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10581 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10582 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10584 } else if(appData.tourneyType > 1) {
10585 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10586 *whitePlayer = curRound + appData.tourneyType;
10587 } else if(appData.tourneyType > 0) {
10588 *whitePlayer = curPairing;
10589 *blackPlayer = curRound + appData.tourneyType;
10592 // take care of white/black alternation per round.
10593 // For cycles and games this is already taken care of by default, derived from matchGame!
10594 return curRound & 1;
10598 NextTourneyGame (int nr, int *swapColors)
10599 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10601 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10603 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10604 tf = fopen(appData.tourneyFile, "r");
10605 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10606 ParseArgsFromFile(tf); fclose(tf);
10607 InitTimeControls(); // TC might be altered from tourney file
10609 nPlayers = CountPlayers(appData.participants); // count participants
10610 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10611 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10614 p = q = appData.results;
10615 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10616 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10617 DisplayMessage(_("Waiting for other game(s)"),"");
10618 waitingForGame = TRUE;
10619 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10622 waitingForGame = FALSE;
10625 if(appData.tourneyType < 0) {
10626 if(nr>=0 && !pairingReceived) {
10628 if(pairing.pr == NoProc) {
10629 if(!appData.pairingEngine[0]) {
10630 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10633 StartChessProgram(&pairing); // starts the pairing engine
10635 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10636 SendToProgram(buf, &pairing);
10637 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10638 SendToProgram(buf, &pairing);
10639 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10641 pairingReceived = 0; // ... so we continue here
10643 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10644 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10645 matchGame = 1; roundNr = nr / syncInterval + 1;
10648 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10650 // redefine engines, engine dir, etc.
10651 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10652 if(first.pr == NoProc) {
10653 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10654 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10656 if(second.pr == NoProc) {
10658 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10659 SwapEngines(1); // and make that valid for second engine by swapping
10660 InitEngine(&second, 1);
10662 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10663 UpdateLogos(FALSE); // leave display to ModeHiglight()
10669 { // performs game initialization that does not invoke engines, and then tries to start the game
10670 int res, firstWhite, swapColors = 0;
10671 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10672 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
10674 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10675 if(strcmp(buf, currentDebugFile)) { // name has changed
10676 FILE *f = fopen(buf, "w");
10677 if(f) { // if opening the new file failed, just keep using the old one
10678 ASSIGN(currentDebugFile, buf);
10682 if(appData.serverFileName) {
10683 if(serverFP) fclose(serverFP);
10684 serverFP = fopen(appData.serverFileName, "w");
10685 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10686 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10690 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10691 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10692 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10693 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10694 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10695 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10696 Reset(FALSE, first.pr != NoProc);
10697 res = LoadGameOrPosition(matchGame); // setup game
10698 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10699 if(!res) return; // abort when bad game/pos file
10700 TwoMachinesEvent();
10704 UserAdjudicationEvent (int result)
10706 ChessMove gameResult = GameIsDrawn;
10709 gameResult = WhiteWins;
10711 else if( result < 0 ) {
10712 gameResult = BlackWins;
10715 if( gameMode == TwoMachinesPlay ) {
10716 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10721 // [HGM] save: calculate checksum of game to make games easily identifiable
10723 StringCheckSum (char *s)
10726 if(s==NULL) return 0;
10727 while(*s) i = i*259 + *s++;
10735 for(i=backwardMostMove; i<forwardMostMove; i++) {
10736 sum += pvInfoList[i].depth;
10737 sum += StringCheckSum(parseList[i]);
10738 sum += StringCheckSum(commentList[i]);
10741 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10742 return sum + StringCheckSum(commentList[i]);
10743 } // end of save patch
10746 GameEnds (ChessMove result, char *resultDetails, int whosays)
10748 GameMode nextGameMode;
10750 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10752 if(endingGame) return; /* [HGM] crash: forbid recursion */
10754 if(twoBoards) { // [HGM] dual: switch back to one board
10755 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10756 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10758 if (appData.debugMode) {
10759 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10760 result, resultDetails ? resultDetails : "(null)", whosays);
10763 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10765 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10767 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10768 /* If we are playing on ICS, the server decides when the
10769 game is over, but the engine can offer to draw, claim
10773 if (appData.zippyPlay && first.initDone) {
10774 if (result == GameIsDrawn) {
10775 /* In case draw still needs to be claimed */
10776 SendToICS(ics_prefix);
10777 SendToICS("draw\n");
10778 } else if (StrCaseStr(resultDetails, "resign")) {
10779 SendToICS(ics_prefix);
10780 SendToICS("resign\n");
10784 endingGame = 0; /* [HGM] crash */
10788 /* If we're loading the game from a file, stop */
10789 if (whosays == GE_FILE) {
10790 (void) StopLoadGameTimer();
10794 /* Cancel draw offers */
10795 first.offeredDraw = second.offeredDraw = 0;
10797 /* If this is an ICS game, only ICS can really say it's done;
10798 if not, anyone can. */
10799 isIcsGame = (gameMode == IcsPlayingWhite ||
10800 gameMode == IcsPlayingBlack ||
10801 gameMode == IcsObserving ||
10802 gameMode == IcsExamining);
10804 if (!isIcsGame || whosays == GE_ICS) {
10805 /* OK -- not an ICS game, or ICS said it was done */
10807 if (!isIcsGame && !appData.noChessProgram)
10808 SetUserThinkingEnables();
10810 /* [HGM] if a machine claims the game end we verify this claim */
10811 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10812 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10814 ChessMove trueResult = (ChessMove) -1;
10816 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10817 first.twoMachinesColor[0] :
10818 second.twoMachinesColor[0] ;
10820 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10821 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10822 /* [HGM] verify: engine mate claims accepted if they were flagged */
10823 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10825 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10826 /* [HGM] verify: engine mate claims accepted if they were flagged */
10827 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10829 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10830 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10833 // now verify win claims, but not in drop games, as we don't understand those yet
10834 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10835 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10836 (result == WhiteWins && claimer == 'w' ||
10837 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10838 if (appData.debugMode) {
10839 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10840 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10842 if(result != trueResult) {
10843 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10844 result = claimer == 'w' ? BlackWins : WhiteWins;
10845 resultDetails = buf;
10848 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10849 && (forwardMostMove <= backwardMostMove ||
10850 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10851 (claimer=='b')==(forwardMostMove&1))
10853 /* [HGM] verify: draws that were not flagged are false claims */
10854 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10855 result = claimer == 'w' ? BlackWins : WhiteWins;
10856 resultDetails = buf;
10858 /* (Claiming a loss is accepted no questions asked!) */
10859 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10860 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10861 result = GameUnfinished;
10862 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10864 /* [HGM] bare: don't allow bare King to win */
10865 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10866 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10867 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10868 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10869 && result != GameIsDrawn)
10870 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10871 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10872 int p = (signed char)boards[forwardMostMove][i][j] - color;
10873 if(p >= 0 && p <= (int)WhiteKing) k++;
10875 if (appData.debugMode) {
10876 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10877 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10880 result = GameIsDrawn;
10881 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10882 resultDetails = buf;
10888 if(serverMoves != NULL && !loadFlag) { char c = '=';
10889 if(result==WhiteWins) c = '+';
10890 if(result==BlackWins) c = '-';
10891 if(resultDetails != NULL)
10892 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10894 if (resultDetails != NULL) {
10895 gameInfo.result = result;
10896 gameInfo.resultDetails = StrSave(resultDetails);
10898 /* display last move only if game was not loaded from file */
10899 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10900 DisplayMove(currentMove - 1);
10902 if (forwardMostMove != 0) {
10903 if (gameMode != PlayFromGameFile && gameMode != EditGame
10904 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10906 if (*appData.saveGameFile != NULLCHAR) {
10907 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10908 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10910 SaveGameToFile(appData.saveGameFile, TRUE);
10911 } else if (appData.autoSaveGames) {
10912 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10914 if (*appData.savePositionFile != NULLCHAR) {
10915 SavePositionToFile(appData.savePositionFile);
10917 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10921 /* Tell program how game ended in case it is learning */
10922 /* [HGM] Moved this to after saving the PGN, just in case */
10923 /* engine died and we got here through time loss. In that */
10924 /* case we will get a fatal error writing the pipe, which */
10925 /* would otherwise lose us the PGN. */
10926 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10927 /* output during GameEnds should never be fatal anymore */
10928 if (gameMode == MachinePlaysWhite ||
10929 gameMode == MachinePlaysBlack ||
10930 gameMode == TwoMachinesPlay ||
10931 gameMode == IcsPlayingWhite ||
10932 gameMode == IcsPlayingBlack ||
10933 gameMode == BeginningOfGame) {
10935 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10937 if (first.pr != NoProc) {
10938 SendToProgram(buf, &first);
10940 if (second.pr != NoProc &&
10941 gameMode == TwoMachinesPlay) {
10942 SendToProgram(buf, &second);
10947 if (appData.icsActive) {
10948 if (appData.quietPlay &&
10949 (gameMode == IcsPlayingWhite ||
10950 gameMode == IcsPlayingBlack)) {
10951 SendToICS(ics_prefix);
10952 SendToICS("set shout 1\n");
10954 nextGameMode = IcsIdle;
10955 ics_user_moved = FALSE;
10956 /* clean up premove. It's ugly when the game has ended and the
10957 * premove highlights are still on the board.
10960 gotPremove = FALSE;
10961 ClearPremoveHighlights();
10962 DrawPosition(FALSE, boards[currentMove]);
10964 if (whosays == GE_ICS) {
10967 if (gameMode == IcsPlayingWhite)
10969 else if(gameMode == IcsPlayingBlack)
10970 PlayIcsLossSound();
10973 if (gameMode == IcsPlayingBlack)
10975 else if(gameMode == IcsPlayingWhite)
10976 PlayIcsLossSound();
10979 PlayIcsDrawSound();
10982 PlayIcsUnfinishedSound();
10985 if(appData.quitNext) { ExitEvent(0); return; }
10986 } else if (gameMode == EditGame ||
10987 gameMode == PlayFromGameFile ||
10988 gameMode == AnalyzeMode ||
10989 gameMode == AnalyzeFile) {
10990 nextGameMode = gameMode;
10992 nextGameMode = EndOfGame;
10997 nextGameMode = gameMode;
11000 if (appData.noChessProgram) {
11001 gameMode = nextGameMode;
11003 endingGame = 0; /* [HGM] crash */
11008 /* Put first chess program into idle state */
11009 if (first.pr != NoProc &&
11010 (gameMode == MachinePlaysWhite ||
11011 gameMode == MachinePlaysBlack ||
11012 gameMode == TwoMachinesPlay ||
11013 gameMode == IcsPlayingWhite ||
11014 gameMode == IcsPlayingBlack ||
11015 gameMode == BeginningOfGame)) {
11016 SendToProgram("force\n", &first);
11017 if (first.usePing) {
11019 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11020 SendToProgram(buf, &first);
11023 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11024 /* Kill off first chess program */
11025 if (first.isr != NULL)
11026 RemoveInputSource(first.isr);
11029 if (first.pr != NoProc) {
11031 DoSleep( appData.delayBeforeQuit );
11032 SendToProgram("quit\n", &first);
11033 DoSleep( appData.delayAfterQuit );
11034 DestroyChildProcess(first.pr, first.useSigterm);
11035 first.reload = TRUE;
11039 if (second.reuse) {
11040 /* Put second chess program into idle state */
11041 if (second.pr != NoProc &&
11042 gameMode == TwoMachinesPlay) {
11043 SendToProgram("force\n", &second);
11044 if (second.usePing) {
11046 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11047 SendToProgram(buf, &second);
11050 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11051 /* Kill off second chess program */
11052 if (second.isr != NULL)
11053 RemoveInputSource(second.isr);
11056 if (second.pr != NoProc) {
11057 DoSleep( appData.delayBeforeQuit );
11058 SendToProgram("quit\n", &second);
11059 DoSleep( appData.delayAfterQuit );
11060 DestroyChildProcess(second.pr, second.useSigterm);
11061 second.reload = TRUE;
11063 second.pr = NoProc;
11066 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11067 char resChar = '=';
11071 if (first.twoMachinesColor[0] == 'w') {
11074 second.matchWins++;
11079 if (first.twoMachinesColor[0] == 'b') {
11082 second.matchWins++;
11085 case GameUnfinished:
11091 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11092 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11093 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11094 ReserveGame(nextGame, resChar); // sets nextGame
11095 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11096 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11097 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11099 if (nextGame <= appData.matchGames && !abortMatch) {
11100 gameMode = nextGameMode;
11101 matchGame = nextGame; // this will be overruled in tourney mode!
11102 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11103 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11104 endingGame = 0; /* [HGM] crash */
11107 gameMode = nextGameMode;
11108 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11109 first.tidy, second.tidy,
11110 first.matchWins, second.matchWins,
11111 appData.matchGames - (first.matchWins + second.matchWins));
11112 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11113 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11114 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11115 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11116 first.twoMachinesColor = "black\n";
11117 second.twoMachinesColor = "white\n";
11119 first.twoMachinesColor = "white\n";
11120 second.twoMachinesColor = "black\n";
11124 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11125 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11127 gameMode = nextGameMode;
11129 endingGame = 0; /* [HGM] crash */
11130 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11131 if(matchMode == TRUE) { // match through command line: exit with or without popup
11133 ToNrEvent(forwardMostMove);
11134 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11136 } else DisplayFatalError(buf, 0, 0);
11137 } else { // match through menu; just stop, with or without popup
11138 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11141 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11142 } else DisplayNote(buf);
11144 if(ranking) free(ranking);
11148 /* Assumes program was just initialized (initString sent).
11149 Leaves program in force mode. */
11151 FeedMovesToProgram (ChessProgramState *cps, int upto)
11155 if (appData.debugMode)
11156 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11157 startedFromSetupPosition ? "position and " : "",
11158 backwardMostMove, upto, cps->which);
11159 if(currentlyInitializedVariant != gameInfo.variant) {
11161 // [HGM] variantswitch: make engine aware of new variant
11162 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11163 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11164 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11165 SendToProgram(buf, cps);
11166 currentlyInitializedVariant = gameInfo.variant;
11168 SendToProgram("force\n", cps);
11169 if (startedFromSetupPosition) {
11170 SendBoard(cps, backwardMostMove);
11171 if (appData.debugMode) {
11172 fprintf(debugFP, "feedMoves\n");
11175 for (i = backwardMostMove; i < upto; i++) {
11176 SendMoveToProgram(i, cps);
11182 ResurrectChessProgram ()
11184 /* The chess program may have exited.
11185 If so, restart it and feed it all the moves made so far. */
11186 static int doInit = 0;
11188 if (appData.noChessProgram) return 1;
11190 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11191 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11192 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11193 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11195 if (first.pr != NoProc) return 1;
11196 StartChessProgram(&first);
11198 InitChessProgram(&first, FALSE);
11199 FeedMovesToProgram(&first, currentMove);
11201 if (!first.sendTime) {
11202 /* can't tell gnuchess what its clock should read,
11203 so we bow to its notion. */
11205 timeRemaining[0][currentMove] = whiteTimeRemaining;
11206 timeRemaining[1][currentMove] = blackTimeRemaining;
11209 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11210 appData.icsEngineAnalyze) && first.analysisSupport) {
11211 SendToProgram("analyze\n", &first);
11212 first.analyzing = TRUE;
11218 * Button procedures
11221 Reset (int redraw, int init)
11225 if (appData.debugMode) {
11226 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11227 redraw, init, gameMode);
11229 CleanupTail(); // [HGM] vari: delete any stored variations
11230 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11231 pausing = pauseExamInvalid = FALSE;
11232 startedFromSetupPosition = blackPlaysFirst = FALSE;
11234 whiteFlag = blackFlag = FALSE;
11235 userOfferedDraw = FALSE;
11236 hintRequested = bookRequested = FALSE;
11237 first.maybeThinking = FALSE;
11238 second.maybeThinking = FALSE;
11239 first.bookSuspend = FALSE; // [HGM] book
11240 second.bookSuspend = FALSE;
11241 thinkOutput[0] = NULLCHAR;
11242 lastHint[0] = NULLCHAR;
11243 ClearGameInfo(&gameInfo);
11244 gameInfo.variant = StringToVariant(appData.variant);
11245 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11246 ics_user_moved = ics_clock_paused = FALSE;
11247 ics_getting_history = H_FALSE;
11249 white_holding[0] = black_holding[0] = NULLCHAR;
11250 ClearProgramStats();
11251 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11255 flipView = appData.flipView;
11256 ClearPremoveHighlights();
11257 gotPremove = FALSE;
11258 alarmSounded = FALSE;
11260 GameEnds(EndOfFile, NULL, GE_PLAYER);
11261 if(appData.serverMovesName != NULL) {
11262 /* [HGM] prepare to make moves file for broadcasting */
11263 clock_t t = clock();
11264 if(serverMoves != NULL) fclose(serverMoves);
11265 serverMoves = fopen(appData.serverMovesName, "r");
11266 if(serverMoves != NULL) {
11267 fclose(serverMoves);
11268 /* delay 15 sec before overwriting, so all clients can see end */
11269 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11271 serverMoves = fopen(appData.serverMovesName, "w");
11275 gameMode = BeginningOfGame;
11277 if(appData.icsActive) gameInfo.variant = VariantNormal;
11278 currentMove = forwardMostMove = backwardMostMove = 0;
11279 MarkTargetSquares(1);
11280 InitPosition(redraw);
11281 for (i = 0; i < MAX_MOVES; i++) {
11282 if (commentList[i] != NULL) {
11283 free(commentList[i]);
11284 commentList[i] = NULL;
11288 timeRemaining[0][0] = whiteTimeRemaining;
11289 timeRemaining[1][0] = blackTimeRemaining;
11291 if (first.pr == NoProc) {
11292 StartChessProgram(&first);
11295 InitChessProgram(&first, startedFromSetupPosition);
11298 DisplayMessage("", "");
11299 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11300 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11301 ClearMap(); // [HGM] exclude: invalidate map
11305 AutoPlayGameLoop ()
11308 if (!AutoPlayOneMove())
11310 if (matchMode || appData.timeDelay == 0)
11312 if (appData.timeDelay < 0)
11314 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11322 ReloadGame(1); // next game
11328 int fromX, fromY, toX, toY;
11330 if (appData.debugMode) {
11331 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11334 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11337 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11338 pvInfoList[currentMove].depth = programStats.depth;
11339 pvInfoList[currentMove].score = programStats.score;
11340 pvInfoList[currentMove].time = 0;
11341 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11342 else { // append analysis of final position as comment
11344 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11345 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11347 programStats.depth = 0;
11350 if (currentMove >= forwardMostMove) {
11351 if(gameMode == AnalyzeFile) {
11352 if(appData.loadGameIndex == -1) {
11353 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11354 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11356 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11359 // gameMode = EndOfGame;
11360 // ModeHighlight();
11362 /* [AS] Clear current move marker at the end of a game */
11363 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11368 toX = moveList[currentMove][2] - AAA;
11369 toY = moveList[currentMove][3] - ONE;
11371 if (moveList[currentMove][1] == '@') {
11372 if (appData.highlightLastMove) {
11373 SetHighlights(-1, -1, toX, toY);
11376 fromX = moveList[currentMove][0] - AAA;
11377 fromY = moveList[currentMove][1] - ONE;
11379 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11381 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11383 if (appData.highlightLastMove) {
11384 SetHighlights(fromX, fromY, toX, toY);
11387 DisplayMove(currentMove);
11388 SendMoveToProgram(currentMove++, &first);
11389 DisplayBothClocks();
11390 DrawPosition(FALSE, boards[currentMove]);
11391 // [HGM] PV info: always display, routine tests if empty
11392 DisplayComment(currentMove - 1, commentList[currentMove]);
11398 LoadGameOneMove (ChessMove readAhead)
11400 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11401 char promoChar = NULLCHAR;
11402 ChessMove moveType;
11403 char move[MSG_SIZ];
11406 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11407 gameMode != AnalyzeMode && gameMode != Training) {
11412 yyboardindex = forwardMostMove;
11413 if (readAhead != EndOfFile) {
11414 moveType = readAhead;
11416 if (gameFileFP == NULL)
11418 moveType = (ChessMove) Myylex();
11422 switch (moveType) {
11424 if (appData.debugMode)
11425 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11428 /* append the comment but don't display it */
11429 AppendComment(currentMove, p, FALSE);
11432 case WhiteCapturesEnPassant:
11433 case BlackCapturesEnPassant:
11434 case WhitePromotion:
11435 case BlackPromotion:
11436 case WhiteNonPromotion:
11437 case BlackNonPromotion:
11439 case WhiteKingSideCastle:
11440 case WhiteQueenSideCastle:
11441 case BlackKingSideCastle:
11442 case BlackQueenSideCastle:
11443 case WhiteKingSideCastleWild:
11444 case WhiteQueenSideCastleWild:
11445 case BlackKingSideCastleWild:
11446 case BlackQueenSideCastleWild:
11448 case WhiteHSideCastleFR:
11449 case WhiteASideCastleFR:
11450 case BlackHSideCastleFR:
11451 case BlackASideCastleFR:
11453 if (appData.debugMode)
11454 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11455 fromX = currentMoveString[0] - AAA;
11456 fromY = currentMoveString[1] - ONE;
11457 toX = currentMoveString[2] - AAA;
11458 toY = currentMoveString[3] - ONE;
11459 promoChar = currentMoveString[4];
11464 if (appData.debugMode)
11465 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11466 fromX = moveType == WhiteDrop ?
11467 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11468 (int) CharToPiece(ToLower(currentMoveString[0]));
11470 toX = currentMoveString[2] - AAA;
11471 toY = currentMoveString[3] - ONE;
11477 case GameUnfinished:
11478 if (appData.debugMode)
11479 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11480 p = strchr(yy_text, '{');
11481 if (p == NULL) p = strchr(yy_text, '(');
11484 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11486 q = strchr(p, *p == '{' ? '}' : ')');
11487 if (q != NULL) *q = NULLCHAR;
11490 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11491 GameEnds(moveType, p, GE_FILE);
11493 if (cmailMsgLoaded) {
11495 flipView = WhiteOnMove(currentMove);
11496 if (moveType == GameUnfinished) flipView = !flipView;
11497 if (appData.debugMode)
11498 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11503 if (appData.debugMode)
11504 fprintf(debugFP, "Parser hit end of file\n");
11505 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11511 if (WhiteOnMove(currentMove)) {
11512 GameEnds(BlackWins, "Black mates", GE_FILE);
11514 GameEnds(WhiteWins, "White mates", GE_FILE);
11518 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11524 case MoveNumberOne:
11525 if (lastLoadGameStart == GNUChessGame) {
11526 /* GNUChessGames have numbers, but they aren't move numbers */
11527 if (appData.debugMode)
11528 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11529 yy_text, (int) moveType);
11530 return LoadGameOneMove(EndOfFile); /* tail recursion */
11532 /* else fall thru */
11537 /* Reached start of next game in file */
11538 if (appData.debugMode)
11539 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11540 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11546 if (WhiteOnMove(currentMove)) {
11547 GameEnds(BlackWins, "Black mates", GE_FILE);
11549 GameEnds(WhiteWins, "White mates", GE_FILE);
11553 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11559 case PositionDiagram: /* should not happen; ignore */
11560 case ElapsedTime: /* ignore */
11561 case NAG: /* ignore */
11562 if (appData.debugMode)
11563 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11564 yy_text, (int) moveType);
11565 return LoadGameOneMove(EndOfFile); /* tail recursion */
11568 if (appData.testLegality) {
11569 if (appData.debugMode)
11570 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11571 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11572 (forwardMostMove / 2) + 1,
11573 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11574 DisplayError(move, 0);
11577 if (appData.debugMode)
11578 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11579 yy_text, currentMoveString);
11580 fromX = currentMoveString[0] - AAA;
11581 fromY = currentMoveString[1] - ONE;
11582 toX = currentMoveString[2] - AAA;
11583 toY = currentMoveString[3] - ONE;
11584 promoChar = currentMoveString[4];
11588 case AmbiguousMove:
11589 if (appData.debugMode)
11590 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11591 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11592 (forwardMostMove / 2) + 1,
11593 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11594 DisplayError(move, 0);
11599 case ImpossibleMove:
11600 if (appData.debugMode)
11601 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11602 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11603 (forwardMostMove / 2) + 1,
11604 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11605 DisplayError(move, 0);
11611 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11612 DrawPosition(FALSE, boards[currentMove]);
11613 DisplayBothClocks();
11614 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11615 DisplayComment(currentMove - 1, commentList[currentMove]);
11617 (void) StopLoadGameTimer();
11619 cmailOldMove = forwardMostMove;
11622 /* currentMoveString is set as a side-effect of yylex */
11624 thinkOutput[0] = NULLCHAR;
11625 MakeMove(fromX, fromY, toX, toY, promoChar);
11626 currentMove = forwardMostMove;
11631 /* Load the nth game from the given file */
11633 LoadGameFromFile (char *filename, int n, char *title, int useList)
11638 if (strcmp(filename, "-") == 0) {
11642 f = fopen(filename, "rb");
11644 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11645 DisplayError(buf, errno);
11649 if (fseek(f, 0, 0) == -1) {
11650 /* f is not seekable; probably a pipe */
11653 if (useList && n == 0) {
11654 int error = GameListBuild(f);
11656 DisplayError(_("Cannot build game list"), error);
11657 } else if (!ListEmpty(&gameList) &&
11658 ((ListGame *) gameList.tailPred)->number > 1) {
11659 GameListPopUp(f, title);
11666 return LoadGame(f, n, title, FALSE);
11671 MakeRegisteredMove ()
11673 int fromX, fromY, toX, toY;
11675 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11676 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11679 if (appData.debugMode)
11680 fprintf(debugFP, "Restoring %s for game %d\n",
11681 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11683 thinkOutput[0] = NULLCHAR;
11684 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11685 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11686 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11687 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11688 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11689 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11690 MakeMove(fromX, fromY, toX, toY, promoChar);
11691 ShowMove(fromX, fromY, toX, toY);
11693 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11700 if (WhiteOnMove(currentMove)) {
11701 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11703 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11708 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11715 if (WhiteOnMove(currentMove)) {
11716 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11718 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11723 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11734 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11736 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11740 if (gameNumber > nCmailGames) {
11741 DisplayError(_("No more games in this message"), 0);
11744 if (f == lastLoadGameFP) {
11745 int offset = gameNumber - lastLoadGameNumber;
11747 cmailMsg[0] = NULLCHAR;
11748 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11749 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11750 nCmailMovesRegistered--;
11752 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11753 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11754 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11757 if (! RegisterMove()) return FALSE;
11761 retVal = LoadGame(f, gameNumber, title, useList);
11763 /* Make move registered during previous look at this game, if any */
11764 MakeRegisteredMove();
11766 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11767 commentList[currentMove]
11768 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11769 DisplayComment(currentMove - 1, commentList[currentMove]);
11775 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11777 ReloadGame (int offset)
11779 int gameNumber = lastLoadGameNumber + offset;
11780 if (lastLoadGameFP == NULL) {
11781 DisplayError(_("No game has been loaded yet"), 0);
11784 if (gameNumber <= 0) {
11785 DisplayError(_("Can't back up any further"), 0);
11788 if (cmailMsgLoaded) {
11789 return CmailLoadGame(lastLoadGameFP, gameNumber,
11790 lastLoadGameTitle, lastLoadGameUseList);
11792 return LoadGame(lastLoadGameFP, gameNumber,
11793 lastLoadGameTitle, lastLoadGameUseList);
11797 int keys[EmptySquare+1];
11800 PositionMatches (Board b1, Board b2)
11803 switch(appData.searchMode) {
11804 case 1: return CompareWithRights(b1, b2);
11806 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11807 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11811 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11812 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11813 sum += keys[b1[r][f]] - keys[b2[r][f]];
11817 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11818 sum += keys[b1[r][f]] - keys[b2[r][f]];
11830 int pieceList[256], quickBoard[256];
11831 ChessSquare pieceType[256] = { EmptySquare };
11832 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11833 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11834 int soughtTotal, turn;
11835 Boolean epOK, flipSearch;
11838 unsigned char piece, to;
11841 #define DSIZE (250000)
11843 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11844 Move *moveDatabase = initialSpace;
11845 unsigned int movePtr, dataSize = DSIZE;
11848 MakePieceList (Board board, int *counts)
11850 int r, f, n=Q_PROMO, total=0;
11851 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11852 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11853 int sq = f + (r<<4);
11854 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11855 quickBoard[sq] = ++n;
11857 pieceType[n] = board[r][f];
11858 counts[board[r][f]]++;
11859 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11860 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11864 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11869 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11871 int sq = fromX + (fromY<<4);
11872 int piece = quickBoard[sq];
11873 quickBoard[sq] = 0;
11874 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11875 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11876 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11877 moveDatabase[movePtr++].piece = Q_WCASTL;
11878 quickBoard[sq] = piece;
11879 piece = quickBoard[from]; quickBoard[from] = 0;
11880 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11882 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11883 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11884 moveDatabase[movePtr++].piece = Q_BCASTL;
11885 quickBoard[sq] = piece;
11886 piece = quickBoard[from]; quickBoard[from] = 0;
11887 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11889 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11890 quickBoard[(fromY<<4)+toX] = 0;
11891 moveDatabase[movePtr].piece = Q_EP;
11892 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11893 moveDatabase[movePtr].to = sq;
11895 if(promoPiece != pieceType[piece]) {
11896 moveDatabase[movePtr++].piece = Q_PROMO;
11897 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11899 moveDatabase[movePtr].piece = piece;
11900 quickBoard[sq] = piece;
11905 PackGame (Board board)
11907 Move *newSpace = NULL;
11908 moveDatabase[movePtr].piece = 0; // terminate previous game
11909 if(movePtr > dataSize) {
11910 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11911 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11912 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11915 Move *p = moveDatabase, *q = newSpace;
11916 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11917 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11918 moveDatabase = newSpace;
11919 } else { // calloc failed, we must be out of memory. Too bad...
11920 dataSize = 0; // prevent calloc events for all subsequent games
11921 return 0; // and signal this one isn't cached
11925 MakePieceList(board, counts);
11930 QuickCompare (Board board, int *minCounts, int *maxCounts)
11931 { // compare according to search mode
11933 switch(appData.searchMode)
11935 case 1: // exact position match
11936 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11937 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11938 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11941 case 2: // can have extra material on empty squares
11942 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11943 if(board[r][f] == EmptySquare) continue;
11944 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11947 case 3: // material with exact Pawn structure
11948 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11949 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11950 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11951 } // fall through to material comparison
11952 case 4: // exact material
11953 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11955 case 6: // material range with given imbalance
11956 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11957 // fall through to range comparison
11958 case 5: // material range
11959 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11965 QuickScan (Board board, Move *move)
11966 { // reconstruct game,and compare all positions in it
11967 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11969 int piece = move->piece;
11970 int to = move->to, from = pieceList[piece];
11971 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11972 if(!piece) return -1;
11973 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11974 piece = (++move)->piece;
11975 from = pieceList[piece];
11976 counts[pieceType[piece]]--;
11977 pieceType[piece] = (ChessSquare) move->to;
11978 counts[move->to]++;
11979 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11980 counts[pieceType[quickBoard[to]]]--;
11981 quickBoard[to] = 0; total--;
11984 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11985 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11986 from = pieceList[piece]; // so this must be King
11987 quickBoard[from] = 0;
11988 pieceList[piece] = to;
11989 from = pieceList[(++move)->piece]; // for FRC this has to be done here
11990 quickBoard[from] = 0; // rook
11991 quickBoard[to] = piece;
11992 to = move->to; piece = move->piece;
11996 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11997 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11998 quickBoard[from] = 0;
12000 quickBoard[to] = piece;
12001 pieceList[piece] = to;
12003 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12004 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12005 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12006 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12008 static int lastCounts[EmptySquare+1];
12010 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12011 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12012 } else stretch = 0;
12013 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12022 flipSearch = FALSE;
12023 CopyBoard(soughtBoard, boards[currentMove]);
12024 soughtTotal = MakePieceList(soughtBoard, maxSought);
12025 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12026 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12027 CopyBoard(reverseBoard, boards[currentMove]);
12028 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12029 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12030 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12031 reverseBoard[r][f] = piece;
12033 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12034 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12035 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12036 || (boards[currentMove][CASTLING][2] == NoRights ||
12037 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12038 && (boards[currentMove][CASTLING][5] == NoRights ||
12039 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12042 CopyBoard(flipBoard, soughtBoard);
12043 CopyBoard(rotateBoard, reverseBoard);
12044 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12045 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12046 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12049 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12050 if(appData.searchMode >= 5) {
12051 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12052 MakePieceList(soughtBoard, minSought);
12053 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12055 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12056 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12059 GameInfo dummyInfo;
12060 static int creatingBook;
12063 GameContainsPosition (FILE *f, ListGame *lg)
12065 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12066 int fromX, fromY, toX, toY;
12068 static int initDone=FALSE;
12070 // weed out games based on numerical tag comparison
12071 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12072 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12073 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12074 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12076 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12079 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
12080 else CopyBoard(boards[scratch], initialPosition); // default start position
12083 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12084 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12087 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12088 fseek(f, lg->offset, 0);
12091 yyboardindex = scratch;
12092 quickFlag = plyNr+1;
12097 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12103 if(plyNr) return -1; // after we have seen moves, this is for new game
12106 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12107 case ImpossibleMove:
12108 case WhiteWins: // game ends here with these four
12111 case GameUnfinished:
12115 if(appData.testLegality) return -1;
12116 case WhiteCapturesEnPassant:
12117 case BlackCapturesEnPassant:
12118 case WhitePromotion:
12119 case BlackPromotion:
12120 case WhiteNonPromotion:
12121 case BlackNonPromotion:
12123 case WhiteKingSideCastle:
12124 case WhiteQueenSideCastle:
12125 case BlackKingSideCastle:
12126 case BlackQueenSideCastle:
12127 case WhiteKingSideCastleWild:
12128 case WhiteQueenSideCastleWild:
12129 case BlackKingSideCastleWild:
12130 case BlackQueenSideCastleWild:
12131 case WhiteHSideCastleFR:
12132 case WhiteASideCastleFR:
12133 case BlackHSideCastleFR:
12134 case BlackASideCastleFR:
12135 fromX = currentMoveString[0] - AAA;
12136 fromY = currentMoveString[1] - ONE;
12137 toX = currentMoveString[2] - AAA;
12138 toY = currentMoveString[3] - ONE;
12139 promoChar = currentMoveString[4];
12143 fromX = next == WhiteDrop ?
12144 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12145 (int) CharToPiece(ToLower(currentMoveString[0]));
12147 toX = currentMoveString[2] - AAA;
12148 toY = currentMoveString[3] - ONE;
12152 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12154 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12155 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12156 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12157 if(appData.findMirror) {
12158 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12159 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12164 /* Load the nth game from open file f */
12166 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12170 int gn = gameNumber;
12171 ListGame *lg = NULL;
12172 int numPGNTags = 0;
12174 GameMode oldGameMode;
12175 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12177 if (appData.debugMode)
12178 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12180 if (gameMode == Training )
12181 SetTrainingModeOff();
12183 oldGameMode = gameMode;
12184 if (gameMode != BeginningOfGame) {
12185 Reset(FALSE, TRUE);
12189 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12190 fclose(lastLoadGameFP);
12194 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12197 fseek(f, lg->offset, 0);
12198 GameListHighlight(gameNumber);
12199 pos = lg->position;
12203 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12204 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12206 DisplayError(_("Game number out of range"), 0);
12211 if (fseek(f, 0, 0) == -1) {
12212 if (f == lastLoadGameFP ?
12213 gameNumber == lastLoadGameNumber + 1 :
12217 DisplayError(_("Can't seek on game file"), 0);
12222 lastLoadGameFP = f;
12223 lastLoadGameNumber = gameNumber;
12224 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12225 lastLoadGameUseList = useList;
12229 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12230 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12231 lg->gameInfo.black);
12233 } else if (*title != NULLCHAR) {
12234 if (gameNumber > 1) {
12235 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12238 DisplayTitle(title);
12242 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12243 gameMode = PlayFromGameFile;
12247 currentMove = forwardMostMove = backwardMostMove = 0;
12248 CopyBoard(boards[0], initialPosition);
12252 * Skip the first gn-1 games in the file.
12253 * Also skip over anything that precedes an identifiable
12254 * start of game marker, to avoid being confused by
12255 * garbage at the start of the file. Currently
12256 * recognized start of game markers are the move number "1",
12257 * the pattern "gnuchess .* game", the pattern
12258 * "^[#;%] [^ ]* game file", and a PGN tag block.
12259 * A game that starts with one of the latter two patterns
12260 * will also have a move number 1, possibly
12261 * following a position diagram.
12262 * 5-4-02: Let's try being more lenient and allowing a game to
12263 * start with an unnumbered move. Does that break anything?
12265 cm = lastLoadGameStart = EndOfFile;
12267 yyboardindex = forwardMostMove;
12268 cm = (ChessMove) Myylex();
12271 if (cmailMsgLoaded) {
12272 nCmailGames = CMAIL_MAX_GAMES - gn;
12275 DisplayError(_("Game not found in file"), 0);
12282 lastLoadGameStart = cm;
12285 case MoveNumberOne:
12286 switch (lastLoadGameStart) {
12291 case MoveNumberOne:
12293 gn--; /* count this game */
12294 lastLoadGameStart = cm;
12303 switch (lastLoadGameStart) {
12306 case MoveNumberOne:
12308 gn--; /* count this game */
12309 lastLoadGameStart = cm;
12312 lastLoadGameStart = cm; /* game counted already */
12320 yyboardindex = forwardMostMove;
12321 cm = (ChessMove) Myylex();
12322 } while (cm == PGNTag || cm == Comment);
12329 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12330 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12331 != CMAIL_OLD_RESULT) {
12333 cmailResult[ CMAIL_MAX_GAMES
12334 - gn - 1] = CMAIL_OLD_RESULT;
12340 /* Only a NormalMove can be at the start of a game
12341 * without a position diagram. */
12342 if (lastLoadGameStart == EndOfFile ) {
12344 lastLoadGameStart = MoveNumberOne;
12353 if (appData.debugMode)
12354 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12356 if (cm == XBoardGame) {
12357 /* Skip any header junk before position diagram and/or move 1 */
12359 yyboardindex = forwardMostMove;
12360 cm = (ChessMove) Myylex();
12362 if (cm == EndOfFile ||
12363 cm == GNUChessGame || cm == XBoardGame) {
12364 /* Empty game; pretend end-of-file and handle later */
12369 if (cm == MoveNumberOne || cm == PositionDiagram ||
12370 cm == PGNTag || cm == Comment)
12373 } else if (cm == GNUChessGame) {
12374 if (gameInfo.event != NULL) {
12375 free(gameInfo.event);
12377 gameInfo.event = StrSave(yy_text);
12380 startedFromSetupPosition = FALSE;
12381 while (cm == PGNTag) {
12382 if (appData.debugMode)
12383 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12384 err = ParsePGNTag(yy_text, &gameInfo);
12385 if (!err) numPGNTags++;
12387 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12388 if(gameInfo.variant != oldVariant) {
12389 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12390 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12391 InitPosition(TRUE);
12392 oldVariant = gameInfo.variant;
12393 if (appData.debugMode)
12394 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12398 if (gameInfo.fen != NULL) {
12399 Board initial_position;
12400 startedFromSetupPosition = TRUE;
12401 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12403 DisplayError(_("Bad FEN position in file"), 0);
12406 CopyBoard(boards[0], initial_position);
12407 if (blackPlaysFirst) {
12408 currentMove = forwardMostMove = backwardMostMove = 1;
12409 CopyBoard(boards[1], initial_position);
12410 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12411 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12412 timeRemaining[0][1] = whiteTimeRemaining;
12413 timeRemaining[1][1] = blackTimeRemaining;
12414 if (commentList[0] != NULL) {
12415 commentList[1] = commentList[0];
12416 commentList[0] = NULL;
12419 currentMove = forwardMostMove = backwardMostMove = 0;
12421 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12423 initialRulePlies = FENrulePlies;
12424 for( i=0; i< nrCastlingRights; i++ )
12425 initialRights[i] = initial_position[CASTLING][i];
12427 yyboardindex = forwardMostMove;
12428 free(gameInfo.fen);
12429 gameInfo.fen = NULL;
12432 yyboardindex = forwardMostMove;
12433 cm = (ChessMove) Myylex();
12435 /* Handle comments interspersed among the tags */
12436 while (cm == Comment) {
12438 if (appData.debugMode)
12439 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12441 AppendComment(currentMove, p, FALSE);
12442 yyboardindex = forwardMostMove;
12443 cm = (ChessMove) Myylex();
12447 /* don't rely on existence of Event tag since if game was
12448 * pasted from clipboard the Event tag may not exist
12450 if (numPGNTags > 0){
12452 if (gameInfo.variant == VariantNormal) {
12453 VariantClass v = StringToVariant(gameInfo.event);
12454 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12455 if(v < VariantShogi) gameInfo.variant = v;
12458 if( appData.autoDisplayTags ) {
12459 tags = PGNTags(&gameInfo);
12460 TagsPopUp(tags, CmailMsg());
12465 /* Make something up, but don't display it now */
12470 if (cm == PositionDiagram) {
12473 Board initial_position;
12475 if (appData.debugMode)
12476 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12478 if (!startedFromSetupPosition) {
12480 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12481 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12492 initial_position[i][j++] = CharToPiece(*p);
12495 while (*p == ' ' || *p == '\t' ||
12496 *p == '\n' || *p == '\r') p++;
12498 if (strncmp(p, "black", strlen("black"))==0)
12499 blackPlaysFirst = TRUE;
12501 blackPlaysFirst = FALSE;
12502 startedFromSetupPosition = TRUE;
12504 CopyBoard(boards[0], initial_position);
12505 if (blackPlaysFirst) {
12506 currentMove = forwardMostMove = backwardMostMove = 1;
12507 CopyBoard(boards[1], initial_position);
12508 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12509 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12510 timeRemaining[0][1] = whiteTimeRemaining;
12511 timeRemaining[1][1] = blackTimeRemaining;
12512 if (commentList[0] != NULL) {
12513 commentList[1] = commentList[0];
12514 commentList[0] = NULL;
12517 currentMove = forwardMostMove = backwardMostMove = 0;
12520 yyboardindex = forwardMostMove;
12521 cm = (ChessMove) Myylex();
12524 if(!creatingBook) {
12525 if (first.pr == NoProc) {
12526 StartChessProgram(&first);
12528 InitChessProgram(&first, FALSE);
12529 SendToProgram("force\n", &first);
12530 if (startedFromSetupPosition) {
12531 SendBoard(&first, forwardMostMove);
12532 if (appData.debugMode) {
12533 fprintf(debugFP, "Load Game\n");
12535 DisplayBothClocks();
12539 /* [HGM] server: flag to write setup moves in broadcast file as one */
12540 loadFlag = appData.suppressLoadMoves;
12542 while (cm == Comment) {
12544 if (appData.debugMode)
12545 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12547 AppendComment(currentMove, p, FALSE);
12548 yyboardindex = forwardMostMove;
12549 cm = (ChessMove) Myylex();
12552 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12553 cm == WhiteWins || cm == BlackWins ||
12554 cm == GameIsDrawn || cm == GameUnfinished) {
12555 DisplayMessage("", _("No moves in game"));
12556 if (cmailMsgLoaded) {
12557 if (appData.debugMode)
12558 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12562 DrawPosition(FALSE, boards[currentMove]);
12563 DisplayBothClocks();
12564 gameMode = EditGame;
12571 // [HGM] PV info: routine tests if comment empty
12572 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12573 DisplayComment(currentMove - 1, commentList[currentMove]);
12575 if (!matchMode && appData.timeDelay != 0)
12576 DrawPosition(FALSE, boards[currentMove]);
12578 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12579 programStats.ok_to_send = 1;
12582 /* if the first token after the PGN tags is a move
12583 * and not move number 1, retrieve it from the parser
12585 if (cm != MoveNumberOne)
12586 LoadGameOneMove(cm);
12588 /* load the remaining moves from the file */
12589 while (LoadGameOneMove(EndOfFile)) {
12590 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12591 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12594 /* rewind to the start of the game */
12595 currentMove = backwardMostMove;
12597 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12599 if (oldGameMode == AnalyzeFile) {
12600 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12601 AnalyzeFileEvent();
12603 if (oldGameMode == AnalyzeMode) {
12604 AnalyzeFileEvent();
12607 if(creatingBook) return TRUE;
12608 if (!matchMode && pos > 0) {
12609 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12611 if (matchMode || appData.timeDelay == 0) {
12613 } else if (appData.timeDelay > 0) {
12614 AutoPlayGameLoop();
12617 if (appData.debugMode)
12618 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12620 loadFlag = 0; /* [HGM] true game starts */
12624 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12626 ReloadPosition (int offset)
12628 int positionNumber = lastLoadPositionNumber + offset;
12629 if (lastLoadPositionFP == NULL) {
12630 DisplayError(_("No position has been loaded yet"), 0);
12633 if (positionNumber <= 0) {
12634 DisplayError(_("Can't back up any further"), 0);
12637 return LoadPosition(lastLoadPositionFP, positionNumber,
12638 lastLoadPositionTitle);
12641 /* Load the nth position from the given file */
12643 LoadPositionFromFile (char *filename, int n, char *title)
12648 if (strcmp(filename, "-") == 0) {
12649 return LoadPosition(stdin, n, "stdin");
12651 f = fopen(filename, "rb");
12653 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12654 DisplayError(buf, errno);
12657 return LoadPosition(f, n, title);
12662 /* Load the nth position from the given open file, and close it */
12664 LoadPosition (FILE *f, int positionNumber, char *title)
12666 char *p, line[MSG_SIZ];
12667 Board initial_position;
12668 int i, j, fenMode, pn;
12670 if (gameMode == Training )
12671 SetTrainingModeOff();
12673 if (gameMode != BeginningOfGame) {
12674 Reset(FALSE, TRUE);
12676 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12677 fclose(lastLoadPositionFP);
12679 if (positionNumber == 0) positionNumber = 1;
12680 lastLoadPositionFP = f;
12681 lastLoadPositionNumber = positionNumber;
12682 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12683 if (first.pr == NoProc && !appData.noChessProgram) {
12684 StartChessProgram(&first);
12685 InitChessProgram(&first, FALSE);
12687 pn = positionNumber;
12688 if (positionNumber < 0) {
12689 /* Negative position number means to seek to that byte offset */
12690 if (fseek(f, -positionNumber, 0) == -1) {
12691 DisplayError(_("Can't seek on position file"), 0);
12696 if (fseek(f, 0, 0) == -1) {
12697 if (f == lastLoadPositionFP ?
12698 positionNumber == lastLoadPositionNumber + 1 :
12699 positionNumber == 1) {
12702 DisplayError(_("Can't seek on position file"), 0);
12707 /* See if this file is FEN or old-style xboard */
12708 if (fgets(line, MSG_SIZ, f) == NULL) {
12709 DisplayError(_("Position not found in file"), 0);
12712 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12713 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12716 if (fenMode || line[0] == '#') pn--;
12718 /* skip positions before number pn */
12719 if (fgets(line, MSG_SIZ, f) == NULL) {
12721 DisplayError(_("Position not found in file"), 0);
12724 if (fenMode || line[0] == '#') pn--;
12729 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12730 DisplayError(_("Bad FEN position in file"), 0);
12734 (void) fgets(line, MSG_SIZ, f);
12735 (void) fgets(line, MSG_SIZ, f);
12737 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12738 (void) fgets(line, MSG_SIZ, f);
12739 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12742 initial_position[i][j++] = CharToPiece(*p);
12746 blackPlaysFirst = FALSE;
12748 (void) fgets(line, MSG_SIZ, f);
12749 if (strncmp(line, "black", strlen("black"))==0)
12750 blackPlaysFirst = TRUE;
12753 startedFromSetupPosition = TRUE;
12755 CopyBoard(boards[0], initial_position);
12756 if (blackPlaysFirst) {
12757 currentMove = forwardMostMove = backwardMostMove = 1;
12758 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12759 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12760 CopyBoard(boards[1], initial_position);
12761 DisplayMessage("", _("Black to play"));
12763 currentMove = forwardMostMove = backwardMostMove = 0;
12764 DisplayMessage("", _("White to play"));
12766 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12767 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12768 SendToProgram("force\n", &first);
12769 SendBoard(&first, forwardMostMove);
12771 if (appData.debugMode) {
12773 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12774 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12775 fprintf(debugFP, "Load Position\n");
12778 if (positionNumber > 1) {
12779 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12780 DisplayTitle(line);
12782 DisplayTitle(title);
12784 gameMode = EditGame;
12787 timeRemaining[0][1] = whiteTimeRemaining;
12788 timeRemaining[1][1] = blackTimeRemaining;
12789 DrawPosition(FALSE, boards[currentMove]);
12796 CopyPlayerNameIntoFileName (char **dest, char *src)
12798 while (*src != NULLCHAR && *src != ',') {
12803 *(*dest)++ = *src++;
12809 DefaultFileName (char *ext)
12811 static char def[MSG_SIZ];
12814 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12816 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12818 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12820 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12827 /* Save the current game to the given file */
12829 SaveGameToFile (char *filename, int append)
12833 int result, i, t,tot=0;
12835 if (strcmp(filename, "-") == 0) {
12836 return SaveGame(stdout, 0, NULL);
12838 for(i=0; i<10; i++) { // upto 10 tries
12839 f = fopen(filename, append ? "a" : "w");
12840 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12841 if(f || errno != 13) break;
12842 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12846 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12847 DisplayError(buf, errno);
12850 safeStrCpy(buf, lastMsg, MSG_SIZ);
12851 DisplayMessage(_("Waiting for access to save file"), "");
12852 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12853 DisplayMessage(_("Saving game"), "");
12854 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12855 result = SaveGame(f, 0, NULL);
12856 DisplayMessage(buf, "");
12863 SavePart (char *str)
12865 static char buf[MSG_SIZ];
12868 p = strchr(str, ' ');
12869 if (p == NULL) return str;
12870 strncpy(buf, str, p - str);
12871 buf[p - str] = NULLCHAR;
12875 #define PGN_MAX_LINE 75
12877 #define PGN_SIDE_WHITE 0
12878 #define PGN_SIDE_BLACK 1
12881 FindFirstMoveOutOfBook (int side)
12885 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12886 int index = backwardMostMove;
12887 int has_book_hit = 0;
12889 if( (index % 2) != side ) {
12893 while( index < forwardMostMove ) {
12894 /* Check to see if engine is in book */
12895 int depth = pvInfoList[index].depth;
12896 int score = pvInfoList[index].score;
12902 else if( score == 0 && depth == 63 ) {
12903 in_book = 1; /* Zappa */
12905 else if( score == 2 && depth == 99 ) {
12906 in_book = 1; /* Abrok */
12909 has_book_hit += in_book;
12925 GetOutOfBookInfo (char * buf)
12929 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12931 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12932 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12936 if( oob[0] >= 0 || oob[1] >= 0 ) {
12937 for( i=0; i<2; i++ ) {
12941 if( i > 0 && oob[0] >= 0 ) {
12942 strcat( buf, " " );
12945 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12946 sprintf( buf+strlen(buf), "%s%.2f",
12947 pvInfoList[idx].score >= 0 ? "+" : "",
12948 pvInfoList[idx].score / 100.0 );
12954 /* Save game in PGN style and close the file */
12956 SaveGamePGN (FILE *f)
12958 int i, offset, linelen, newblock;
12961 int movelen, numlen, blank;
12962 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12964 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12966 PrintPGNTags(f, &gameInfo);
12968 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12970 if (backwardMostMove > 0 || startedFromSetupPosition) {
12971 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12972 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12973 fprintf(f, "\n{--------------\n");
12974 PrintPosition(f, backwardMostMove);
12975 fprintf(f, "--------------}\n");
12979 /* [AS] Out of book annotation */
12980 if( appData.saveOutOfBookInfo ) {
12983 GetOutOfBookInfo( buf );
12985 if( buf[0] != '\0' ) {
12986 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12993 i = backwardMostMove;
12997 while (i < forwardMostMove) {
12998 /* Print comments preceding this move */
12999 if (commentList[i] != NULL) {
13000 if (linelen > 0) fprintf(f, "\n");
13001 fprintf(f, "%s", commentList[i]);
13006 /* Format move number */
13008 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13011 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13013 numtext[0] = NULLCHAR;
13015 numlen = strlen(numtext);
13018 /* Print move number */
13019 blank = linelen > 0 && numlen > 0;
13020 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13029 fprintf(f, "%s", numtext);
13033 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13034 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13037 blank = linelen > 0 && movelen > 0;
13038 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13047 fprintf(f, "%s", move_buffer);
13048 linelen += movelen;
13050 /* [AS] Add PV info if present */
13051 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13052 /* [HGM] add time */
13053 char buf[MSG_SIZ]; int seconds;
13055 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13061 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13064 seconds = (seconds + 4)/10; // round to full seconds
13066 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13068 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13071 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13072 pvInfoList[i].score >= 0 ? "+" : "",
13073 pvInfoList[i].score / 100.0,
13074 pvInfoList[i].depth,
13077 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13079 /* Print score/depth */
13080 blank = linelen > 0 && movelen > 0;
13081 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13090 fprintf(f, "%s", move_buffer);
13091 linelen += movelen;
13097 /* Start a new line */
13098 if (linelen > 0) fprintf(f, "\n");
13100 /* Print comments after last move */
13101 if (commentList[i] != NULL) {
13102 fprintf(f, "%s\n", commentList[i]);
13106 if (gameInfo.resultDetails != NULL &&
13107 gameInfo.resultDetails[0] != NULLCHAR) {
13108 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
13109 PGNResult(gameInfo.result));
13111 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13115 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13119 /* Save game in old style and close the file */
13121 SaveGameOldStyle (FILE *f)
13126 tm = time((time_t *) NULL);
13128 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13131 if (backwardMostMove > 0 || startedFromSetupPosition) {
13132 fprintf(f, "\n[--------------\n");
13133 PrintPosition(f, backwardMostMove);
13134 fprintf(f, "--------------]\n");
13139 i = backwardMostMove;
13140 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13142 while (i < forwardMostMove) {
13143 if (commentList[i] != NULL) {
13144 fprintf(f, "[%s]\n", commentList[i]);
13147 if ((i % 2) == 1) {
13148 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13151 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13153 if (commentList[i] != NULL) {
13157 if (i >= forwardMostMove) {
13161 fprintf(f, "%s\n", parseList[i]);
13166 if (commentList[i] != NULL) {
13167 fprintf(f, "[%s]\n", commentList[i]);
13170 /* This isn't really the old style, but it's close enough */
13171 if (gameInfo.resultDetails != NULL &&
13172 gameInfo.resultDetails[0] != NULLCHAR) {
13173 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13174 gameInfo.resultDetails);
13176 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13183 /* Save the current game to open file f and close the file */
13185 SaveGame (FILE *f, int dummy, char *dummy2)
13187 if (gameMode == EditPosition) EditPositionDone(TRUE);
13188 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13189 if (appData.oldSaveStyle)
13190 return SaveGameOldStyle(f);
13192 return SaveGamePGN(f);
13195 /* Save the current position to the given file */
13197 SavePositionToFile (char *filename)
13202 if (strcmp(filename, "-") == 0) {
13203 return SavePosition(stdout, 0, NULL);
13205 f = fopen(filename, "a");
13207 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13208 DisplayError(buf, errno);
13211 safeStrCpy(buf, lastMsg, MSG_SIZ);
13212 DisplayMessage(_("Waiting for access to save file"), "");
13213 flock(fileno(f), LOCK_EX); // [HGM] lock
13214 DisplayMessage(_("Saving position"), "");
13215 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13216 SavePosition(f, 0, NULL);
13217 DisplayMessage(buf, "");
13223 /* Save the current position to the given open file and close the file */
13225 SavePosition (FILE *f, int dummy, char *dummy2)
13230 if (gameMode == EditPosition) EditPositionDone(TRUE);
13231 if (appData.oldSaveStyle) {
13232 tm = time((time_t *) NULL);
13234 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13236 fprintf(f, "[--------------\n");
13237 PrintPosition(f, currentMove);
13238 fprintf(f, "--------------]\n");
13240 fen = PositionToFEN(currentMove, NULL, 1);
13241 fprintf(f, "%s\n", fen);
13249 ReloadCmailMsgEvent (int unregister)
13252 static char *inFilename = NULL;
13253 static char *outFilename;
13255 struct stat inbuf, outbuf;
13258 /* Any registered moves are unregistered if unregister is set, */
13259 /* i.e. invoked by the signal handler */
13261 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13262 cmailMoveRegistered[i] = FALSE;
13263 if (cmailCommentList[i] != NULL) {
13264 free(cmailCommentList[i]);
13265 cmailCommentList[i] = NULL;
13268 nCmailMovesRegistered = 0;
13271 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13272 cmailResult[i] = CMAIL_NOT_RESULT;
13276 if (inFilename == NULL) {
13277 /* Because the filenames are static they only get malloced once */
13278 /* and they never get freed */
13279 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13280 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13282 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13283 sprintf(outFilename, "%s.out", appData.cmailGameName);
13286 status = stat(outFilename, &outbuf);
13288 cmailMailedMove = FALSE;
13290 status = stat(inFilename, &inbuf);
13291 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13294 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13295 counts the games, notes how each one terminated, etc.
13297 It would be nice to remove this kludge and instead gather all
13298 the information while building the game list. (And to keep it
13299 in the game list nodes instead of having a bunch of fixed-size
13300 parallel arrays.) Note this will require getting each game's
13301 termination from the PGN tags, as the game list builder does
13302 not process the game moves. --mann
13304 cmailMsgLoaded = TRUE;
13305 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13307 /* Load first game in the file or popup game menu */
13308 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13310 #endif /* !WIN32 */
13318 char string[MSG_SIZ];
13320 if ( cmailMailedMove
13321 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13322 return TRUE; /* Allow free viewing */
13325 /* Unregister move to ensure that we don't leave RegisterMove */
13326 /* with the move registered when the conditions for registering no */
13328 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13329 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13330 nCmailMovesRegistered --;
13332 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13334 free(cmailCommentList[lastLoadGameNumber - 1]);
13335 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13339 if (cmailOldMove == -1) {
13340 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13344 if (currentMove > cmailOldMove + 1) {
13345 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13349 if (currentMove < cmailOldMove) {
13350 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13354 if (forwardMostMove > currentMove) {
13355 /* Silently truncate extra moves */
13359 if ( (currentMove == cmailOldMove + 1)
13360 || ( (currentMove == cmailOldMove)
13361 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13362 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13363 if (gameInfo.result != GameUnfinished) {
13364 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13367 if (commentList[currentMove] != NULL) {
13368 cmailCommentList[lastLoadGameNumber - 1]
13369 = StrSave(commentList[currentMove]);
13371 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13373 if (appData.debugMode)
13374 fprintf(debugFP, "Saving %s for game %d\n",
13375 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13377 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13379 f = fopen(string, "w");
13380 if (appData.oldSaveStyle) {
13381 SaveGameOldStyle(f); /* also closes the file */
13383 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13384 f = fopen(string, "w");
13385 SavePosition(f, 0, NULL); /* also closes the file */
13387 fprintf(f, "{--------------\n");
13388 PrintPosition(f, currentMove);
13389 fprintf(f, "--------------}\n\n");
13391 SaveGame(f, 0, NULL); /* also closes the file*/
13394 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13395 nCmailMovesRegistered ++;
13396 } else if (nCmailGames == 1) {
13397 DisplayError(_("You have not made a move yet"), 0);
13408 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13409 FILE *commandOutput;
13410 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13411 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13417 if (! cmailMsgLoaded) {
13418 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13422 if (nCmailGames == nCmailResults) {
13423 DisplayError(_("No unfinished games"), 0);
13427 #if CMAIL_PROHIBIT_REMAIL
13428 if (cmailMailedMove) {
13429 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);
13430 DisplayError(msg, 0);
13435 if (! (cmailMailedMove || RegisterMove())) return;
13437 if ( cmailMailedMove
13438 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13439 snprintf(string, MSG_SIZ, partCommandString,
13440 appData.debugMode ? " -v" : "", appData.cmailGameName);
13441 commandOutput = popen(string, "r");
13443 if (commandOutput == NULL) {
13444 DisplayError(_("Failed to invoke cmail"), 0);
13446 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13447 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13449 if (nBuffers > 1) {
13450 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13451 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13452 nBytes = MSG_SIZ - 1;
13454 (void) memcpy(msg, buffer, nBytes);
13456 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13458 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13459 cmailMailedMove = TRUE; /* Prevent >1 moves */
13462 for (i = 0; i < nCmailGames; i ++) {
13463 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13468 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13470 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13472 appData.cmailGameName,
13474 LoadGameFromFile(buffer, 1, buffer, FALSE);
13475 cmailMsgLoaded = FALSE;
13479 DisplayInformation(msg);
13480 pclose(commandOutput);
13483 if ((*cmailMsg) != '\0') {
13484 DisplayInformation(cmailMsg);
13489 #endif /* !WIN32 */
13498 int prependComma = 0;
13500 char string[MSG_SIZ]; /* Space for game-list */
13503 if (!cmailMsgLoaded) return "";
13505 if (cmailMailedMove) {
13506 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13508 /* Create a list of games left */
13509 snprintf(string, MSG_SIZ, "[");
13510 for (i = 0; i < nCmailGames; i ++) {
13511 if (! ( cmailMoveRegistered[i]
13512 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13513 if (prependComma) {
13514 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13516 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13520 strcat(string, number);
13523 strcat(string, "]");
13525 if (nCmailMovesRegistered + nCmailResults == 0) {
13526 switch (nCmailGames) {
13528 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13532 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13536 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13541 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13543 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13548 if (nCmailResults == nCmailGames) {
13549 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13551 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13556 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13568 if (gameMode == Training)
13569 SetTrainingModeOff();
13572 cmailMsgLoaded = FALSE;
13573 if (appData.icsActive) {
13574 SendToICS(ics_prefix);
13575 SendToICS("refresh\n");
13580 ExitEvent (int status)
13584 /* Give up on clean exit */
13588 /* Keep trying for clean exit */
13592 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13594 if (telnetISR != NULL) {
13595 RemoveInputSource(telnetISR);
13597 if (icsPR != NoProc) {
13598 DestroyChildProcess(icsPR, TRUE);
13601 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13602 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13604 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13605 /* make sure this other one finishes before killing it! */
13606 if(endingGame) { int count = 0;
13607 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13608 while(endingGame && count++ < 10) DoSleep(1);
13609 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13612 /* Kill off chess programs */
13613 if (first.pr != NoProc) {
13616 DoSleep( appData.delayBeforeQuit );
13617 SendToProgram("quit\n", &first);
13618 DoSleep( appData.delayAfterQuit );
13619 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13621 if (second.pr != NoProc) {
13622 DoSleep( appData.delayBeforeQuit );
13623 SendToProgram("quit\n", &second);
13624 DoSleep( appData.delayAfterQuit );
13625 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13627 if (first.isr != NULL) {
13628 RemoveInputSource(first.isr);
13630 if (second.isr != NULL) {
13631 RemoveInputSource(second.isr);
13634 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13635 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13637 ShutDownFrontEnd();
13642 PauseEngine (ChessProgramState *cps)
13644 SendToProgram("pause\n", cps);
13649 UnPauseEngine (ChessProgramState *cps)
13651 SendToProgram("resume\n", cps);
13658 if (appData.debugMode)
13659 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13663 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13665 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13666 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13667 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13669 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13670 HandleMachineMove(stashedInputMove, stalledEngine);
13671 stalledEngine = NULL;
13674 if (gameMode == MachinePlaysWhite ||
13675 gameMode == TwoMachinesPlay ||
13676 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13677 if(first.pause) UnPauseEngine(&first);
13678 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13679 if(second.pause) UnPauseEngine(&second);
13680 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13683 DisplayBothClocks();
13685 if (gameMode == PlayFromGameFile) {
13686 if (appData.timeDelay >= 0)
13687 AutoPlayGameLoop();
13688 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13689 Reset(FALSE, TRUE);
13690 SendToICS(ics_prefix);
13691 SendToICS("refresh\n");
13692 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13693 ForwardInner(forwardMostMove);
13695 pauseExamInvalid = FALSE;
13697 switch (gameMode) {
13701 pauseExamForwardMostMove = forwardMostMove;
13702 pauseExamInvalid = FALSE;
13705 case IcsPlayingWhite:
13706 case IcsPlayingBlack:
13710 case PlayFromGameFile:
13711 (void) StopLoadGameTimer();
13715 case BeginningOfGame:
13716 if (appData.icsActive) return;
13717 /* else fall through */
13718 case MachinePlaysWhite:
13719 case MachinePlaysBlack:
13720 case TwoMachinesPlay:
13721 if (forwardMostMove == 0)
13722 return; /* don't pause if no one has moved */
13723 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13724 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13725 if(onMove->pause) { // thinking engine can be paused
13726 PauseEngine(onMove); // do it
13727 if(onMove->other->pause) // pondering opponent can always be paused immediately
13728 PauseEngine(onMove->other);
13730 SendToProgram("easy\n", onMove->other);
13732 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13733 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13735 PauseEngine(&first);
13737 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13738 } else { // human on move, pause pondering by either method
13740 PauseEngine(&first);
13741 else if(appData.ponderNextMove)
13742 SendToProgram("easy\n", &first);
13745 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13755 EditCommentEvent ()
13757 char title[MSG_SIZ];
13759 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13760 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13762 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13763 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13764 parseList[currentMove - 1]);
13767 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13774 char *tags = PGNTags(&gameInfo);
13776 EditTagsPopUp(tags, NULL);
13783 if(second.analyzing) {
13784 SendToProgram("exit\n", &second);
13785 second.analyzing = FALSE;
13787 if (second.pr == NoProc) StartChessProgram(&second);
13788 InitChessProgram(&second, FALSE);
13789 FeedMovesToProgram(&second, currentMove);
13791 SendToProgram("analyze\n", &second);
13792 second.analyzing = TRUE;
13796 /* Toggle ShowThinking */
13798 ToggleShowThinking()
13800 appData.showThinking = !appData.showThinking;
13801 ShowThinkingEvent();
13805 AnalyzeModeEvent ()
13809 if (!first.analysisSupport) {
13810 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13811 DisplayError(buf, 0);
13814 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13815 if (appData.icsActive) {
13816 if (gameMode != IcsObserving) {
13817 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13818 DisplayError(buf, 0);
13820 if (appData.icsEngineAnalyze) {
13821 if (appData.debugMode)
13822 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13828 /* if enable, user wants to disable icsEngineAnalyze */
13829 if (appData.icsEngineAnalyze) {
13834 appData.icsEngineAnalyze = TRUE;
13835 if (appData.debugMode)
13836 fprintf(debugFP, "ICS engine analyze starting... \n");
13839 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13840 if (appData.noChessProgram || gameMode == AnalyzeMode)
13843 if (gameMode != AnalyzeFile) {
13844 if (!appData.icsEngineAnalyze) {
13846 if (gameMode != EditGame) return 0;
13848 if (!appData.showThinking) ToggleShowThinking();
13849 ResurrectChessProgram();
13850 SendToProgram("analyze\n", &first);
13851 first.analyzing = TRUE;
13852 /*first.maybeThinking = TRUE;*/
13853 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13854 EngineOutputPopUp();
13856 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13861 StartAnalysisClock();
13862 GetTimeMark(&lastNodeCountTime);
13868 AnalyzeFileEvent ()
13870 if (appData.noChessProgram || gameMode == AnalyzeFile)
13873 if (!first.analysisSupport) {
13875 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13876 DisplayError(buf, 0);
13880 if (gameMode != AnalyzeMode) {
13881 keepInfo = 1; // mere annotating should not alter PGN tags
13884 if (gameMode != EditGame) return;
13885 if (!appData.showThinking) ToggleShowThinking();
13886 ResurrectChessProgram();
13887 SendToProgram("analyze\n", &first);
13888 first.analyzing = TRUE;
13889 /*first.maybeThinking = TRUE;*/
13890 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13891 EngineOutputPopUp();
13893 gameMode = AnalyzeFile;
13897 StartAnalysisClock();
13898 GetTimeMark(&lastNodeCountTime);
13900 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13901 AnalysisPeriodicEvent(1);
13905 MachineWhiteEvent ()
13908 char *bookHit = NULL;
13910 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13914 if (gameMode == PlayFromGameFile ||
13915 gameMode == TwoMachinesPlay ||
13916 gameMode == Training ||
13917 gameMode == AnalyzeMode ||
13918 gameMode == EndOfGame)
13921 if (gameMode == EditPosition)
13922 EditPositionDone(TRUE);
13924 if (!WhiteOnMove(currentMove)) {
13925 DisplayError(_("It is not White's turn"), 0);
13929 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13932 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13933 gameMode == AnalyzeFile)
13936 ResurrectChessProgram(); /* in case it isn't running */
13937 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13938 gameMode = MachinePlaysWhite;
13941 gameMode = MachinePlaysWhite;
13945 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13947 if (first.sendName) {
13948 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13949 SendToProgram(buf, &first);
13951 if (first.sendTime) {
13952 if (first.useColors) {
13953 SendToProgram("black\n", &first); /*gnu kludge*/
13955 SendTimeRemaining(&first, TRUE);
13957 if (first.useColors) {
13958 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13960 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13961 SetMachineThinkingEnables();
13962 first.maybeThinking = TRUE;
13966 if (appData.autoFlipView && !flipView) {
13967 flipView = !flipView;
13968 DrawPosition(FALSE, NULL);
13969 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13972 if(bookHit) { // [HGM] book: simulate book reply
13973 static char bookMove[MSG_SIZ]; // a bit generous?
13975 programStats.nodes = programStats.depth = programStats.time =
13976 programStats.score = programStats.got_only_move = 0;
13977 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13979 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13980 strcat(bookMove, bookHit);
13981 HandleMachineMove(bookMove, &first);
13986 MachineBlackEvent ()
13989 char *bookHit = NULL;
13991 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13995 if (gameMode == PlayFromGameFile ||
13996 gameMode == TwoMachinesPlay ||
13997 gameMode == Training ||
13998 gameMode == AnalyzeMode ||
13999 gameMode == EndOfGame)
14002 if (gameMode == EditPosition)
14003 EditPositionDone(TRUE);
14005 if (WhiteOnMove(currentMove)) {
14006 DisplayError(_("It is not Black's turn"), 0);
14010 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14013 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14014 gameMode == AnalyzeFile)
14017 ResurrectChessProgram(); /* in case it isn't running */
14018 gameMode = MachinePlaysBlack;
14022 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14024 if (first.sendName) {
14025 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14026 SendToProgram(buf, &first);
14028 if (first.sendTime) {
14029 if (first.useColors) {
14030 SendToProgram("white\n", &first); /*gnu kludge*/
14032 SendTimeRemaining(&first, FALSE);
14034 if (first.useColors) {
14035 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14037 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14038 SetMachineThinkingEnables();
14039 first.maybeThinking = TRUE;
14042 if (appData.autoFlipView && flipView) {
14043 flipView = !flipView;
14044 DrawPosition(FALSE, NULL);
14045 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14047 if(bookHit) { // [HGM] book: simulate book reply
14048 static char bookMove[MSG_SIZ]; // a bit generous?
14050 programStats.nodes = programStats.depth = programStats.time =
14051 programStats.score = programStats.got_only_move = 0;
14052 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14054 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14055 strcat(bookMove, bookHit);
14056 HandleMachineMove(bookMove, &first);
14062 DisplayTwoMachinesTitle ()
14065 if (appData.matchGames > 0) {
14066 if(appData.tourneyFile[0]) {
14067 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14068 gameInfo.white, _("vs."), gameInfo.black,
14069 nextGame+1, appData.matchGames+1,
14070 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14072 if (first.twoMachinesColor[0] == 'w') {
14073 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14074 gameInfo.white, _("vs."), gameInfo.black,
14075 first.matchWins, second.matchWins,
14076 matchGame - 1 - (first.matchWins + second.matchWins));
14078 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14079 gameInfo.white, _("vs."), gameInfo.black,
14080 second.matchWins, first.matchWins,
14081 matchGame - 1 - (first.matchWins + second.matchWins));
14084 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14090 SettingsMenuIfReady ()
14092 if (second.lastPing != second.lastPong) {
14093 DisplayMessage("", _("Waiting for second chess program"));
14094 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14098 DisplayMessage("", "");
14099 SettingsPopUp(&second);
14103 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14106 if (cps->pr == NoProc) {
14107 StartChessProgram(cps);
14108 if (cps->protocolVersion == 1) {
14110 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14112 /* kludge: allow timeout for initial "feature" command */
14113 if(retry != TwoMachinesEventIfReady) FreezeUI();
14114 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14115 DisplayMessage("", buf);
14116 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14124 TwoMachinesEvent P((void))
14128 ChessProgramState *onmove;
14129 char *bookHit = NULL;
14130 static int stalling = 0;
14134 if (appData.noChessProgram) return;
14136 switch (gameMode) {
14137 case TwoMachinesPlay:
14139 case MachinePlaysWhite:
14140 case MachinePlaysBlack:
14141 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14142 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14146 case BeginningOfGame:
14147 case PlayFromGameFile:
14150 if (gameMode != EditGame) return;
14153 EditPositionDone(TRUE);
14164 // forwardMostMove = currentMove;
14165 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14166 startingEngine = TRUE;
14168 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14170 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14171 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14172 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14175 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14177 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14178 startingEngine = FALSE;
14179 DisplayError("second engine does not play this", 0);
14184 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14185 SendToProgram("force\n", &second);
14187 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14190 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14191 if(appData.matchPause>10000 || appData.matchPause<10)
14192 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14193 wait = SubtractTimeMarks(&now, &pauseStart);
14194 if(wait < appData.matchPause) {
14195 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14198 // we are now committed to starting the game
14200 DisplayMessage("", "");
14201 if (startedFromSetupPosition) {
14202 SendBoard(&second, backwardMostMove);
14203 if (appData.debugMode) {
14204 fprintf(debugFP, "Two Machines\n");
14207 for (i = backwardMostMove; i < forwardMostMove; i++) {
14208 SendMoveToProgram(i, &second);
14211 gameMode = TwoMachinesPlay;
14212 pausing = startingEngine = FALSE;
14213 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14215 DisplayTwoMachinesTitle();
14217 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14222 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14223 SendToProgram(first.computerString, &first);
14224 if (first.sendName) {
14225 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14226 SendToProgram(buf, &first);
14228 SendToProgram(second.computerString, &second);
14229 if (second.sendName) {
14230 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14231 SendToProgram(buf, &second);
14235 if (!first.sendTime || !second.sendTime) {
14236 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14237 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14239 if (onmove->sendTime) {
14240 if (onmove->useColors) {
14241 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14243 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14245 if (onmove->useColors) {
14246 SendToProgram(onmove->twoMachinesColor, onmove);
14248 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14249 // SendToProgram("go\n", onmove);
14250 onmove->maybeThinking = TRUE;
14251 SetMachineThinkingEnables();
14255 if(bookHit) { // [HGM] book: simulate book reply
14256 static char bookMove[MSG_SIZ]; // a bit generous?
14258 programStats.nodes = programStats.depth = programStats.time =
14259 programStats.score = programStats.got_only_move = 0;
14260 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14262 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14263 strcat(bookMove, bookHit);
14264 savedMessage = bookMove; // args for deferred call
14265 savedState = onmove;
14266 ScheduleDelayedEvent(DeferredBookMove, 1);
14273 if (gameMode == Training) {
14274 SetTrainingModeOff();
14275 gameMode = PlayFromGameFile;
14276 DisplayMessage("", _("Training mode off"));
14278 gameMode = Training;
14279 animateTraining = appData.animate;
14281 /* make sure we are not already at the end of the game */
14282 if (currentMove < forwardMostMove) {
14283 SetTrainingModeOn();
14284 DisplayMessage("", _("Training mode on"));
14286 gameMode = PlayFromGameFile;
14287 DisplayError(_("Already at end of game"), 0);
14296 if (!appData.icsActive) return;
14297 switch (gameMode) {
14298 case IcsPlayingWhite:
14299 case IcsPlayingBlack:
14302 case BeginningOfGame:
14310 EditPositionDone(TRUE);
14323 gameMode = IcsIdle;
14333 switch (gameMode) {
14335 SetTrainingModeOff();
14337 case MachinePlaysWhite:
14338 case MachinePlaysBlack:
14339 case BeginningOfGame:
14340 SendToProgram("force\n", &first);
14341 SetUserThinkingEnables();
14343 case PlayFromGameFile:
14344 (void) StopLoadGameTimer();
14345 if (gameFileFP != NULL) {
14350 EditPositionDone(TRUE);
14355 SendToProgram("force\n", &first);
14357 case TwoMachinesPlay:
14358 GameEnds(EndOfFile, NULL, GE_PLAYER);
14359 ResurrectChessProgram();
14360 SetUserThinkingEnables();
14363 ResurrectChessProgram();
14365 case IcsPlayingBlack:
14366 case IcsPlayingWhite:
14367 DisplayError(_("Warning: You are still playing a game"), 0);
14370 DisplayError(_("Warning: You are still observing a game"), 0);
14373 DisplayError(_("Warning: You are still examining a game"), 0);
14384 first.offeredDraw = second.offeredDraw = 0;
14386 if (gameMode == PlayFromGameFile) {
14387 whiteTimeRemaining = timeRemaining[0][currentMove];
14388 blackTimeRemaining = timeRemaining[1][currentMove];
14392 if (gameMode == MachinePlaysWhite ||
14393 gameMode == MachinePlaysBlack ||
14394 gameMode == TwoMachinesPlay ||
14395 gameMode == EndOfGame) {
14396 i = forwardMostMove;
14397 while (i > currentMove) {
14398 SendToProgram("undo\n", &first);
14401 if(!adjustedClock) {
14402 whiteTimeRemaining = timeRemaining[0][currentMove];
14403 blackTimeRemaining = timeRemaining[1][currentMove];
14404 DisplayBothClocks();
14406 if (whiteFlag || blackFlag) {
14407 whiteFlag = blackFlag = 0;
14412 gameMode = EditGame;
14419 EditPositionEvent ()
14421 if (gameMode == EditPosition) {
14427 if (gameMode != EditGame) return;
14429 gameMode = EditPosition;
14432 if (currentMove > 0)
14433 CopyBoard(boards[0], boards[currentMove]);
14435 blackPlaysFirst = !WhiteOnMove(currentMove);
14437 currentMove = forwardMostMove = backwardMostMove = 0;
14438 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14440 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14446 /* [DM] icsEngineAnalyze - possible call from other functions */
14447 if (appData.icsEngineAnalyze) {
14448 appData.icsEngineAnalyze = FALSE;
14450 DisplayMessage("",_("Close ICS engine analyze..."));
14452 if (first.analysisSupport && first.analyzing) {
14453 SendToBoth("exit\n");
14454 first.analyzing = second.analyzing = FALSE;
14456 thinkOutput[0] = NULLCHAR;
14460 EditPositionDone (Boolean fakeRights)
14462 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14464 startedFromSetupPosition = TRUE;
14465 InitChessProgram(&first, FALSE);
14466 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14467 boards[0][EP_STATUS] = EP_NONE;
14468 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14469 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14470 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14471 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14472 } else boards[0][CASTLING][2] = NoRights;
14473 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14474 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14475 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14476 } else boards[0][CASTLING][5] = NoRights;
14477 if(gameInfo.variant == VariantSChess) {
14479 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14480 boards[0][VIRGIN][i] = 0;
14481 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14482 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14486 SendToProgram("force\n", &first);
14487 if (blackPlaysFirst) {
14488 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14489 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14490 currentMove = forwardMostMove = backwardMostMove = 1;
14491 CopyBoard(boards[1], boards[0]);
14493 currentMove = forwardMostMove = backwardMostMove = 0;
14495 SendBoard(&first, forwardMostMove);
14496 if (appData.debugMode) {
14497 fprintf(debugFP, "EditPosDone\n");
14500 DisplayMessage("", "");
14501 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14502 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14503 gameMode = EditGame;
14505 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14506 ClearHighlights(); /* [AS] */
14509 /* Pause for `ms' milliseconds */
14510 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14512 TimeDelay (long ms)
14519 } while (SubtractTimeMarks(&m2, &m1) < ms);
14522 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14524 SendMultiLineToICS (char *buf)
14526 char temp[MSG_SIZ+1], *p;
14533 strncpy(temp, buf, len);
14538 if (*p == '\n' || *p == '\r')
14543 strcat(temp, "\n");
14545 SendToPlayer(temp, strlen(temp));
14549 SetWhiteToPlayEvent ()
14551 if (gameMode == EditPosition) {
14552 blackPlaysFirst = FALSE;
14553 DisplayBothClocks(); /* works because currentMove is 0 */
14554 } else if (gameMode == IcsExamining) {
14555 SendToICS(ics_prefix);
14556 SendToICS("tomove white\n");
14561 SetBlackToPlayEvent ()
14563 if (gameMode == EditPosition) {
14564 blackPlaysFirst = TRUE;
14565 currentMove = 1; /* kludge */
14566 DisplayBothClocks();
14568 } else if (gameMode == IcsExamining) {
14569 SendToICS(ics_prefix);
14570 SendToICS("tomove black\n");
14575 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14578 ChessSquare piece = boards[0][y][x];
14580 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14582 switch (selection) {
14584 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14585 SendToICS(ics_prefix);
14586 SendToICS("bsetup clear\n");
14587 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14588 SendToICS(ics_prefix);
14589 SendToICS("clearboard\n");
14591 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14592 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14593 for (y = 0; y < BOARD_HEIGHT; y++) {
14594 if (gameMode == IcsExamining) {
14595 if (boards[currentMove][y][x] != EmptySquare) {
14596 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14601 boards[0][y][x] = p;
14606 if (gameMode == EditPosition) {
14607 DrawPosition(FALSE, boards[0]);
14612 SetWhiteToPlayEvent();
14616 SetBlackToPlayEvent();
14620 if (gameMode == IcsExamining) {
14621 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14622 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14625 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14626 if(x == BOARD_LEFT-2) {
14627 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14628 boards[0][y][1] = 0;
14630 if(x == BOARD_RGHT+1) {
14631 if(y >= gameInfo.holdingsSize) break;
14632 boards[0][y][BOARD_WIDTH-2] = 0;
14635 boards[0][y][x] = EmptySquare;
14636 DrawPosition(FALSE, boards[0]);
14641 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14642 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14643 selection = (ChessSquare) (PROMOTED piece);
14644 } else if(piece == EmptySquare) selection = WhiteSilver;
14645 else selection = (ChessSquare)((int)piece - 1);
14649 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14650 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14651 selection = (ChessSquare) (DEMOTED piece);
14652 } else if(piece == EmptySquare) selection = BlackSilver;
14653 else selection = (ChessSquare)((int)piece + 1);
14658 if(gameInfo.variant == VariantShatranj ||
14659 gameInfo.variant == VariantXiangqi ||
14660 gameInfo.variant == VariantCourier ||
14661 gameInfo.variant == VariantASEAN ||
14662 gameInfo.variant == VariantMakruk )
14663 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14668 if(gameInfo.variant == VariantXiangqi)
14669 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14670 if(gameInfo.variant == VariantKnightmate)
14671 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14674 if (gameMode == IcsExamining) {
14675 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14676 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14677 PieceToChar(selection), AAA + x, ONE + y);
14680 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14682 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14683 n = PieceToNumber(selection - BlackPawn);
14684 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14685 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14686 boards[0][BOARD_HEIGHT-1-n][1]++;
14688 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14689 n = PieceToNumber(selection);
14690 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14691 boards[0][n][BOARD_WIDTH-1] = selection;
14692 boards[0][n][BOARD_WIDTH-2]++;
14695 boards[0][y][x] = selection;
14696 DrawPosition(TRUE, boards[0]);
14698 fromX = fromY = -1;
14706 DropMenuEvent (ChessSquare selection, int x, int y)
14708 ChessMove moveType;
14710 switch (gameMode) {
14711 case IcsPlayingWhite:
14712 case MachinePlaysBlack:
14713 if (!WhiteOnMove(currentMove)) {
14714 DisplayMoveError(_("It is Black's turn"));
14717 moveType = WhiteDrop;
14719 case IcsPlayingBlack:
14720 case MachinePlaysWhite:
14721 if (WhiteOnMove(currentMove)) {
14722 DisplayMoveError(_("It is White's turn"));
14725 moveType = BlackDrop;
14728 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14734 if (moveType == BlackDrop && selection < BlackPawn) {
14735 selection = (ChessSquare) ((int) selection
14736 + (int) BlackPawn - (int) WhitePawn);
14738 if (boards[currentMove][y][x] != EmptySquare) {
14739 DisplayMoveError(_("That square is occupied"));
14743 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14749 /* Accept a pending offer of any kind from opponent */
14751 if (appData.icsActive) {
14752 SendToICS(ics_prefix);
14753 SendToICS("accept\n");
14754 } else if (cmailMsgLoaded) {
14755 if (currentMove == cmailOldMove &&
14756 commentList[cmailOldMove] != NULL &&
14757 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14758 "Black offers a draw" : "White offers a draw")) {
14760 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14761 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14763 DisplayError(_("There is no pending offer on this move"), 0);
14764 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14767 /* Not used for offers from chess program */
14774 /* Decline a pending offer of any kind from opponent */
14776 if (appData.icsActive) {
14777 SendToICS(ics_prefix);
14778 SendToICS("decline\n");
14779 } else if (cmailMsgLoaded) {
14780 if (currentMove == cmailOldMove &&
14781 commentList[cmailOldMove] != NULL &&
14782 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14783 "Black offers a draw" : "White offers a draw")) {
14785 AppendComment(cmailOldMove, "Draw declined", TRUE);
14786 DisplayComment(cmailOldMove - 1, "Draw declined");
14789 DisplayError(_("There is no pending offer on this move"), 0);
14792 /* Not used for offers from chess program */
14799 /* Issue ICS rematch command */
14800 if (appData.icsActive) {
14801 SendToICS(ics_prefix);
14802 SendToICS("rematch\n");
14809 /* Call your opponent's flag (claim a win on time) */
14810 if (appData.icsActive) {
14811 SendToICS(ics_prefix);
14812 SendToICS("flag\n");
14814 switch (gameMode) {
14817 case MachinePlaysWhite:
14820 GameEnds(GameIsDrawn, "Both players ran out of time",
14823 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14825 DisplayError(_("Your opponent is not out of time"), 0);
14828 case MachinePlaysBlack:
14831 GameEnds(GameIsDrawn, "Both players ran out of time",
14834 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14836 DisplayError(_("Your opponent is not out of time"), 0);
14844 ClockClick (int which)
14845 { // [HGM] code moved to back-end from winboard.c
14846 if(which) { // black clock
14847 if (gameMode == EditPosition || gameMode == IcsExamining) {
14848 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14849 SetBlackToPlayEvent();
14850 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14851 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14852 } else if (shiftKey) {
14853 AdjustClock(which, -1);
14854 } else if (gameMode == IcsPlayingWhite ||
14855 gameMode == MachinePlaysBlack) {
14858 } else { // white clock
14859 if (gameMode == EditPosition || gameMode == IcsExamining) {
14860 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14861 SetWhiteToPlayEvent();
14862 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14863 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14864 } else if (shiftKey) {
14865 AdjustClock(which, -1);
14866 } else if (gameMode == IcsPlayingBlack ||
14867 gameMode == MachinePlaysWhite) {
14876 /* Offer draw or accept pending draw offer from opponent */
14878 if (appData.icsActive) {
14879 /* Note: tournament rules require draw offers to be
14880 made after you make your move but before you punch
14881 your clock. Currently ICS doesn't let you do that;
14882 instead, you immediately punch your clock after making
14883 a move, but you can offer a draw at any time. */
14885 SendToICS(ics_prefix);
14886 SendToICS("draw\n");
14887 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14888 } else if (cmailMsgLoaded) {
14889 if (currentMove == cmailOldMove &&
14890 commentList[cmailOldMove] != NULL &&
14891 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14892 "Black offers a draw" : "White offers a draw")) {
14893 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14894 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14895 } else if (currentMove == cmailOldMove + 1) {
14896 char *offer = WhiteOnMove(cmailOldMove) ?
14897 "White offers a draw" : "Black offers a draw";
14898 AppendComment(currentMove, offer, TRUE);
14899 DisplayComment(currentMove - 1, offer);
14900 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14902 DisplayError(_("You must make your move before offering a draw"), 0);
14903 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14905 } else if (first.offeredDraw) {
14906 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14908 if (first.sendDrawOffers) {
14909 SendToProgram("draw\n", &first);
14910 userOfferedDraw = TRUE;
14918 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14920 if (appData.icsActive) {
14921 SendToICS(ics_prefix);
14922 SendToICS("adjourn\n");
14924 /* Currently GNU Chess doesn't offer or accept Adjourns */
14932 /* Offer Abort or accept pending Abort offer from opponent */
14934 if (appData.icsActive) {
14935 SendToICS(ics_prefix);
14936 SendToICS("abort\n");
14938 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14945 /* Resign. You can do this even if it's not your turn. */
14947 if (appData.icsActive) {
14948 SendToICS(ics_prefix);
14949 SendToICS("resign\n");
14951 switch (gameMode) {
14952 case MachinePlaysWhite:
14953 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14955 case MachinePlaysBlack:
14956 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14959 if (cmailMsgLoaded) {
14961 if (WhiteOnMove(cmailOldMove)) {
14962 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14964 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14966 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14977 StopObservingEvent ()
14979 /* Stop observing current games */
14980 SendToICS(ics_prefix);
14981 SendToICS("unobserve\n");
14985 StopExaminingEvent ()
14987 /* Stop observing current game */
14988 SendToICS(ics_prefix);
14989 SendToICS("unexamine\n");
14993 ForwardInner (int target)
14995 int limit; int oldSeekGraphUp = seekGraphUp;
14997 if (appData.debugMode)
14998 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14999 target, currentMove, forwardMostMove);
15001 if (gameMode == EditPosition)
15004 seekGraphUp = FALSE;
15005 MarkTargetSquares(1);
15007 if (gameMode == PlayFromGameFile && !pausing)
15010 if (gameMode == IcsExamining && pausing)
15011 limit = pauseExamForwardMostMove;
15013 limit = forwardMostMove;
15015 if (target > limit) target = limit;
15017 if (target > 0 && moveList[target - 1][0]) {
15018 int fromX, fromY, toX, toY;
15019 toX = moveList[target - 1][2] - AAA;
15020 toY = moveList[target - 1][3] - ONE;
15021 if (moveList[target - 1][1] == '@') {
15022 if (appData.highlightLastMove) {
15023 SetHighlights(-1, -1, toX, toY);
15026 fromX = moveList[target - 1][0] - AAA;
15027 fromY = moveList[target - 1][1] - ONE;
15028 if (target == currentMove + 1) {
15029 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15031 if (appData.highlightLastMove) {
15032 SetHighlights(fromX, fromY, toX, toY);
15036 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15037 gameMode == Training || gameMode == PlayFromGameFile ||
15038 gameMode == AnalyzeFile) {
15039 while (currentMove < target) {
15040 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15041 SendMoveToProgram(currentMove++, &first);
15044 currentMove = target;
15047 if (gameMode == EditGame || gameMode == EndOfGame) {
15048 whiteTimeRemaining = timeRemaining[0][currentMove];
15049 blackTimeRemaining = timeRemaining[1][currentMove];
15051 DisplayBothClocks();
15052 DisplayMove(currentMove - 1);
15053 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15054 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15055 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15056 DisplayComment(currentMove - 1, commentList[currentMove]);
15058 ClearMap(); // [HGM] exclude: invalidate map
15065 if (gameMode == IcsExamining && !pausing) {
15066 SendToICS(ics_prefix);
15067 SendToICS("forward\n");
15069 ForwardInner(currentMove + 1);
15076 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15077 /* to optimze, we temporarily turn off analysis mode while we feed
15078 * the remaining moves to the engine. Otherwise we get analysis output
15081 if (first.analysisSupport) {
15082 SendToProgram("exit\nforce\n", &first);
15083 first.analyzing = FALSE;
15087 if (gameMode == IcsExamining && !pausing) {
15088 SendToICS(ics_prefix);
15089 SendToICS("forward 999999\n");
15091 ForwardInner(forwardMostMove);
15094 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15095 /* we have fed all the moves, so reactivate analysis mode */
15096 SendToProgram("analyze\n", &first);
15097 first.analyzing = TRUE;
15098 /*first.maybeThinking = TRUE;*/
15099 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15104 BackwardInner (int target)
15106 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15108 if (appData.debugMode)
15109 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15110 target, currentMove, forwardMostMove);
15112 if (gameMode == EditPosition) return;
15113 seekGraphUp = FALSE;
15114 MarkTargetSquares(1);
15115 if (currentMove <= backwardMostMove) {
15117 DrawPosition(full_redraw, boards[currentMove]);
15120 if (gameMode == PlayFromGameFile && !pausing)
15123 if (moveList[target][0]) {
15124 int fromX, fromY, toX, toY;
15125 toX = moveList[target][2] - AAA;
15126 toY = moveList[target][3] - ONE;
15127 if (moveList[target][1] == '@') {
15128 if (appData.highlightLastMove) {
15129 SetHighlights(-1, -1, toX, toY);
15132 fromX = moveList[target][0] - AAA;
15133 fromY = moveList[target][1] - ONE;
15134 if (target == currentMove - 1) {
15135 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15137 if (appData.highlightLastMove) {
15138 SetHighlights(fromX, fromY, toX, toY);
15142 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15143 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15144 while (currentMove > target) {
15145 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15146 // null move cannot be undone. Reload program with move history before it.
15148 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15149 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15151 SendBoard(&first, i);
15152 if(second.analyzing) SendBoard(&second, i);
15153 for(currentMove=i; currentMove<target; currentMove++) {
15154 SendMoveToProgram(currentMove, &first);
15155 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15159 SendToBoth("undo\n");
15163 currentMove = target;
15166 if (gameMode == EditGame || gameMode == EndOfGame) {
15167 whiteTimeRemaining = timeRemaining[0][currentMove];
15168 blackTimeRemaining = timeRemaining[1][currentMove];
15170 DisplayBothClocks();
15171 DisplayMove(currentMove - 1);
15172 DrawPosition(full_redraw, boards[currentMove]);
15173 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15174 // [HGM] PV info: routine tests if comment empty
15175 DisplayComment(currentMove - 1, commentList[currentMove]);
15176 ClearMap(); // [HGM] exclude: invalidate map
15182 if (gameMode == IcsExamining && !pausing) {
15183 SendToICS(ics_prefix);
15184 SendToICS("backward\n");
15186 BackwardInner(currentMove - 1);
15193 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15194 /* to optimize, we temporarily turn off analysis mode while we undo
15195 * all the moves. Otherwise we get analysis output after each undo.
15197 if (first.analysisSupport) {
15198 SendToProgram("exit\nforce\n", &first);
15199 first.analyzing = FALSE;
15203 if (gameMode == IcsExamining && !pausing) {
15204 SendToICS(ics_prefix);
15205 SendToICS("backward 999999\n");
15207 BackwardInner(backwardMostMove);
15210 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15211 /* we have fed all the moves, so reactivate analysis mode */
15212 SendToProgram("analyze\n", &first);
15213 first.analyzing = TRUE;
15214 /*first.maybeThinking = TRUE;*/
15215 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15222 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15223 if (to >= forwardMostMove) to = forwardMostMove;
15224 if (to <= backwardMostMove) to = backwardMostMove;
15225 if (to < currentMove) {
15233 RevertEvent (Boolean annotate)
15235 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15238 if (gameMode != IcsExamining) {
15239 DisplayError(_("You are not examining a game"), 0);
15243 DisplayError(_("You can't revert while pausing"), 0);
15246 SendToICS(ics_prefix);
15247 SendToICS("revert\n");
15251 RetractMoveEvent ()
15253 switch (gameMode) {
15254 case MachinePlaysWhite:
15255 case MachinePlaysBlack:
15256 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15257 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15260 if (forwardMostMove < 2) return;
15261 currentMove = forwardMostMove = forwardMostMove - 2;
15262 whiteTimeRemaining = timeRemaining[0][currentMove];
15263 blackTimeRemaining = timeRemaining[1][currentMove];
15264 DisplayBothClocks();
15265 DisplayMove(currentMove - 1);
15266 ClearHighlights();/*!! could figure this out*/
15267 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15268 SendToProgram("remove\n", &first);
15269 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15272 case BeginningOfGame:
15276 case IcsPlayingWhite:
15277 case IcsPlayingBlack:
15278 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15279 SendToICS(ics_prefix);
15280 SendToICS("takeback 2\n");
15282 SendToICS(ics_prefix);
15283 SendToICS("takeback 1\n");
15292 ChessProgramState *cps;
15294 switch (gameMode) {
15295 case MachinePlaysWhite:
15296 if (!WhiteOnMove(forwardMostMove)) {
15297 DisplayError(_("It is your turn"), 0);
15302 case MachinePlaysBlack:
15303 if (WhiteOnMove(forwardMostMove)) {
15304 DisplayError(_("It is your turn"), 0);
15309 case TwoMachinesPlay:
15310 if (WhiteOnMove(forwardMostMove) ==
15311 (first.twoMachinesColor[0] == 'w')) {
15317 case BeginningOfGame:
15321 SendToProgram("?\n", cps);
15325 TruncateGameEvent ()
15328 if (gameMode != EditGame) return;
15335 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15336 if (forwardMostMove > currentMove) {
15337 if (gameInfo.resultDetails != NULL) {
15338 free(gameInfo.resultDetails);
15339 gameInfo.resultDetails = NULL;
15340 gameInfo.result = GameUnfinished;
15342 forwardMostMove = currentMove;
15343 HistorySet(parseList, backwardMostMove, forwardMostMove,
15351 if (appData.noChessProgram) return;
15352 switch (gameMode) {
15353 case MachinePlaysWhite:
15354 if (WhiteOnMove(forwardMostMove)) {
15355 DisplayError(_("Wait until your turn"), 0);
15359 case BeginningOfGame:
15360 case MachinePlaysBlack:
15361 if (!WhiteOnMove(forwardMostMove)) {
15362 DisplayError(_("Wait until your turn"), 0);
15367 DisplayError(_("No hint available"), 0);
15370 SendToProgram("hint\n", &first);
15371 hintRequested = TRUE;
15377 ListGame * lg = (ListGame *) gameList.head;
15380 static int secondTime = FALSE;
15382 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15383 DisplayError(_("Game list not loaded or empty"), 0);
15387 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15390 DisplayNote(_("Book file exists! Try again for overwrite."));
15394 creatingBook = TRUE;
15395 secondTime = FALSE;
15397 /* Get list size */
15398 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15399 LoadGame(f, nItem, "", TRUE);
15400 AddGameToBook(TRUE);
15401 lg = (ListGame *) lg->node.succ;
15404 creatingBook = FALSE;
15411 if (appData.noChessProgram) return;
15412 switch (gameMode) {
15413 case MachinePlaysWhite:
15414 if (WhiteOnMove(forwardMostMove)) {
15415 DisplayError(_("Wait until your turn"), 0);
15419 case BeginningOfGame:
15420 case MachinePlaysBlack:
15421 if (!WhiteOnMove(forwardMostMove)) {
15422 DisplayError(_("Wait until your turn"), 0);
15427 EditPositionDone(TRUE);
15429 case TwoMachinesPlay:
15434 SendToProgram("bk\n", &first);
15435 bookOutput[0] = NULLCHAR;
15436 bookRequested = TRUE;
15442 char *tags = PGNTags(&gameInfo);
15443 TagsPopUp(tags, CmailMsg());
15447 /* end button procedures */
15450 PrintPosition (FILE *fp, int move)
15454 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15455 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15456 char c = PieceToChar(boards[move][i][j]);
15457 fputc(c == 'x' ? '.' : c, fp);
15458 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15461 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15462 fprintf(fp, "white to play\n");
15464 fprintf(fp, "black to play\n");
15468 PrintOpponents (FILE *fp)
15470 if (gameInfo.white != NULL) {
15471 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15477 /* Find last component of program's own name, using some heuristics */
15479 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15482 int local = (strcmp(host, "localhost") == 0);
15483 while (!local && (p = strchr(prog, ';')) != NULL) {
15485 while (*p == ' ') p++;
15488 if (*prog == '"' || *prog == '\'') {
15489 q = strchr(prog + 1, *prog);
15491 q = strchr(prog, ' ');
15493 if (q == NULL) q = prog + strlen(prog);
15495 while (p >= prog && *p != '/' && *p != '\\') p--;
15497 if(p == prog && *p == '"') p++;
15499 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15500 memcpy(buf, p, q - p);
15501 buf[q - p] = NULLCHAR;
15509 TimeControlTagValue ()
15512 if (!appData.clockMode) {
15513 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15514 } else if (movesPerSession > 0) {
15515 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15516 } else if (timeIncrement == 0) {
15517 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15519 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15521 return StrSave(buf);
15527 /* This routine is used only for certain modes */
15528 VariantClass v = gameInfo.variant;
15529 ChessMove r = GameUnfinished;
15532 if(keepInfo) return;
15534 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15535 r = gameInfo.result;
15536 p = gameInfo.resultDetails;
15537 gameInfo.resultDetails = NULL;
15539 ClearGameInfo(&gameInfo);
15540 gameInfo.variant = v;
15542 switch (gameMode) {
15543 case MachinePlaysWhite:
15544 gameInfo.event = StrSave( appData.pgnEventHeader );
15545 gameInfo.site = StrSave(HostName());
15546 gameInfo.date = PGNDate();
15547 gameInfo.round = StrSave("-");
15548 gameInfo.white = StrSave(first.tidy);
15549 gameInfo.black = StrSave(UserName());
15550 gameInfo.timeControl = TimeControlTagValue();
15553 case MachinePlaysBlack:
15554 gameInfo.event = StrSave( appData.pgnEventHeader );
15555 gameInfo.site = StrSave(HostName());
15556 gameInfo.date = PGNDate();
15557 gameInfo.round = StrSave("-");
15558 gameInfo.white = StrSave(UserName());
15559 gameInfo.black = StrSave(first.tidy);
15560 gameInfo.timeControl = TimeControlTagValue();
15563 case TwoMachinesPlay:
15564 gameInfo.event = StrSave( appData.pgnEventHeader );
15565 gameInfo.site = StrSave(HostName());
15566 gameInfo.date = PGNDate();
15569 snprintf(buf, MSG_SIZ, "%d", roundNr);
15570 gameInfo.round = StrSave(buf);
15572 gameInfo.round = StrSave("-");
15574 if (first.twoMachinesColor[0] == 'w') {
15575 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15576 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15578 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15579 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15581 gameInfo.timeControl = TimeControlTagValue();
15585 gameInfo.event = StrSave("Edited game");
15586 gameInfo.site = StrSave(HostName());
15587 gameInfo.date = PGNDate();
15588 gameInfo.round = StrSave("-");
15589 gameInfo.white = StrSave("-");
15590 gameInfo.black = StrSave("-");
15591 gameInfo.result = r;
15592 gameInfo.resultDetails = p;
15596 gameInfo.event = StrSave("Edited position");
15597 gameInfo.site = StrSave(HostName());
15598 gameInfo.date = PGNDate();
15599 gameInfo.round = StrSave("-");
15600 gameInfo.white = StrSave("-");
15601 gameInfo.black = StrSave("-");
15604 case IcsPlayingWhite:
15605 case IcsPlayingBlack:
15610 case PlayFromGameFile:
15611 gameInfo.event = StrSave("Game from non-PGN file");
15612 gameInfo.site = StrSave(HostName());
15613 gameInfo.date = PGNDate();
15614 gameInfo.round = StrSave("-");
15615 gameInfo.white = StrSave("?");
15616 gameInfo.black = StrSave("?");
15625 ReplaceComment (int index, char *text)
15631 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15632 pvInfoList[index-1].depth == len &&
15633 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15634 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15635 while (*text == '\n') text++;
15636 len = strlen(text);
15637 while (len > 0 && text[len - 1] == '\n') len--;
15639 if (commentList[index] != NULL)
15640 free(commentList[index]);
15643 commentList[index] = NULL;
15646 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15647 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15648 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15649 commentList[index] = (char *) malloc(len + 2);
15650 strncpy(commentList[index], text, len);
15651 commentList[index][len] = '\n';
15652 commentList[index][len + 1] = NULLCHAR;
15654 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15656 commentList[index] = (char *) malloc(len + 7);
15657 safeStrCpy(commentList[index], "{\n", 3);
15658 safeStrCpy(commentList[index]+2, text, len+1);
15659 commentList[index][len+2] = NULLCHAR;
15660 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15661 strcat(commentList[index], "\n}\n");
15666 CrushCRs (char *text)
15674 if (ch == '\r') continue;
15676 } while (ch != '\0');
15680 AppendComment (int index, char *text, Boolean addBraces)
15681 /* addBraces tells if we should add {} */
15686 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15687 if(addBraces == 3) addBraces = 0; else // force appending literally
15688 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15691 while (*text == '\n') text++;
15692 len = strlen(text);
15693 while (len > 0 && text[len - 1] == '\n') len--;
15694 text[len] = NULLCHAR;
15696 if (len == 0) return;
15698 if (commentList[index] != NULL) {
15699 Boolean addClosingBrace = addBraces;
15700 old = commentList[index];
15701 oldlen = strlen(old);
15702 while(commentList[index][oldlen-1] == '\n')
15703 commentList[index][--oldlen] = NULLCHAR;
15704 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15705 safeStrCpy(commentList[index], old, oldlen + len + 6);
15707 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15708 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15709 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15710 while (*text == '\n') { text++; len--; }
15711 commentList[index][--oldlen] = NULLCHAR;
15713 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15714 else strcat(commentList[index], "\n");
15715 strcat(commentList[index], text);
15716 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15717 else strcat(commentList[index], "\n");
15719 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15721 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15722 else commentList[index][0] = NULLCHAR;
15723 strcat(commentList[index], text);
15724 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15725 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15730 FindStr (char * text, char * sub_text)
15732 char * result = strstr( text, sub_text );
15734 if( result != NULL ) {
15735 result += strlen( sub_text );
15741 /* [AS] Try to extract PV info from PGN comment */
15742 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15744 GetInfoFromComment (int index, char * text)
15746 char * sep = text, *p;
15748 if( text != NULL && index > 0 ) {
15751 int time = -1, sec = 0, deci;
15752 char * s_eval = FindStr( text, "[%eval " );
15753 char * s_emt = FindStr( text, "[%emt " );
15755 if( s_eval != NULL || s_emt != NULL ) {
15757 if(0) { // [HGM] this code is not finished, and could actually be detrimental
15762 if( s_eval != NULL ) {
15763 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15767 if( delim != ']' ) {
15772 if( s_emt != NULL ) {
15777 /* We expect something like: [+|-]nnn.nn/dd */
15780 if(*text != '{') return text; // [HGM] braces: must be normal comment
15782 sep = strchr( text, '/' );
15783 if( sep == NULL || sep < (text+4) ) {
15788 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15789 if(p[1] == '(') { // comment starts with PV
15790 p = strchr(p, ')'); // locate end of PV
15791 if(p == NULL || sep < p+5) return text;
15792 // at this point we have something like "{(.*) +0.23/6 ..."
15793 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15794 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15795 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15797 time = -1; sec = -1; deci = -1;
15798 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15799 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15800 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15801 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15805 if( score_lo < 0 || score_lo >= 100 ) {
15809 if(sec >= 0) time = 600*time + 10*sec; else
15810 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15812 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15814 /* [HGM] PV time: now locate end of PV info */
15815 while( *++sep >= '0' && *sep <= '9'); // strip depth
15817 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15819 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15821 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15822 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15833 pvInfoList[index-1].depth = depth;
15834 pvInfoList[index-1].score = score;
15835 pvInfoList[index-1].time = 10*time; // centi-sec
15836 if(*sep == '}') *sep = 0; else *--sep = '{';
15837 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15843 SendToProgram (char *message, ChessProgramState *cps)
15845 int count, outCount, error;
15848 if (cps->pr == NoProc) return;
15851 if (appData.debugMode) {
15854 fprintf(debugFP, "%ld >%-6s: %s",
15855 SubtractTimeMarks(&now, &programStartTime),
15856 cps->which, message);
15858 fprintf(serverFP, "%ld >%-6s: %s",
15859 SubtractTimeMarks(&now, &programStartTime),
15860 cps->which, message), fflush(serverFP);
15863 count = strlen(message);
15864 outCount = OutputToProcess(cps->pr, message, count, &error);
15865 if (outCount < count && !exiting
15866 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15867 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15868 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15869 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15870 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15871 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15872 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15873 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15875 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15876 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15877 gameInfo.result = res;
15879 gameInfo.resultDetails = StrSave(buf);
15881 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15882 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15887 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15891 ChessProgramState *cps = (ChessProgramState *)closure;
15893 if (isr != cps->isr) return; /* Killed intentionally */
15896 RemoveInputSource(cps->isr);
15897 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15898 _(cps->which), cps->program);
15899 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15900 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15901 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15902 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15903 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15904 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15906 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15907 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15908 gameInfo.result = res;
15910 gameInfo.resultDetails = StrSave(buf);
15912 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15913 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15915 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15916 _(cps->which), cps->program);
15917 RemoveInputSource(cps->isr);
15919 /* [AS] Program is misbehaving badly... kill it */
15920 if( count == -2 ) {
15921 DestroyChildProcess( cps->pr, 9 );
15925 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15930 if ((end_str = strchr(message, '\r')) != NULL)
15931 *end_str = NULLCHAR;
15932 if ((end_str = strchr(message, '\n')) != NULL)
15933 *end_str = NULLCHAR;
15935 if (appData.debugMode) {
15936 TimeMark now; int print = 1;
15937 char *quote = ""; char c; int i;
15939 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15940 char start = message[0];
15941 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15942 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15943 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15944 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15945 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15946 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15947 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15948 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15949 sscanf(message, "hint: %c", &c)!=1 &&
15950 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15951 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15952 print = (appData.engineComments >= 2);
15954 message[0] = start; // restore original message
15958 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15959 SubtractTimeMarks(&now, &programStartTime), cps->which,
15963 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15964 SubtractTimeMarks(&now, &programStartTime), cps->which,
15966 message), fflush(serverFP);
15970 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15971 if (appData.icsEngineAnalyze) {
15972 if (strstr(message, "whisper") != NULL ||
15973 strstr(message, "kibitz") != NULL ||
15974 strstr(message, "tellics") != NULL) return;
15977 HandleMachineMove(message, cps);
15982 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15987 if( timeControl_2 > 0 ) {
15988 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15989 tc = timeControl_2;
15992 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15993 inc /= cps->timeOdds;
15994 st /= cps->timeOdds;
15996 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15999 /* Set exact time per move, normally using st command */
16000 if (cps->stKludge) {
16001 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16003 if (seconds == 0) {
16004 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16006 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16009 snprintf(buf, MSG_SIZ, "st %d\n", st);
16012 /* Set conventional or incremental time control, using level command */
16013 if (seconds == 0) {
16014 /* Note old gnuchess bug -- minutes:seconds used to not work.
16015 Fixed in later versions, but still avoid :seconds
16016 when seconds is 0. */
16017 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16019 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16020 seconds, inc/1000.);
16023 SendToProgram(buf, cps);
16025 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16026 /* Orthogonally, limit search to given depth */
16028 if (cps->sdKludge) {
16029 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16031 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16033 SendToProgram(buf, cps);
16036 if(cps->nps >= 0) { /* [HGM] nps */
16037 if(cps->supportsNPS == FALSE)
16038 cps->nps = -1; // don't use if engine explicitly says not supported!
16040 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16041 SendToProgram(buf, cps);
16046 ChessProgramState *
16048 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16050 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16051 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16057 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16059 char message[MSG_SIZ];
16062 /* Note: this routine must be called when the clocks are stopped
16063 or when they have *just* been set or switched; otherwise
16064 it will be off by the time since the current tick started.
16066 if (machineWhite) {
16067 time = whiteTimeRemaining / 10;
16068 otime = blackTimeRemaining / 10;
16070 time = blackTimeRemaining / 10;
16071 otime = whiteTimeRemaining / 10;
16073 /* [HGM] translate opponent's time by time-odds factor */
16074 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16076 if (time <= 0) time = 1;
16077 if (otime <= 0) otime = 1;
16079 snprintf(message, MSG_SIZ, "time %ld\n", time);
16080 SendToProgram(message, cps);
16082 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16083 SendToProgram(message, cps);
16087 EngineDefinedVariant (ChessProgramState *cps, int n)
16088 { // return name of n-th unknown variant that engine supports
16089 static char buf[MSG_SIZ];
16090 char *p, *s = cps->variants;
16091 if(!s) return NULL;
16092 do { // parse string from variants feature
16094 p = strchr(s, ',');
16095 if(p) *p = NULLCHAR;
16096 v = StringToVariant(s);
16097 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16098 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16099 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16102 if(n < 0) return buf;
16108 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16111 int len = strlen(name);
16114 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16116 sscanf(*p, "%d", &val);
16118 while (**p && **p != ' ')
16120 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16121 SendToProgram(buf, cps);
16128 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16131 int len = strlen(name);
16132 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16134 sscanf(*p, "%d", loc);
16135 while (**p && **p != ' ') (*p)++;
16136 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16137 SendToProgram(buf, cps);
16144 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16147 int len = strlen(name);
16148 if (strncmp((*p), name, len) == 0
16149 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16151 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16152 sscanf(*p, "%[^\"]", *loc);
16153 while (**p && **p != '\"') (*p)++;
16154 if (**p == '\"') (*p)++;
16155 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16156 SendToProgram(buf, cps);
16163 ParseOption (Option *opt, ChessProgramState *cps)
16164 // [HGM] options: process the string that defines an engine option, and determine
16165 // name, type, default value, and allowed value range
16167 char *p, *q, buf[MSG_SIZ];
16168 int n, min = (-1)<<31, max = 1<<31, def;
16170 if(p = strstr(opt->name, " -spin ")) {
16171 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16172 if(max < min) max = min; // enforce consistency
16173 if(def < min) def = min;
16174 if(def > max) def = max;
16179 } else if((p = strstr(opt->name, " -slider "))) {
16180 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16181 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16182 if(max < min) max = min; // enforce consistency
16183 if(def < min) def = min;
16184 if(def > max) def = max;
16188 opt->type = Spin; // Slider;
16189 } else if((p = strstr(opt->name, " -string "))) {
16190 opt->textValue = p+9;
16191 opt->type = TextBox;
16192 } else if((p = strstr(opt->name, " -file "))) {
16193 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16194 opt->textValue = p+7;
16195 opt->type = FileName; // FileName;
16196 } else if((p = strstr(opt->name, " -path "))) {
16197 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16198 opt->textValue = p+7;
16199 opt->type = PathName; // PathName;
16200 } else if(p = strstr(opt->name, " -check ")) {
16201 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16202 opt->value = (def != 0);
16203 opt->type = CheckBox;
16204 } else if(p = strstr(opt->name, " -combo ")) {
16205 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16206 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16207 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16208 opt->value = n = 0;
16209 while(q = StrStr(q, " /// ")) {
16210 n++; *q = 0; // count choices, and null-terminate each of them
16212 if(*q == '*') { // remember default, which is marked with * prefix
16216 cps->comboList[cps->comboCnt++] = q;
16218 cps->comboList[cps->comboCnt++] = NULL;
16220 opt->type = ComboBox;
16221 } else if(p = strstr(opt->name, " -button")) {
16222 opt->type = Button;
16223 } else if(p = strstr(opt->name, " -save")) {
16224 opt->type = SaveButton;
16225 } else return FALSE;
16226 *p = 0; // terminate option name
16227 // now look if the command-line options define a setting for this engine option.
16228 if(cps->optionSettings && cps->optionSettings[0])
16229 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16230 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16231 snprintf(buf, MSG_SIZ, "option %s", p);
16232 if(p = strstr(buf, ",")) *p = 0;
16233 if(q = strchr(buf, '=')) switch(opt->type) {
16235 for(n=0; n<opt->max; n++)
16236 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16239 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16243 opt->value = atoi(q+1);
16248 SendToProgram(buf, cps);
16254 FeatureDone (ChessProgramState *cps, int val)
16256 DelayedEventCallback cb = GetDelayedEvent();
16257 if ((cb == InitBackEnd3 && cps == &first) ||
16258 (cb == SettingsMenuIfReady && cps == &second) ||
16259 (cb == LoadEngine) ||
16260 (cb == TwoMachinesEventIfReady)) {
16261 CancelDelayedEvent();
16262 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16264 cps->initDone = val;
16265 if(val) cps->reload = FALSE;
16268 /* Parse feature command from engine */
16270 ParseFeatures (char *args, ChessProgramState *cps)
16278 while (*p == ' ') p++;
16279 if (*p == NULLCHAR) return;
16281 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16282 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16283 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16284 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16285 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16286 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16287 if (BoolFeature(&p, "reuse", &val, cps)) {
16288 /* Engine can disable reuse, but can't enable it if user said no */
16289 if (!val) cps->reuse = FALSE;
16292 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16293 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16294 if (gameMode == TwoMachinesPlay) {
16295 DisplayTwoMachinesTitle();
16301 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16302 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16303 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16304 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16305 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16306 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16307 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16308 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16309 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16310 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16311 if (IntFeature(&p, "done", &val, cps)) {
16312 FeatureDone(cps, val);
16315 /* Added by Tord: */
16316 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16317 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16318 /* End of additions by Tord */
16320 /* [HGM] added features: */
16321 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16322 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16323 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16324 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16325 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16326 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16327 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16328 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16329 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16330 FREE(cps->option[cps->nrOptions].name);
16331 cps->option[cps->nrOptions].name = q; q = NULL;
16332 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16333 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16334 SendToProgram(buf, cps);
16337 if(cps->nrOptions >= MAX_OPTIONS) {
16339 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16340 DisplayError(buf, 0);
16344 /* End of additions by HGM */
16346 /* unknown feature: complain and skip */
16348 while (*q && *q != '=') q++;
16349 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16350 SendToProgram(buf, cps);
16356 while (*p && *p != '\"') p++;
16357 if (*p == '\"') p++;
16359 while (*p && *p != ' ') p++;
16367 PeriodicUpdatesEvent (int newState)
16369 if (newState == appData.periodicUpdates)
16372 appData.periodicUpdates=newState;
16374 /* Display type changes, so update it now */
16375 // DisplayAnalysis();
16377 /* Get the ball rolling again... */
16379 AnalysisPeriodicEvent(1);
16380 StartAnalysisClock();
16385 PonderNextMoveEvent (int newState)
16387 if (newState == appData.ponderNextMove) return;
16388 if (gameMode == EditPosition) EditPositionDone(TRUE);
16390 SendToProgram("hard\n", &first);
16391 if (gameMode == TwoMachinesPlay) {
16392 SendToProgram("hard\n", &second);
16395 SendToProgram("easy\n", &first);
16396 thinkOutput[0] = NULLCHAR;
16397 if (gameMode == TwoMachinesPlay) {
16398 SendToProgram("easy\n", &second);
16401 appData.ponderNextMove = newState;
16405 NewSettingEvent (int option, int *feature, char *command, int value)
16409 if (gameMode == EditPosition) EditPositionDone(TRUE);
16410 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16411 if(feature == NULL || *feature) SendToProgram(buf, &first);
16412 if (gameMode == TwoMachinesPlay) {
16413 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16418 ShowThinkingEvent ()
16419 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16421 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16422 int newState = appData.showThinking
16423 // [HGM] thinking: other features now need thinking output as well
16424 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16426 if (oldState == newState) return;
16427 oldState = newState;
16428 if (gameMode == EditPosition) EditPositionDone(TRUE);
16430 SendToProgram("post\n", &first);
16431 if (gameMode == TwoMachinesPlay) {
16432 SendToProgram("post\n", &second);
16435 SendToProgram("nopost\n", &first);
16436 thinkOutput[0] = NULLCHAR;
16437 if (gameMode == TwoMachinesPlay) {
16438 SendToProgram("nopost\n", &second);
16441 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16445 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16447 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16448 if (pr == NoProc) return;
16449 AskQuestion(title, question, replyPrefix, pr);
16453 TypeInEvent (char firstChar)
16455 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16456 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16457 gameMode == AnalyzeMode || gameMode == EditGame ||
16458 gameMode == EditPosition || gameMode == IcsExamining ||
16459 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16460 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16461 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16462 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16463 gameMode == Training) PopUpMoveDialog(firstChar);
16467 TypeInDoneEvent (char *move)
16470 int n, fromX, fromY, toX, toY;
16472 ChessMove moveType;
16475 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16476 EditPositionPasteFEN(move);
16479 // [HGM] movenum: allow move number to be typed in any mode
16480 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16484 // undocumented kludge: allow command-line option to be typed in!
16485 // (potentially fatal, and does not implement the effect of the option.)
16486 // should only be used for options that are values on which future decisions will be made,
16487 // and definitely not on options that would be used during initialization.
16488 if(strstr(move, "!!! -") == move) {
16489 ParseArgsFromString(move+4);
16493 if (gameMode != EditGame && currentMove != forwardMostMove &&
16494 gameMode != Training) {
16495 DisplayMoveError(_("Displayed move is not current"));
16497 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16498 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16499 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16500 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16501 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16502 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16504 DisplayMoveError(_("Could not parse move"));
16510 DisplayMove (int moveNumber)
16512 char message[MSG_SIZ];
16514 char cpThinkOutput[MSG_SIZ];
16516 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16518 if (moveNumber == forwardMostMove - 1 ||
16519 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16521 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16523 if (strchr(cpThinkOutput, '\n')) {
16524 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16527 *cpThinkOutput = NULLCHAR;
16530 /* [AS] Hide thinking from human user */
16531 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16532 *cpThinkOutput = NULLCHAR;
16533 if( thinkOutput[0] != NULLCHAR ) {
16536 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16537 cpThinkOutput[i] = '.';
16539 cpThinkOutput[i] = NULLCHAR;
16540 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16544 if (moveNumber == forwardMostMove - 1 &&
16545 gameInfo.resultDetails != NULL) {
16546 if (gameInfo.resultDetails[0] == NULLCHAR) {
16547 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16549 snprintf(res, MSG_SIZ, " {%s} %s",
16550 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16556 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16557 DisplayMessage(res, cpThinkOutput);
16559 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16560 WhiteOnMove(moveNumber) ? " " : ".. ",
16561 parseList[moveNumber], res);
16562 DisplayMessage(message, cpThinkOutput);
16567 DisplayComment (int moveNumber, char *text)
16569 char title[MSG_SIZ];
16571 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16572 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16574 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16575 WhiteOnMove(moveNumber) ? " " : ".. ",
16576 parseList[moveNumber]);
16578 if (text != NULL && (appData.autoDisplayComment || commentUp))
16579 CommentPopUp(title, text);
16582 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16583 * might be busy thinking or pondering. It can be omitted if your
16584 * gnuchess is configured to stop thinking immediately on any user
16585 * input. However, that gnuchess feature depends on the FIONREAD
16586 * ioctl, which does not work properly on some flavors of Unix.
16589 Attention (ChessProgramState *cps)
16592 if (!cps->useSigint) return;
16593 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16594 switch (gameMode) {
16595 case MachinePlaysWhite:
16596 case MachinePlaysBlack:
16597 case TwoMachinesPlay:
16598 case IcsPlayingWhite:
16599 case IcsPlayingBlack:
16602 /* Skip if we know it isn't thinking */
16603 if (!cps->maybeThinking) return;
16604 if (appData.debugMode)
16605 fprintf(debugFP, "Interrupting %s\n", cps->which);
16606 InterruptChildProcess(cps->pr);
16607 cps->maybeThinking = FALSE;
16612 #endif /*ATTENTION*/
16618 if (whiteTimeRemaining <= 0) {
16621 if (appData.icsActive) {
16622 if (appData.autoCallFlag &&
16623 gameMode == IcsPlayingBlack && !blackFlag) {
16624 SendToICS(ics_prefix);
16625 SendToICS("flag\n");
16629 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16631 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16632 if (appData.autoCallFlag) {
16633 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16640 if (blackTimeRemaining <= 0) {
16643 if (appData.icsActive) {
16644 if (appData.autoCallFlag &&
16645 gameMode == IcsPlayingWhite && !whiteFlag) {
16646 SendToICS(ics_prefix);
16647 SendToICS("flag\n");
16651 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16653 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16654 if (appData.autoCallFlag) {
16655 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16666 CheckTimeControl ()
16668 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16669 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16672 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16674 if ( !WhiteOnMove(forwardMostMove) ) {
16675 /* White made time control */
16676 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16677 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16678 /* [HGM] time odds: correct new time quota for time odds! */
16679 / WhitePlayer()->timeOdds;
16680 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16682 lastBlack -= blackTimeRemaining;
16683 /* Black made time control */
16684 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16685 / WhitePlayer()->other->timeOdds;
16686 lastWhite = whiteTimeRemaining;
16691 DisplayBothClocks ()
16693 int wom = gameMode == EditPosition ?
16694 !blackPlaysFirst : WhiteOnMove(currentMove);
16695 DisplayWhiteClock(whiteTimeRemaining, wom);
16696 DisplayBlackClock(blackTimeRemaining, !wom);
16700 /* Timekeeping seems to be a portability nightmare. I think everyone
16701 has ftime(), but I'm really not sure, so I'm including some ifdefs
16702 to use other calls if you don't. Clocks will be less accurate if
16703 you have neither ftime nor gettimeofday.
16706 /* VS 2008 requires the #include outside of the function */
16707 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16708 #include <sys/timeb.h>
16711 /* Get the current time as a TimeMark */
16713 GetTimeMark (TimeMark *tm)
16715 #if HAVE_GETTIMEOFDAY
16717 struct timeval timeVal;
16718 struct timezone timeZone;
16720 gettimeofday(&timeVal, &timeZone);
16721 tm->sec = (long) timeVal.tv_sec;
16722 tm->ms = (int) (timeVal.tv_usec / 1000L);
16724 #else /*!HAVE_GETTIMEOFDAY*/
16727 // include <sys/timeb.h> / moved to just above start of function
16728 struct timeb timeB;
16731 tm->sec = (long) timeB.time;
16732 tm->ms = (int) timeB.millitm;
16734 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16735 tm->sec = (long) time(NULL);
16741 /* Return the difference in milliseconds between two
16742 time marks. We assume the difference will fit in a long!
16745 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16747 return 1000L*(tm2->sec - tm1->sec) +
16748 (long) (tm2->ms - tm1->ms);
16753 * Code to manage the game clocks.
16755 * In tournament play, black starts the clock and then white makes a move.
16756 * We give the human user a slight advantage if he is playing white---the
16757 * clocks don't run until he makes his first move, so it takes zero time.
16758 * Also, we don't account for network lag, so we could get out of sync
16759 * with GNU Chess's clock -- but then, referees are always right.
16762 static TimeMark tickStartTM;
16763 static long intendedTickLength;
16766 NextTickLength (long timeRemaining)
16768 long nominalTickLength, nextTickLength;
16770 if (timeRemaining > 0L && timeRemaining <= 10000L)
16771 nominalTickLength = 100L;
16773 nominalTickLength = 1000L;
16774 nextTickLength = timeRemaining % nominalTickLength;
16775 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16777 return nextTickLength;
16780 /* Adjust clock one minute up or down */
16782 AdjustClock (Boolean which, int dir)
16784 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16785 if(which) blackTimeRemaining += 60000*dir;
16786 else whiteTimeRemaining += 60000*dir;
16787 DisplayBothClocks();
16788 adjustedClock = TRUE;
16791 /* Stop clocks and reset to a fresh time control */
16795 (void) StopClockTimer();
16796 if (appData.icsActive) {
16797 whiteTimeRemaining = blackTimeRemaining = 0;
16798 } else if (searchTime) {
16799 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16800 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16801 } else { /* [HGM] correct new time quote for time odds */
16802 whiteTC = blackTC = fullTimeControlString;
16803 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16804 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16806 if (whiteFlag || blackFlag) {
16808 whiteFlag = blackFlag = FALSE;
16810 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16811 DisplayBothClocks();
16812 adjustedClock = FALSE;
16815 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16817 /* Decrement running clock by amount of time that has passed */
16821 long timeRemaining;
16822 long lastTickLength, fudge;
16825 if (!appData.clockMode) return;
16826 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16830 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16832 /* Fudge if we woke up a little too soon */
16833 fudge = intendedTickLength - lastTickLength;
16834 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16836 if (WhiteOnMove(forwardMostMove)) {
16837 if(whiteNPS >= 0) lastTickLength = 0;
16838 timeRemaining = whiteTimeRemaining -= lastTickLength;
16839 if(timeRemaining < 0 && !appData.icsActive) {
16840 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16841 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16842 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16843 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16846 DisplayWhiteClock(whiteTimeRemaining - fudge,
16847 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16849 if(blackNPS >= 0) lastTickLength = 0;
16850 timeRemaining = blackTimeRemaining -= lastTickLength;
16851 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16852 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16854 blackStartMove = forwardMostMove;
16855 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16858 DisplayBlackClock(blackTimeRemaining - fudge,
16859 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16861 if (CheckFlags()) return;
16863 if(twoBoards) { // count down secondary board's clocks as well
16864 activePartnerTime -= lastTickLength;
16866 if(activePartner == 'W')
16867 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16869 DisplayBlackClock(activePartnerTime, TRUE);
16874 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16875 StartClockTimer(intendedTickLength);
16877 /* if the time remaining has fallen below the alarm threshold, sound the
16878 * alarm. if the alarm has sounded and (due to a takeback or time control
16879 * with increment) the time remaining has increased to a level above the
16880 * threshold, reset the alarm so it can sound again.
16883 if (appData.icsActive && appData.icsAlarm) {
16885 /* make sure we are dealing with the user's clock */
16886 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16887 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16890 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16891 alarmSounded = FALSE;
16892 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16894 alarmSounded = TRUE;
16900 /* A player has just moved, so stop the previously running
16901 clock and (if in clock mode) start the other one.
16902 We redisplay both clocks in case we're in ICS mode, because
16903 ICS gives us an update to both clocks after every move.
16904 Note that this routine is called *after* forwardMostMove
16905 is updated, so the last fractional tick must be subtracted
16906 from the color that is *not* on move now.
16909 SwitchClocks (int newMoveNr)
16911 long lastTickLength;
16913 int flagged = FALSE;
16917 if (StopClockTimer() && appData.clockMode) {
16918 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16919 if (!WhiteOnMove(forwardMostMove)) {
16920 if(blackNPS >= 0) lastTickLength = 0;
16921 blackTimeRemaining -= lastTickLength;
16922 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16923 // if(pvInfoList[forwardMostMove].time == -1)
16924 pvInfoList[forwardMostMove].time = // use GUI time
16925 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16927 if(whiteNPS >= 0) lastTickLength = 0;
16928 whiteTimeRemaining -= lastTickLength;
16929 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16930 // if(pvInfoList[forwardMostMove].time == -1)
16931 pvInfoList[forwardMostMove].time =
16932 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16934 flagged = CheckFlags();
16936 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16937 CheckTimeControl();
16939 if (flagged || !appData.clockMode) return;
16941 switch (gameMode) {
16942 case MachinePlaysBlack:
16943 case MachinePlaysWhite:
16944 case BeginningOfGame:
16945 if (pausing) return;
16949 case PlayFromGameFile:
16957 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16958 if(WhiteOnMove(forwardMostMove))
16959 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16960 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16964 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16965 whiteTimeRemaining : blackTimeRemaining);
16966 StartClockTimer(intendedTickLength);
16970 /* Stop both clocks */
16974 long lastTickLength;
16977 if (!StopClockTimer()) return;
16978 if (!appData.clockMode) return;
16982 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16983 if (WhiteOnMove(forwardMostMove)) {
16984 if(whiteNPS >= 0) lastTickLength = 0;
16985 whiteTimeRemaining -= lastTickLength;
16986 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16988 if(blackNPS >= 0) lastTickLength = 0;
16989 blackTimeRemaining -= lastTickLength;
16990 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16995 /* Start clock of player on move. Time may have been reset, so
16996 if clock is already running, stop and restart it. */
17000 (void) StopClockTimer(); /* in case it was running already */
17001 DisplayBothClocks();
17002 if (CheckFlags()) return;
17004 if (!appData.clockMode) return;
17005 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17007 GetTimeMark(&tickStartTM);
17008 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17009 whiteTimeRemaining : blackTimeRemaining);
17011 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17012 whiteNPS = blackNPS = -1;
17013 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17014 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17015 whiteNPS = first.nps;
17016 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17017 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17018 blackNPS = first.nps;
17019 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17020 whiteNPS = second.nps;
17021 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17022 blackNPS = second.nps;
17023 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17025 StartClockTimer(intendedTickLength);
17029 TimeString (long ms)
17031 long second, minute, hour, day;
17033 static char buf[32];
17035 if (ms > 0 && ms <= 9900) {
17036 /* convert milliseconds to tenths, rounding up */
17037 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17039 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17043 /* convert milliseconds to seconds, rounding up */
17044 /* use floating point to avoid strangeness of integer division
17045 with negative dividends on many machines */
17046 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17053 day = second / (60 * 60 * 24);
17054 second = second % (60 * 60 * 24);
17055 hour = second / (60 * 60);
17056 second = second % (60 * 60);
17057 minute = second / 60;
17058 second = second % 60;
17061 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17062 sign, day, hour, minute, second);
17064 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17066 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17073 * This is necessary because some C libraries aren't ANSI C compliant yet.
17076 StrStr (char *string, char *match)
17080 length = strlen(match);
17082 for (i = strlen(string) - length; i >= 0; i--, string++)
17083 if (!strncmp(match, string, length))
17090 StrCaseStr (char *string, char *match)
17094 length = strlen(match);
17096 for (i = strlen(string) - length; i >= 0; i--, string++) {
17097 for (j = 0; j < length; j++) {
17098 if (ToLower(match[j]) != ToLower(string[j]))
17101 if (j == length) return string;
17109 StrCaseCmp (char *s1, char *s2)
17114 c1 = ToLower(*s1++);
17115 c2 = ToLower(*s2++);
17116 if (c1 > c2) return 1;
17117 if (c1 < c2) return -1;
17118 if (c1 == NULLCHAR) return 0;
17126 return isupper(c) ? tolower(c) : c;
17133 return islower(c) ? toupper(c) : c;
17135 #endif /* !_amigados */
17142 if ((ret = (char *) malloc(strlen(s) + 1)))
17144 safeStrCpy(ret, s, strlen(s)+1);
17150 StrSavePtr (char *s, char **savePtr)
17155 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17156 safeStrCpy(*savePtr, s, strlen(s)+1);
17168 clock = time((time_t *)NULL);
17169 tm = localtime(&clock);
17170 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17171 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17172 return StrSave(buf);
17177 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17179 int i, j, fromX, fromY, toX, toY;
17186 whiteToPlay = (gameMode == EditPosition) ?
17187 !blackPlaysFirst : (move % 2 == 0);
17190 /* Piece placement data */
17191 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17192 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17194 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17195 if (boards[move][i][j] == EmptySquare) {
17197 } else { ChessSquare piece = boards[move][i][j];
17198 if (emptycount > 0) {
17199 if(emptycount<10) /* [HGM] can be >= 10 */
17200 *p++ = '0' + emptycount;
17201 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17204 if(PieceToChar(piece) == '+') {
17205 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17207 piece = (ChessSquare)(DEMOTED piece);
17209 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17211 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17212 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17217 if (emptycount > 0) {
17218 if(emptycount<10) /* [HGM] can be >= 10 */
17219 *p++ = '0' + emptycount;
17220 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17227 /* [HGM] print Crazyhouse or Shogi holdings */
17228 if( gameInfo.holdingsWidth ) {
17229 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17231 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17232 piece = boards[move][i][BOARD_WIDTH-1];
17233 if( piece != EmptySquare )
17234 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17235 *p++ = PieceToChar(piece);
17237 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17238 piece = boards[move][BOARD_HEIGHT-i-1][0];
17239 if( piece != EmptySquare )
17240 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17241 *p++ = PieceToChar(piece);
17244 if( q == p ) *p++ = '-';
17250 *p++ = whiteToPlay ? 'w' : 'b';
17253 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17254 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17256 if(nrCastlingRights) {
17258 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17259 /* [HGM] write directly from rights */
17260 if(boards[move][CASTLING][2] != NoRights &&
17261 boards[move][CASTLING][0] != NoRights )
17262 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17263 if(boards[move][CASTLING][2] != NoRights &&
17264 boards[move][CASTLING][1] != NoRights )
17265 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17266 if(boards[move][CASTLING][5] != NoRights &&
17267 boards[move][CASTLING][3] != NoRights )
17268 *p++ = boards[move][CASTLING][3] + AAA;
17269 if(boards[move][CASTLING][5] != NoRights &&
17270 boards[move][CASTLING][4] != NoRights )
17271 *p++ = boards[move][CASTLING][4] + AAA;
17274 /* [HGM] write true castling rights */
17275 if( nrCastlingRights == 6 ) {
17277 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17278 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17279 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17280 boards[move][CASTLING][2] != NoRights );
17281 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17282 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17283 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17284 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17285 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17289 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17290 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17291 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17292 boards[move][CASTLING][5] != NoRights );
17293 if(gameInfo.variant == VariantSChess) {
17294 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17295 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17296 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17297 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17302 if (q == p) *p++ = '-'; /* No castling rights */
17306 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17307 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17308 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17309 /* En passant target square */
17310 if (move > backwardMostMove) {
17311 fromX = moveList[move - 1][0] - AAA;
17312 fromY = moveList[move - 1][1] - ONE;
17313 toX = moveList[move - 1][2] - AAA;
17314 toY = moveList[move - 1][3] - ONE;
17315 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17316 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17317 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17319 /* 2-square pawn move just happened */
17321 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17325 } else if(move == backwardMostMove) {
17326 // [HGM] perhaps we should always do it like this, and forget the above?
17327 if((signed char)boards[move][EP_STATUS] >= 0) {
17328 *p++ = boards[move][EP_STATUS] + AAA;
17329 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17341 { int i = 0, j=move;
17343 /* [HGM] find reversible plies */
17344 if (appData.debugMode) { int k;
17345 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17346 for(k=backwardMostMove; k<=forwardMostMove; k++)
17347 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17351 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17352 if( j == backwardMostMove ) i += initialRulePlies;
17353 sprintf(p, "%d ", i);
17354 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17356 /* Fullmove number */
17357 sprintf(p, "%d", (move / 2) + 1);
17358 } else *--p = NULLCHAR;
17360 return StrSave(buf);
17364 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17368 int emptycount, virgin[BOARD_FILES];
17373 /* [HGM] by default clear Crazyhouse holdings, if present */
17374 if(gameInfo.holdingsWidth) {
17375 for(i=0; i<BOARD_HEIGHT; i++) {
17376 board[i][0] = EmptySquare; /* black holdings */
17377 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17378 board[i][1] = (ChessSquare) 0; /* black counts */
17379 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17383 /* Piece placement data */
17384 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17387 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17388 if (*p == '/') p++;
17389 emptycount = gameInfo.boardWidth - j;
17390 while (emptycount--)
17391 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17393 #if(BOARD_FILES >= 10)
17394 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17395 p++; emptycount=10;
17396 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17397 while (emptycount--)
17398 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17400 } else if (*p == '*') {
17401 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17402 } else if (isdigit(*p)) {
17403 emptycount = *p++ - '0';
17404 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17405 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17406 while (emptycount--)
17407 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17408 } else if (*p == '+' || isalpha(*p)) {
17409 if (j >= gameInfo.boardWidth) return FALSE;
17411 piece = CharToPiece(*++p);
17412 if(piece == EmptySquare) return FALSE; /* unknown piece */
17413 piece = (ChessSquare) (PROMOTED piece ); p++;
17414 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17415 } else piece = CharToPiece(*p++);
17417 if(piece==EmptySquare) return FALSE; /* unknown piece */
17418 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17419 piece = (ChessSquare) (PROMOTED piece);
17420 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17423 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17429 while (*p == '/' || *p == ' ') p++;
17431 /* [HGM] look for Crazyhouse holdings here */
17432 while(*p==' ') p++;
17433 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17435 if(*p == '-' ) p++; /* empty holdings */ else {
17436 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17437 /* if we would allow FEN reading to set board size, we would */
17438 /* have to add holdings and shift the board read so far here */
17439 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17441 if((int) piece >= (int) BlackPawn ) {
17442 i = (int)piece - (int)BlackPawn;
17443 i = PieceToNumber((ChessSquare)i);
17444 if( i >= gameInfo.holdingsSize ) return FALSE;
17445 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17446 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17448 i = (int)piece - (int)WhitePawn;
17449 i = PieceToNumber((ChessSquare)i);
17450 if( i >= gameInfo.holdingsSize ) return FALSE;
17451 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17452 board[i][BOARD_WIDTH-2]++; /* black holdings */
17459 while(*p == ' ') p++;
17463 if(appData.colorNickNames) {
17464 if( c == appData.colorNickNames[0] ) c = 'w'; else
17465 if( c == appData.colorNickNames[1] ) c = 'b';
17469 *blackPlaysFirst = FALSE;
17472 *blackPlaysFirst = TRUE;
17478 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17479 /* return the extra info in global variiables */
17481 /* set defaults in case FEN is incomplete */
17482 board[EP_STATUS] = EP_UNKNOWN;
17483 for(i=0; i<nrCastlingRights; i++ ) {
17484 board[CASTLING][i] =
17485 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17486 } /* assume possible unless obviously impossible */
17487 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17488 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17489 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17490 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17491 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17492 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17493 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17494 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17497 while(*p==' ') p++;
17498 if(nrCastlingRights) {
17499 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17500 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17501 /* castling indicator present, so default becomes no castlings */
17502 for(i=0; i<nrCastlingRights; i++ ) {
17503 board[CASTLING][i] = NoRights;
17506 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17507 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17508 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17509 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17510 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17512 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17513 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17514 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17516 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17517 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17518 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17519 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17520 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17521 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17524 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17525 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17526 board[CASTLING][2] = whiteKingFile;
17527 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17528 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17531 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17532 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17533 board[CASTLING][2] = whiteKingFile;
17534 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17535 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17538 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17539 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17540 board[CASTLING][5] = blackKingFile;
17541 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17542 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17545 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17546 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17547 board[CASTLING][5] = blackKingFile;
17548 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17549 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17552 default: /* FRC castlings */
17553 if(c >= 'a') { /* black rights */
17554 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17555 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17556 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17557 if(i == BOARD_RGHT) break;
17558 board[CASTLING][5] = i;
17560 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17561 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17563 board[CASTLING][3] = c;
17565 board[CASTLING][4] = c;
17566 } else { /* white rights */
17567 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17568 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17569 if(board[0][i] == WhiteKing) break;
17570 if(i == BOARD_RGHT) break;
17571 board[CASTLING][2] = i;
17572 c -= AAA - 'a' + 'A';
17573 if(board[0][c] >= WhiteKing) break;
17575 board[CASTLING][0] = c;
17577 board[CASTLING][1] = c;
17581 for(i=0; i<nrCastlingRights; i++)
17582 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17583 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17584 if (appData.debugMode) {
17585 fprintf(debugFP, "FEN castling rights:");
17586 for(i=0; i<nrCastlingRights; i++)
17587 fprintf(debugFP, " %d", board[CASTLING][i]);
17588 fprintf(debugFP, "\n");
17591 while(*p==' ') p++;
17594 /* read e.p. field in games that know e.p. capture */
17595 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17596 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17597 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17599 p++; board[EP_STATUS] = EP_NONE;
17601 char c = *p++ - AAA;
17603 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17604 if(*p >= '0' && *p <='9') p++;
17605 board[EP_STATUS] = c;
17610 if(sscanf(p, "%d", &i) == 1) {
17611 FENrulePlies = i; /* 50-move ply counter */
17612 /* (The move number is still ignored) */
17619 EditPositionPasteFEN (char *fen)
17622 Board initial_position;
17624 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17625 DisplayError(_("Bad FEN position in clipboard"), 0);
17628 int savedBlackPlaysFirst = blackPlaysFirst;
17629 EditPositionEvent();
17630 blackPlaysFirst = savedBlackPlaysFirst;
17631 CopyBoard(boards[0], initial_position);
17632 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17633 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17634 DisplayBothClocks();
17635 DrawPosition(FALSE, boards[currentMove]);
17640 static char cseq[12] = "\\ ";
17643 set_cont_sequence (char *new_seq)
17648 // handle bad attempts to set the sequence
17650 return 0; // acceptable error - no debug
17652 len = strlen(new_seq);
17653 ret = (len > 0) && (len < sizeof(cseq));
17655 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17656 else if (appData.debugMode)
17657 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17662 reformat a source message so words don't cross the width boundary. internal
17663 newlines are not removed. returns the wrapped size (no null character unless
17664 included in source message). If dest is NULL, only calculate the size required
17665 for the dest buffer. lp argument indicats line position upon entry, and it's
17666 passed back upon exit.
17669 wrap (char *dest, char *src, int count, int width, int *lp)
17671 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17673 cseq_len = strlen(cseq);
17674 old_line = line = *lp;
17675 ansi = len = clen = 0;
17677 for (i=0; i < count; i++)
17679 if (src[i] == '\033')
17682 // if we hit the width, back up
17683 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17685 // store i & len in case the word is too long
17686 old_i = i, old_len = len;
17688 // find the end of the last word
17689 while (i && src[i] != ' ' && src[i] != '\n')
17695 // word too long? restore i & len before splitting it
17696 if ((old_i-i+clen) >= width)
17703 if (i && src[i-1] == ' ')
17706 if (src[i] != ' ' && src[i] != '\n')
17713 // now append the newline and continuation sequence
17718 strncpy(dest+len, cseq, cseq_len);
17726 dest[len] = src[i];
17730 if (src[i] == '\n')
17735 if (dest && appData.debugMode)
17737 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17738 count, width, line, len, *lp);
17739 show_bytes(debugFP, src, count);
17740 fprintf(debugFP, "\ndest: ");
17741 show_bytes(debugFP, dest, len);
17742 fprintf(debugFP, "\n");
17744 *lp = dest ? line : old_line;
17749 // [HGM] vari: routines for shelving variations
17750 Boolean modeRestore = FALSE;
17753 PushInner (int firstMove, int lastMove)
17755 int i, j, nrMoves = lastMove - firstMove;
17757 // push current tail of game on stack
17758 savedResult[storedGames] = gameInfo.result;
17759 savedDetails[storedGames] = gameInfo.resultDetails;
17760 gameInfo.resultDetails = NULL;
17761 savedFirst[storedGames] = firstMove;
17762 savedLast [storedGames] = lastMove;
17763 savedFramePtr[storedGames] = framePtr;
17764 framePtr -= nrMoves; // reserve space for the boards
17765 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17766 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17767 for(j=0; j<MOVE_LEN; j++)
17768 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17769 for(j=0; j<2*MOVE_LEN; j++)
17770 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17771 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17772 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17773 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17774 pvInfoList[firstMove+i-1].depth = 0;
17775 commentList[framePtr+i] = commentList[firstMove+i];
17776 commentList[firstMove+i] = NULL;
17780 forwardMostMove = firstMove; // truncate game so we can start variation
17784 PushTail (int firstMove, int lastMove)
17786 if(appData.icsActive) { // only in local mode
17787 forwardMostMove = currentMove; // mimic old ICS behavior
17790 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17792 PushInner(firstMove, lastMove);
17793 if(storedGames == 1) GreyRevert(FALSE);
17794 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17798 PopInner (Boolean annotate)
17801 char buf[8000], moveBuf[20];
17803 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17804 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17805 nrMoves = savedLast[storedGames] - currentMove;
17808 if(!WhiteOnMove(currentMove))
17809 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17810 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17811 for(i=currentMove; i<forwardMostMove; i++) {
17813 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17814 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17815 strcat(buf, moveBuf);
17816 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17817 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17821 for(i=1; i<=nrMoves; i++) { // copy last variation back
17822 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17823 for(j=0; j<MOVE_LEN; j++)
17824 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17825 for(j=0; j<2*MOVE_LEN; j++)
17826 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17827 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17828 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17829 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17830 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17831 commentList[currentMove+i] = commentList[framePtr+i];
17832 commentList[framePtr+i] = NULL;
17834 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17835 framePtr = savedFramePtr[storedGames];
17836 gameInfo.result = savedResult[storedGames];
17837 if(gameInfo.resultDetails != NULL) {
17838 free(gameInfo.resultDetails);
17840 gameInfo.resultDetails = savedDetails[storedGames];
17841 forwardMostMove = currentMove + nrMoves;
17845 PopTail (Boolean annotate)
17847 if(appData.icsActive) return FALSE; // only in local mode
17848 if(!storedGames) return FALSE; // sanity
17849 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17851 PopInner(annotate);
17852 if(currentMove < forwardMostMove) ForwardEvent(); else
17853 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17855 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17861 { // remove all shelved variations
17863 for(i=0; i<storedGames; i++) {
17864 if(savedDetails[i])
17865 free(savedDetails[i]);
17866 savedDetails[i] = NULL;
17868 for(i=framePtr; i<MAX_MOVES; i++) {
17869 if(commentList[i]) free(commentList[i]);
17870 commentList[i] = NULL;
17872 framePtr = MAX_MOVES-1;
17877 LoadVariation (int index, char *text)
17878 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17879 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17880 int level = 0, move;
17882 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17883 // first find outermost bracketing variation
17884 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17885 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17886 if(*p == '{') wait = '}'; else
17887 if(*p == '[') wait = ']'; else
17888 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17889 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17891 if(*p == wait) wait = NULLCHAR; // closing ]} found
17894 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17895 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17896 end[1] = NULLCHAR; // clip off comment beyond variation
17897 ToNrEvent(currentMove-1);
17898 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17899 // kludge: use ParsePV() to append variation to game
17900 move = currentMove;
17901 ParsePV(start, TRUE, TRUE);
17902 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17903 ClearPremoveHighlights();
17905 ToNrEvent(currentMove+1);
17911 char *p, *q, buf[MSG_SIZ];
17912 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17913 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17914 ParseArgsFromString(buf);
17915 ActivateTheme(TRUE); // also redo colors
17919 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17922 q = appData.themeNames;
17923 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17924 if(appData.useBitmaps) {
17925 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17926 appData.liteBackTextureFile, appData.darkBackTextureFile,
17927 appData.liteBackTextureMode,
17928 appData.darkBackTextureMode );
17930 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17931 Col2Text(2), // lightSquareColor
17932 Col2Text(3) ); // darkSquareColor
17934 if(appData.useBorder) {
17935 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17938 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17940 if(appData.useFont) {
17941 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17942 appData.renderPiecesWithFont,
17943 appData.fontToPieceTable,
17944 Col2Text(9), // appData.fontBackColorWhite
17945 Col2Text(10) ); // appData.fontForeColorBlack
17947 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17948 appData.pieceDirectory);
17949 if(!appData.pieceDirectory[0])
17950 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17951 Col2Text(0), // whitePieceColor
17952 Col2Text(1) ); // blackPieceColor
17954 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17955 Col2Text(4), // highlightSquareColor
17956 Col2Text(5) ); // premoveHighlightColor
17957 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17958 if(insert != q) insert[-1] = NULLCHAR;
17959 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17962 ActivateTheme(FALSE);