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 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3032 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3033 SendToPlayer(tmp, strlen(tmp));
3035 next_out = i+1; // [HGM] suppress printing in ICS window
3037 started = STARTED_NONE;
3039 /* Don't match patterns against characters in comment */
3044 if (started == STARTED_CHATTER) {
3045 if (buf[i] != '\n') {
3046 /* Don't match patterns against characters in chatter */
3050 started = STARTED_NONE;
3051 if(suppressKibitz) next_out = i+1;
3054 /* Kludge to deal with rcmd protocol */
3055 if (firstTime && looking_at(buf, &i, "\001*")) {
3056 DisplayFatalError(&buf[1], 0, 1);
3062 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3065 if (appData.debugMode)
3066 fprintf(debugFP, "ics_type %d\n", ics_type);
3069 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3070 ics_type = ICS_FICS;
3072 if (appData.debugMode)
3073 fprintf(debugFP, "ics_type %d\n", ics_type);
3076 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3077 ics_type = ICS_CHESSNET;
3079 if (appData.debugMode)
3080 fprintf(debugFP, "ics_type %d\n", ics_type);
3085 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3086 looking_at(buf, &i, "Logging you in as \"*\"") ||
3087 looking_at(buf, &i, "will be \"*\""))) {
3088 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3092 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3094 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3095 DisplayIcsInteractionTitle(buf);
3096 have_set_title = TRUE;
3099 /* skip finger notes */
3100 if (started == STARTED_NONE &&
3101 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3102 (buf[i] == '1' && buf[i+1] == '0')) &&
3103 buf[i+2] == ':' && buf[i+3] == ' ') {
3104 started = STARTED_CHATTER;
3110 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3111 if(appData.seekGraph) {
3112 if(soughtPending && MatchSoughtLine(buf+i)) {
3113 i = strstr(buf+i, "rated") - buf;
3114 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3115 next_out = leftover_start = i;
3116 started = STARTED_CHATTER;
3117 suppressKibitz = TRUE;
3120 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3121 && looking_at(buf, &i, "* ads displayed")) {
3122 soughtPending = FALSE;
3127 if(appData.autoRefresh) {
3128 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3129 int s = (ics_type == ICS_ICC); // ICC format differs
3131 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3132 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3133 looking_at(buf, &i, "*% "); // eat prompt
3134 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3135 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3136 next_out = i; // suppress
3139 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3140 char *p = star_match[0];
3142 if(seekGraphUp) RemoveSeekAd(atoi(p));
3143 while(*p && *p++ != ' '); // next
3145 looking_at(buf, &i, "*% "); // eat prompt
3146 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3153 /* skip formula vars */
3154 if (started == STARTED_NONE &&
3155 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3156 started = STARTED_CHATTER;
3161 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3162 if (appData.autoKibitz && started == STARTED_NONE &&
3163 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3164 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3165 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3166 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3167 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3168 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3169 suppressKibitz = TRUE;
3170 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3172 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3173 && (gameMode == IcsPlayingWhite)) ||
3174 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3175 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3176 started = STARTED_CHATTER; // own kibitz we simply discard
3178 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3179 parse_pos = 0; parse[0] = NULLCHAR;
3180 savingComment = TRUE;
3181 suppressKibitz = gameMode != IcsObserving ? 2 :
3182 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3186 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3187 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3188 && atoi(star_match[0])) {
3189 // suppress the acknowledgements of our own autoKibitz
3191 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3192 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3193 SendToPlayer(star_match[0], strlen(star_match[0]));
3194 if(looking_at(buf, &i, "*% ")) // eat prompt
3195 suppressKibitz = FALSE;
3199 } // [HGM] kibitz: end of patch
3201 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3203 // [HGM] chat: intercept tells by users for which we have an open chat window
3205 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3206 looking_at(buf, &i, "* whispers:") ||
3207 looking_at(buf, &i, "* kibitzes:") ||
3208 looking_at(buf, &i, "* shouts:") ||
3209 looking_at(buf, &i, "* c-shouts:") ||
3210 looking_at(buf, &i, "--> * ") ||
3211 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3212 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3213 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3214 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3216 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3217 chattingPartner = -1;
3219 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3220 for(p=0; p<MAX_CHAT; p++) {
3221 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3222 talker[0] = '['; strcat(talker, "] ");
3223 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3224 chattingPartner = p; break;
3227 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3228 for(p=0; p<MAX_CHAT; p++) {
3229 if(!strcmp("kibitzes", chatPartner[p])) {
3230 talker[0] = '['; strcat(talker, "] ");
3231 chattingPartner = p; break;
3234 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3235 for(p=0; p<MAX_CHAT; p++) {
3236 if(!strcmp("whispers", chatPartner[p])) {
3237 talker[0] = '['; strcat(talker, "] ");
3238 chattingPartner = p; break;
3241 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3242 if(buf[i-8] == '-' && buf[i-3] == 't')
3243 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3244 if(!strcmp("c-shouts", chatPartner[p])) {
3245 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3246 chattingPartner = p; break;
3249 if(chattingPartner < 0)
3250 for(p=0; p<MAX_CHAT; p++) {
3251 if(!strcmp("shouts", chatPartner[p])) {
3252 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3253 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3254 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3255 chattingPartner = p; break;
3259 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3260 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3261 talker[0] = 0; Colorize(ColorTell, FALSE);
3262 chattingPartner = p; break;
3264 if(chattingPartner<0) i = oldi; else {
3265 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3266 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3267 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3268 started = STARTED_COMMENT;
3269 parse_pos = 0; parse[0] = NULLCHAR;
3270 savingComment = 3 + chattingPartner; // counts as TRUE
3271 suppressKibitz = TRUE;
3274 } // [HGM] chat: end of patch
3277 if (appData.zippyTalk || appData.zippyPlay) {
3278 /* [DM] Backup address for color zippy lines */
3280 if (loggedOn == TRUE)
3281 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3282 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3284 } // [DM] 'else { ' deleted
3286 /* Regular tells and says */
3287 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3288 looking_at(buf, &i, "* (your partner) tells you: ") ||
3289 looking_at(buf, &i, "* says: ") ||
3290 /* Don't color "message" or "messages" output */
3291 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3292 looking_at(buf, &i, "*. * at *:*: ") ||
3293 looking_at(buf, &i, "--* (*:*): ") ||
3294 /* Message notifications (same color as tells) */
3295 looking_at(buf, &i, "* has left a message ") ||
3296 looking_at(buf, &i, "* just sent you a message:\n") ||
3297 /* Whispers and kibitzes */
3298 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3299 looking_at(buf, &i, "* kibitzes: ") ||
3301 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3303 if (tkind == 1 && strchr(star_match[0], ':')) {
3304 /* Avoid "tells you:" spoofs in channels */
3307 if (star_match[0][0] == NULLCHAR ||
3308 strchr(star_match[0], ' ') ||
3309 (tkind == 3 && strchr(star_match[1], ' '))) {
3310 /* Reject bogus matches */
3313 if (appData.colorize) {
3314 if (oldi > next_out) {
3315 SendToPlayer(&buf[next_out], oldi - next_out);
3320 Colorize(ColorTell, FALSE);
3321 curColor = ColorTell;
3324 Colorize(ColorKibitz, FALSE);
3325 curColor = ColorKibitz;
3328 p = strrchr(star_match[1], '(');
3335 Colorize(ColorChannel1, FALSE);
3336 curColor = ColorChannel1;
3338 Colorize(ColorChannel, FALSE);
3339 curColor = ColorChannel;
3343 curColor = ColorNormal;
3347 if (started == STARTED_NONE && appData.autoComment &&
3348 (gameMode == IcsObserving ||
3349 gameMode == IcsPlayingWhite ||
3350 gameMode == IcsPlayingBlack)) {
3351 parse_pos = i - oldi;
3352 memcpy(parse, &buf[oldi], parse_pos);
3353 parse[parse_pos] = NULLCHAR;
3354 started = STARTED_COMMENT;
3355 savingComment = TRUE;
3357 started = STARTED_CHATTER;
3358 savingComment = FALSE;
3365 if (looking_at(buf, &i, "* s-shouts: ") ||
3366 looking_at(buf, &i, "* c-shouts: ")) {
3367 if (appData.colorize) {
3368 if (oldi > next_out) {
3369 SendToPlayer(&buf[next_out], oldi - next_out);
3372 Colorize(ColorSShout, FALSE);
3373 curColor = ColorSShout;
3376 started = STARTED_CHATTER;
3380 if (looking_at(buf, &i, "--->")) {
3385 if (looking_at(buf, &i, "* shouts: ") ||
3386 looking_at(buf, &i, "--> ")) {
3387 if (appData.colorize) {
3388 if (oldi > next_out) {
3389 SendToPlayer(&buf[next_out], oldi - next_out);
3392 Colorize(ColorShout, FALSE);
3393 curColor = ColorShout;
3396 started = STARTED_CHATTER;
3400 if (looking_at( buf, &i, "Challenge:")) {
3401 if (appData.colorize) {
3402 if (oldi > next_out) {
3403 SendToPlayer(&buf[next_out], oldi - next_out);
3406 Colorize(ColorChallenge, FALSE);
3407 curColor = ColorChallenge;
3413 if (looking_at(buf, &i, "* offers you") ||
3414 looking_at(buf, &i, "* offers to be") ||
3415 looking_at(buf, &i, "* would like to") ||
3416 looking_at(buf, &i, "* requests to") ||
3417 looking_at(buf, &i, "Your opponent offers") ||
3418 looking_at(buf, &i, "Your opponent requests")) {
3420 if (appData.colorize) {
3421 if (oldi > next_out) {
3422 SendToPlayer(&buf[next_out], oldi - next_out);
3425 Colorize(ColorRequest, FALSE);
3426 curColor = ColorRequest;
3431 if (looking_at(buf, &i, "* (*) seeking")) {
3432 if (appData.colorize) {
3433 if (oldi > next_out) {
3434 SendToPlayer(&buf[next_out], oldi - next_out);
3437 Colorize(ColorSeek, FALSE);
3438 curColor = ColorSeek;
3443 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3445 if (looking_at(buf, &i, "\\ ")) {
3446 if (prevColor != ColorNormal) {
3447 if (oldi > next_out) {
3448 SendToPlayer(&buf[next_out], oldi - next_out);
3451 Colorize(prevColor, TRUE);
3452 curColor = prevColor;
3454 if (savingComment) {
3455 parse_pos = i - oldi;
3456 memcpy(parse, &buf[oldi], parse_pos);
3457 parse[parse_pos] = NULLCHAR;
3458 started = STARTED_COMMENT;
3459 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3460 chattingPartner = savingComment - 3; // kludge to remember the box
3462 started = STARTED_CHATTER;
3467 if (looking_at(buf, &i, "Black Strength :") ||
3468 looking_at(buf, &i, "<<< style 10 board >>>") ||
3469 looking_at(buf, &i, "<10>") ||
3470 looking_at(buf, &i, "#@#")) {
3471 /* Wrong board style */
3473 SendToICS(ics_prefix);
3474 SendToICS("set style 12\n");
3475 SendToICS(ics_prefix);
3476 SendToICS("refresh\n");
3480 if (looking_at(buf, &i, "login:")) {
3481 if (!have_sent_ICS_logon) {
3483 have_sent_ICS_logon = 1;
3484 else // no init script was found
3485 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3486 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3487 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3492 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3493 (looking_at(buf, &i, "\n<12> ") ||
3494 looking_at(buf, &i, "<12> "))) {
3496 if (oldi > next_out) {
3497 SendToPlayer(&buf[next_out], oldi - next_out);
3500 started = STARTED_BOARD;
3505 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3506 looking_at(buf, &i, "<b1> ")) {
3507 if (oldi > next_out) {
3508 SendToPlayer(&buf[next_out], oldi - next_out);
3511 started = STARTED_HOLDINGS;
3516 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3518 /* Header for a move list -- first line */
3520 switch (ics_getting_history) {
3524 case BeginningOfGame:
3525 /* User typed "moves" or "oldmoves" while we
3526 were idle. Pretend we asked for these
3527 moves and soak them up so user can step
3528 through them and/or save them.
3531 gameMode = IcsObserving;
3534 ics_getting_history = H_GOT_UNREQ_HEADER;
3536 case EditGame: /*?*/
3537 case EditPosition: /*?*/
3538 /* Should above feature work in these modes too? */
3539 /* For now it doesn't */
3540 ics_getting_history = H_GOT_UNWANTED_HEADER;
3543 ics_getting_history = H_GOT_UNWANTED_HEADER;
3548 /* Is this the right one? */
3549 if (gameInfo.white && gameInfo.black &&
3550 strcmp(gameInfo.white, star_match[0]) == 0 &&
3551 strcmp(gameInfo.black, star_match[2]) == 0) {
3553 ics_getting_history = H_GOT_REQ_HEADER;
3556 case H_GOT_REQ_HEADER:
3557 case H_GOT_UNREQ_HEADER:
3558 case H_GOT_UNWANTED_HEADER:
3559 case H_GETTING_MOVES:
3560 /* Should not happen */
3561 DisplayError(_("Error gathering move list: two headers"), 0);
3562 ics_getting_history = H_FALSE;
3566 /* Save player ratings into gameInfo if needed */
3567 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3568 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3569 (gameInfo.whiteRating == -1 ||
3570 gameInfo.blackRating == -1)) {
3572 gameInfo.whiteRating = string_to_rating(star_match[1]);
3573 gameInfo.blackRating = string_to_rating(star_match[3]);
3574 if (appData.debugMode)
3575 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3576 gameInfo.whiteRating, gameInfo.blackRating);
3581 if (looking_at(buf, &i,
3582 "* * match, initial time: * minute*, increment: * second")) {
3583 /* Header for a move list -- second line */
3584 /* Initial board will follow if this is a wild game */
3585 if (gameInfo.event != NULL) free(gameInfo.event);
3586 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3587 gameInfo.event = StrSave(str);
3588 /* [HGM] we switched variant. Translate boards if needed. */
3589 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3593 if (looking_at(buf, &i, "Move ")) {
3594 /* Beginning of a move list */
3595 switch (ics_getting_history) {
3597 /* Normally should not happen */
3598 /* Maybe user hit reset while we were parsing */
3601 /* Happens if we are ignoring a move list that is not
3602 * the one we just requested. Common if the user
3603 * tries to observe two games without turning off
3606 case H_GETTING_MOVES:
3607 /* Should not happen */
3608 DisplayError(_("Error gathering move list: nested"), 0);
3609 ics_getting_history = H_FALSE;
3611 case H_GOT_REQ_HEADER:
3612 ics_getting_history = H_GETTING_MOVES;
3613 started = STARTED_MOVES;
3615 if (oldi > next_out) {
3616 SendToPlayer(&buf[next_out], oldi - next_out);
3619 case H_GOT_UNREQ_HEADER:
3620 ics_getting_history = H_GETTING_MOVES;
3621 started = STARTED_MOVES_NOHIDE;
3624 case H_GOT_UNWANTED_HEADER:
3625 ics_getting_history = H_FALSE;
3631 if (looking_at(buf, &i, "% ") ||
3632 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3633 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3634 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3635 soughtPending = FALSE;
3639 if(suppressKibitz) next_out = i;
3640 savingComment = FALSE;
3644 case STARTED_MOVES_NOHIDE:
3645 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3646 parse[parse_pos + i - oldi] = NULLCHAR;
3647 ParseGameHistory(parse);
3649 if (appData.zippyPlay && first.initDone) {
3650 FeedMovesToProgram(&first, forwardMostMove);
3651 if (gameMode == IcsPlayingWhite) {
3652 if (WhiteOnMove(forwardMostMove)) {
3653 if (first.sendTime) {
3654 if (first.useColors) {
3655 SendToProgram("black\n", &first);
3657 SendTimeRemaining(&first, TRUE);
3659 if (first.useColors) {
3660 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3662 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3663 first.maybeThinking = TRUE;
3665 if (first.usePlayother) {
3666 if (first.sendTime) {
3667 SendTimeRemaining(&first, TRUE);
3669 SendToProgram("playother\n", &first);
3675 } else if (gameMode == IcsPlayingBlack) {
3676 if (!WhiteOnMove(forwardMostMove)) {
3677 if (first.sendTime) {
3678 if (first.useColors) {
3679 SendToProgram("white\n", &first);
3681 SendTimeRemaining(&first, FALSE);
3683 if (first.useColors) {
3684 SendToProgram("black\n", &first);
3686 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3687 first.maybeThinking = TRUE;
3689 if (first.usePlayother) {
3690 if (first.sendTime) {
3691 SendTimeRemaining(&first, FALSE);
3693 SendToProgram("playother\n", &first);
3702 if (gameMode == IcsObserving && ics_gamenum == -1) {
3703 /* Moves came from oldmoves or moves command
3704 while we weren't doing anything else.
3706 currentMove = forwardMostMove;
3707 ClearHighlights();/*!!could figure this out*/
3708 flipView = appData.flipView;
3709 DrawPosition(TRUE, boards[currentMove]);
3710 DisplayBothClocks();
3711 snprintf(str, MSG_SIZ, "%s %s %s",
3712 gameInfo.white, _("vs."), gameInfo.black);
3716 /* Moves were history of an active game */
3717 if (gameInfo.resultDetails != NULL) {
3718 free(gameInfo.resultDetails);
3719 gameInfo.resultDetails = NULL;
3722 HistorySet(parseList, backwardMostMove,
3723 forwardMostMove, currentMove-1);
3724 DisplayMove(currentMove - 1);
3725 if (started == STARTED_MOVES) next_out = i;
3726 started = STARTED_NONE;
3727 ics_getting_history = H_FALSE;
3730 case STARTED_OBSERVE:
3731 started = STARTED_NONE;
3732 SendToICS(ics_prefix);
3733 SendToICS("refresh\n");
3739 if(bookHit) { // [HGM] book: simulate book reply
3740 static char bookMove[MSG_SIZ]; // a bit generous?
3742 programStats.nodes = programStats.depth = programStats.time =
3743 programStats.score = programStats.got_only_move = 0;
3744 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3746 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3747 strcat(bookMove, bookHit);
3748 HandleMachineMove(bookMove, &first);
3753 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3754 started == STARTED_HOLDINGS ||
3755 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3756 /* Accumulate characters in move list or board */
3757 parse[parse_pos++] = buf[i];
3760 /* Start of game messages. Mostly we detect start of game
3761 when the first board image arrives. On some versions
3762 of the ICS, though, we need to do a "refresh" after starting
3763 to observe in order to get the current board right away. */
3764 if (looking_at(buf, &i, "Adding game * to observation list")) {
3765 started = STARTED_OBSERVE;
3769 /* Handle auto-observe */
3770 if (appData.autoObserve &&
3771 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3772 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3774 /* Choose the player that was highlighted, if any. */
3775 if (star_match[0][0] == '\033' ||
3776 star_match[1][0] != '\033') {
3777 player = star_match[0];
3779 player = star_match[2];
3781 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3782 ics_prefix, StripHighlightAndTitle(player));
3785 /* Save ratings from notify string */
3786 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3787 player1Rating = string_to_rating(star_match[1]);
3788 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3789 player2Rating = string_to_rating(star_match[3]);
3791 if (appData.debugMode)
3793 "Ratings from 'Game notification:' %s %d, %s %d\n",
3794 player1Name, player1Rating,
3795 player2Name, player2Rating);
3800 /* Deal with automatic examine mode after a game,
3801 and with IcsObserving -> IcsExamining transition */
3802 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3803 looking_at(buf, &i, "has made you an examiner of game *")) {
3805 int gamenum = atoi(star_match[0]);
3806 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3807 gamenum == ics_gamenum) {
3808 /* We were already playing or observing this game;
3809 no need to refetch history */
3810 gameMode = IcsExamining;
3812 pauseExamForwardMostMove = forwardMostMove;
3813 } else if (currentMove < forwardMostMove) {
3814 ForwardInner(forwardMostMove);
3817 /* I don't think this case really can happen */
3818 SendToICS(ics_prefix);
3819 SendToICS("refresh\n");
3824 /* Error messages */
3825 // if (ics_user_moved) {
3826 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3827 if (looking_at(buf, &i, "Illegal move") ||
3828 looking_at(buf, &i, "Not a legal move") ||
3829 looking_at(buf, &i, "Your king is in check") ||
3830 looking_at(buf, &i, "It isn't your turn") ||
3831 looking_at(buf, &i, "It is not your move")) {
3833 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3834 currentMove = forwardMostMove-1;
3835 DisplayMove(currentMove - 1); /* before DMError */
3836 DrawPosition(FALSE, boards[currentMove]);
3837 SwitchClocks(forwardMostMove-1); // [HGM] race
3838 DisplayBothClocks();
3840 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3846 if (looking_at(buf, &i, "still have time") ||
3847 looking_at(buf, &i, "not out of time") ||
3848 looking_at(buf, &i, "either player is out of time") ||
3849 looking_at(buf, &i, "has timeseal; checking")) {
3850 /* We must have called his flag a little too soon */
3851 whiteFlag = blackFlag = FALSE;
3855 if (looking_at(buf, &i, "added * seconds to") ||
3856 looking_at(buf, &i, "seconds were added to")) {
3857 /* Update the clocks */
3858 SendToICS(ics_prefix);
3859 SendToICS("refresh\n");
3863 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3864 ics_clock_paused = TRUE;
3869 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3870 ics_clock_paused = FALSE;
3875 /* Grab player ratings from the Creating: message.
3876 Note we have to check for the special case when
3877 the ICS inserts things like [white] or [black]. */
3878 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3879 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3881 0 player 1 name (not necessarily white)
3883 2 empty, white, or black (IGNORED)
3884 3 player 2 name (not necessarily black)
3887 The names/ratings are sorted out when the game
3888 actually starts (below).
3890 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3891 player1Rating = string_to_rating(star_match[1]);
3892 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3893 player2Rating = string_to_rating(star_match[4]);
3895 if (appData.debugMode)
3897 "Ratings from 'Creating:' %s %d, %s %d\n",
3898 player1Name, player1Rating,
3899 player2Name, player2Rating);
3904 /* Improved generic start/end-of-game messages */
3905 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3906 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3907 /* If tkind == 0: */
3908 /* star_match[0] is the game number */
3909 /* [1] is the white player's name */
3910 /* [2] is the black player's name */
3911 /* For end-of-game: */
3912 /* [3] is the reason for the game end */
3913 /* [4] is a PGN end game-token, preceded by " " */
3914 /* For start-of-game: */
3915 /* [3] begins with "Creating" or "Continuing" */
3916 /* [4] is " *" or empty (don't care). */
3917 int gamenum = atoi(star_match[0]);
3918 char *whitename, *blackname, *why, *endtoken;
3919 ChessMove endtype = EndOfFile;
3922 whitename = star_match[1];
3923 blackname = star_match[2];
3924 why = star_match[3];
3925 endtoken = star_match[4];
3927 whitename = star_match[1];
3928 blackname = star_match[3];
3929 why = star_match[5];
3930 endtoken = star_match[6];
3933 /* Game start messages */
3934 if (strncmp(why, "Creating ", 9) == 0 ||
3935 strncmp(why, "Continuing ", 11) == 0) {
3936 gs_gamenum = gamenum;
3937 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3938 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3939 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3941 if (appData.zippyPlay) {
3942 ZippyGameStart(whitename, blackname);
3945 partnerBoardValid = FALSE; // [HGM] bughouse
3949 /* Game end messages */
3950 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3951 ics_gamenum != gamenum) {
3954 while (endtoken[0] == ' ') endtoken++;
3955 switch (endtoken[0]) {
3958 endtype = GameUnfinished;
3961 endtype = BlackWins;
3964 if (endtoken[1] == '/')
3965 endtype = GameIsDrawn;
3967 endtype = WhiteWins;
3970 GameEnds(endtype, why, GE_ICS);
3972 if (appData.zippyPlay && first.initDone) {
3973 ZippyGameEnd(endtype, why);
3974 if (first.pr == NoProc) {
3975 /* Start the next process early so that we'll
3976 be ready for the next challenge */
3977 StartChessProgram(&first);
3979 /* Send "new" early, in case this command takes
3980 a long time to finish, so that we'll be ready
3981 for the next challenge. */
3982 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3986 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3990 if (looking_at(buf, &i, "Removing game * from observation") ||
3991 looking_at(buf, &i, "no longer observing game *") ||
3992 looking_at(buf, &i, "Game * (*) has no examiners")) {
3993 if (gameMode == IcsObserving &&
3994 atoi(star_match[0]) == ics_gamenum)
3996 /* icsEngineAnalyze */
3997 if (appData.icsEngineAnalyze) {
4004 ics_user_moved = FALSE;
4009 if (looking_at(buf, &i, "no longer examining game *")) {
4010 if (gameMode == IcsExamining &&
4011 atoi(star_match[0]) == ics_gamenum)
4015 ics_user_moved = FALSE;
4020 /* Advance leftover_start past any newlines we find,
4021 so only partial lines can get reparsed */
4022 if (looking_at(buf, &i, "\n")) {
4023 prevColor = curColor;
4024 if (curColor != ColorNormal) {
4025 if (oldi > next_out) {
4026 SendToPlayer(&buf[next_out], oldi - next_out);
4029 Colorize(ColorNormal, FALSE);
4030 curColor = ColorNormal;
4032 if (started == STARTED_BOARD) {
4033 started = STARTED_NONE;
4034 parse[parse_pos] = NULLCHAR;
4035 ParseBoard12(parse);
4038 /* Send premove here */
4039 if (appData.premove) {
4041 if (currentMove == 0 &&
4042 gameMode == IcsPlayingWhite &&
4043 appData.premoveWhite) {
4044 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4045 if (appData.debugMode)
4046 fprintf(debugFP, "Sending premove:\n");
4048 } else if (currentMove == 1 &&
4049 gameMode == IcsPlayingBlack &&
4050 appData.premoveBlack) {
4051 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4052 if (appData.debugMode)
4053 fprintf(debugFP, "Sending premove:\n");
4055 } else if (gotPremove) {
4057 ClearPremoveHighlights();
4058 if (appData.debugMode)
4059 fprintf(debugFP, "Sending premove:\n");
4060 UserMoveEvent(premoveFromX, premoveFromY,
4061 premoveToX, premoveToY,
4066 /* Usually suppress following prompt */
4067 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4068 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4069 if (looking_at(buf, &i, "*% ")) {
4070 savingComment = FALSE;
4075 } else if (started == STARTED_HOLDINGS) {
4077 char new_piece[MSG_SIZ];
4078 started = STARTED_NONE;
4079 parse[parse_pos] = NULLCHAR;
4080 if (appData.debugMode)
4081 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4082 parse, currentMove);
4083 if (sscanf(parse, " game %d", &gamenum) == 1) {
4084 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4085 if (gameInfo.variant == VariantNormal) {
4086 /* [HGM] We seem to switch variant during a game!
4087 * Presumably no holdings were displayed, so we have
4088 * to move the position two files to the right to
4089 * create room for them!
4091 VariantClass newVariant;
4092 switch(gameInfo.boardWidth) { // base guess on board width
4093 case 9: newVariant = VariantShogi; break;
4094 case 10: newVariant = VariantGreat; break;
4095 default: newVariant = VariantCrazyhouse; break;
4097 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4098 /* Get a move list just to see the header, which
4099 will tell us whether this is really bug or zh */
4100 if (ics_getting_history == H_FALSE) {
4101 ics_getting_history = H_REQUESTED;
4102 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4106 new_piece[0] = NULLCHAR;
4107 sscanf(parse, "game %d white [%s black [%s <- %s",
4108 &gamenum, white_holding, black_holding,
4110 white_holding[strlen(white_holding)-1] = NULLCHAR;
4111 black_holding[strlen(black_holding)-1] = NULLCHAR;
4112 /* [HGM] copy holdings to board holdings area */
4113 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4114 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4115 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4117 if (appData.zippyPlay && first.initDone) {
4118 ZippyHoldings(white_holding, black_holding,
4122 if (tinyLayout || smallLayout) {
4123 char wh[16], bh[16];
4124 PackHolding(wh, white_holding);
4125 PackHolding(bh, black_holding);
4126 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4127 gameInfo.white, gameInfo.black);
4129 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4130 gameInfo.white, white_holding, _("vs."),
4131 gameInfo.black, black_holding);
4133 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4134 DrawPosition(FALSE, boards[currentMove]);
4136 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4137 sscanf(parse, "game %d white [%s black [%s <- %s",
4138 &gamenum, white_holding, black_holding,
4140 white_holding[strlen(white_holding)-1] = NULLCHAR;
4141 black_holding[strlen(black_holding)-1] = NULLCHAR;
4142 /* [HGM] copy holdings to partner-board holdings area */
4143 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4144 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4145 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4146 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4147 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4150 /* Suppress following prompt */
4151 if (looking_at(buf, &i, "*% ")) {
4152 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4153 savingComment = FALSE;
4161 i++; /* skip unparsed character and loop back */
4164 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4165 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4166 // SendToPlayer(&buf[next_out], i - next_out);
4167 started != STARTED_HOLDINGS && leftover_start > next_out) {
4168 SendToPlayer(&buf[next_out], leftover_start - next_out);
4172 leftover_len = buf_len - leftover_start;
4173 /* if buffer ends with something we couldn't parse,
4174 reparse it after appending the next read */
4176 } else if (count == 0) {
4177 RemoveInputSource(isr);
4178 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4180 DisplayFatalError(_("Error reading from ICS"), error, 1);
4185 /* Board style 12 looks like this:
4187 <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
4189 * The "<12> " is stripped before it gets to this routine. The two
4190 * trailing 0's (flip state and clock ticking) are later addition, and
4191 * some chess servers may not have them, or may have only the first.
4192 * Additional trailing fields may be added in the future.
4195 #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"
4197 #define RELATION_OBSERVING_PLAYED 0
4198 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4199 #define RELATION_PLAYING_MYMOVE 1
4200 #define RELATION_PLAYING_NOTMYMOVE -1
4201 #define RELATION_EXAMINING 2
4202 #define RELATION_ISOLATED_BOARD -3
4203 #define RELATION_STARTING_POSITION -4 /* FICS only */
4206 ParseBoard12 (char *string)
4210 char *bookHit = NULL; // [HGM] book
4212 GameMode newGameMode;
4213 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4214 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4215 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4216 char to_play, board_chars[200];
4217 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4218 char black[32], white[32];
4220 int prevMove = currentMove;
4223 int fromX, fromY, toX, toY;
4225 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4226 Boolean weird = FALSE, reqFlag = FALSE;
4228 fromX = fromY = toX = toY = -1;
4232 if (appData.debugMode)
4233 fprintf(debugFP, "Parsing board: %s\n", string);
4235 move_str[0] = NULLCHAR;
4236 elapsed_time[0] = NULLCHAR;
4237 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4239 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4240 if(string[i] == ' ') { ranks++; files = 0; }
4242 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4245 for(j = 0; j <i; j++) board_chars[j] = string[j];
4246 board_chars[i] = '\0';
4249 n = sscanf(string, PATTERN, &to_play, &double_push,
4250 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4251 &gamenum, white, black, &relation, &basetime, &increment,
4252 &white_stren, &black_stren, &white_time, &black_time,
4253 &moveNum, str, elapsed_time, move_str, &ics_flip,
4257 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4258 DisplayError(str, 0);
4262 /* Convert the move number to internal form */
4263 moveNum = (moveNum - 1) * 2;
4264 if (to_play == 'B') moveNum++;
4265 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4266 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4272 case RELATION_OBSERVING_PLAYED:
4273 case RELATION_OBSERVING_STATIC:
4274 if (gamenum == -1) {
4275 /* Old ICC buglet */
4276 relation = RELATION_OBSERVING_STATIC;
4278 newGameMode = IcsObserving;
4280 case RELATION_PLAYING_MYMOVE:
4281 case RELATION_PLAYING_NOTMYMOVE:
4283 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4284 IcsPlayingWhite : IcsPlayingBlack;
4285 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4287 case RELATION_EXAMINING:
4288 newGameMode = IcsExamining;
4290 case RELATION_ISOLATED_BOARD:
4292 /* Just display this board. If user was doing something else,
4293 we will forget about it until the next board comes. */
4294 newGameMode = IcsIdle;
4296 case RELATION_STARTING_POSITION:
4297 newGameMode = gameMode;
4301 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4302 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4303 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4304 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4305 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4306 static int lastBgGame = -1;
4308 for (k = 0; k < ranks; k++) {
4309 for (j = 0; j < files; j++)
4310 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4311 if(gameInfo.holdingsWidth > 1) {
4312 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4313 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4316 CopyBoard(partnerBoard, board);
4317 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4318 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4319 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4320 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4321 if(toSqr = strchr(str, '-')) {
4322 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4323 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4324 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4325 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4326 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4327 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4329 DisplayWhiteClock(white_time*fac, to_play == 'W');
4330 DisplayBlackClock(black_time*fac, to_play != 'W');
4331 activePartner = to_play;
4332 if(gamenum != lastBgGame) {
4334 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4337 lastBgGame = gamenum;
4338 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4339 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4340 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4341 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4342 if(!twoBoards) DisplayMessage(partnerStatus, "");
4343 partnerBoardValid = TRUE;
4347 if(appData.dualBoard && appData.bgObserve) {
4348 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4349 SendToICS(ics_prefix), SendToICS("pobserve\n");
4350 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4352 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4357 /* Modify behavior for initial board display on move listing
4360 switch (ics_getting_history) {
4364 case H_GOT_REQ_HEADER:
4365 case H_GOT_UNREQ_HEADER:
4366 /* This is the initial position of the current game */
4367 gamenum = ics_gamenum;
4368 moveNum = 0; /* old ICS bug workaround */
4369 if (to_play == 'B') {
4370 startedFromSetupPosition = TRUE;
4371 blackPlaysFirst = TRUE;
4373 if (forwardMostMove == 0) forwardMostMove = 1;
4374 if (backwardMostMove == 0) backwardMostMove = 1;
4375 if (currentMove == 0) currentMove = 1;
4377 newGameMode = gameMode;
4378 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4380 case H_GOT_UNWANTED_HEADER:
4381 /* This is an initial board that we don't want */
4383 case H_GETTING_MOVES:
4384 /* Should not happen */
4385 DisplayError(_("Error gathering move list: extra board"), 0);
4386 ics_getting_history = H_FALSE;
4390 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4391 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4392 weird && (int)gameInfo.variant < (int)VariantShogi) {
4393 /* [HGM] We seem to have switched variant unexpectedly
4394 * Try to guess new variant from board size
4396 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4397 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4398 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4399 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4400 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4401 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4402 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4403 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4404 /* Get a move list just to see the header, which
4405 will tell us whether this is really bug or zh */
4406 if (ics_getting_history == H_FALSE) {
4407 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4408 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4413 /* Take action if this is the first board of a new game, or of a
4414 different game than is currently being displayed. */
4415 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4416 relation == RELATION_ISOLATED_BOARD) {
4418 /* Forget the old game and get the history (if any) of the new one */
4419 if (gameMode != BeginningOfGame) {
4423 if (appData.autoRaiseBoard) BoardToTop();
4425 if (gamenum == -1) {
4426 newGameMode = IcsIdle;
4427 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4428 appData.getMoveList && !reqFlag) {
4429 /* Need to get game history */
4430 ics_getting_history = H_REQUESTED;
4431 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4435 /* Initially flip the board to have black on the bottom if playing
4436 black or if the ICS flip flag is set, but let the user change
4437 it with the Flip View button. */
4438 flipView = appData.autoFlipView ?
4439 (newGameMode == IcsPlayingBlack) || ics_flip :
4442 /* Done with values from previous mode; copy in new ones */
4443 gameMode = newGameMode;
4445 ics_gamenum = gamenum;
4446 if (gamenum == gs_gamenum) {
4447 int klen = strlen(gs_kind);
4448 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4449 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4450 gameInfo.event = StrSave(str);
4452 gameInfo.event = StrSave("ICS game");
4454 gameInfo.site = StrSave(appData.icsHost);
4455 gameInfo.date = PGNDate();
4456 gameInfo.round = StrSave("-");
4457 gameInfo.white = StrSave(white);
4458 gameInfo.black = StrSave(black);
4459 timeControl = basetime * 60 * 1000;
4461 timeIncrement = increment * 1000;
4462 movesPerSession = 0;
4463 gameInfo.timeControl = TimeControlTagValue();
4464 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4465 if (appData.debugMode) {
4466 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4467 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4468 setbuf(debugFP, NULL);
4471 gameInfo.outOfBook = NULL;
4473 /* Do we have the ratings? */
4474 if (strcmp(player1Name, white) == 0 &&
4475 strcmp(player2Name, black) == 0) {
4476 if (appData.debugMode)
4477 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4478 player1Rating, player2Rating);
4479 gameInfo.whiteRating = player1Rating;
4480 gameInfo.blackRating = player2Rating;
4481 } else if (strcmp(player2Name, white) == 0 &&
4482 strcmp(player1Name, black) == 0) {
4483 if (appData.debugMode)
4484 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4485 player2Rating, player1Rating);
4486 gameInfo.whiteRating = player2Rating;
4487 gameInfo.blackRating = player1Rating;
4489 player1Name[0] = player2Name[0] = NULLCHAR;
4491 /* Silence shouts if requested */
4492 if (appData.quietPlay &&
4493 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4494 SendToICS(ics_prefix);
4495 SendToICS("set shout 0\n");
4499 /* Deal with midgame name changes */
4501 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4502 if (gameInfo.white) free(gameInfo.white);
4503 gameInfo.white = StrSave(white);
4505 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4506 if (gameInfo.black) free(gameInfo.black);
4507 gameInfo.black = StrSave(black);
4511 /* Throw away game result if anything actually changes in examine mode */
4512 if (gameMode == IcsExamining && !newGame) {
4513 gameInfo.result = GameUnfinished;
4514 if (gameInfo.resultDetails != NULL) {
4515 free(gameInfo.resultDetails);
4516 gameInfo.resultDetails = NULL;
4520 /* In pausing && IcsExamining mode, we ignore boards coming
4521 in if they are in a different variation than we are. */
4522 if (pauseExamInvalid) return;
4523 if (pausing && gameMode == IcsExamining) {
4524 if (moveNum <= pauseExamForwardMostMove) {
4525 pauseExamInvalid = TRUE;
4526 forwardMostMove = pauseExamForwardMostMove;
4531 if (appData.debugMode) {
4532 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4534 /* Parse the board */
4535 for (k = 0; k < ranks; k++) {
4536 for (j = 0; j < files; j++)
4537 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4538 if(gameInfo.holdingsWidth > 1) {
4539 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4540 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4543 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4544 board[5][BOARD_RGHT+1] = WhiteAngel;
4545 board[6][BOARD_RGHT+1] = WhiteMarshall;
4546 board[1][0] = BlackMarshall;
4547 board[2][0] = BlackAngel;
4548 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4550 CopyBoard(boards[moveNum], board);
4551 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4553 startedFromSetupPosition =
4554 !CompareBoards(board, initialPosition);
4555 if(startedFromSetupPosition)
4556 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4559 /* [HGM] Set castling rights. Take the outermost Rooks,
4560 to make it also work for FRC opening positions. Note that board12
4561 is really defective for later FRC positions, as it has no way to
4562 indicate which Rook can castle if they are on the same side of King.
4563 For the initial position we grant rights to the outermost Rooks,
4564 and remember thos rights, and we then copy them on positions
4565 later in an FRC game. This means WB might not recognize castlings with
4566 Rooks that have moved back to their original position as illegal,
4567 but in ICS mode that is not its job anyway.
4569 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4570 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4572 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4573 if(board[0][i] == WhiteRook) j = i;
4574 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4575 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4576 if(board[0][i] == WhiteRook) j = i;
4577 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4578 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4579 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4580 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4581 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4582 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4583 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4585 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4586 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4587 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4588 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4589 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4590 if(board[BOARD_HEIGHT-1][k] == bKing)
4591 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4592 if(gameInfo.variant == VariantTwoKings) {
4593 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4594 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4595 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4598 r = boards[moveNum][CASTLING][0] = initialRights[0];
4599 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4600 r = boards[moveNum][CASTLING][1] = initialRights[1];
4601 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4602 r = boards[moveNum][CASTLING][3] = initialRights[3];
4603 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4604 r = boards[moveNum][CASTLING][4] = initialRights[4];
4605 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4606 /* wildcastle kludge: always assume King has rights */
4607 r = boards[moveNum][CASTLING][2] = initialRights[2];
4608 r = boards[moveNum][CASTLING][5] = initialRights[5];
4610 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4611 boards[moveNum][EP_STATUS] = EP_NONE;
4612 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4613 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4614 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4617 if (ics_getting_history == H_GOT_REQ_HEADER ||
4618 ics_getting_history == H_GOT_UNREQ_HEADER) {
4619 /* This was an initial position from a move list, not
4620 the current position */
4624 /* Update currentMove and known move number limits */
4625 newMove = newGame || moveNum > forwardMostMove;
4628 forwardMostMove = backwardMostMove = currentMove = moveNum;
4629 if (gameMode == IcsExamining && moveNum == 0) {
4630 /* Workaround for ICS limitation: we are not told the wild
4631 type when starting to examine a game. But if we ask for
4632 the move list, the move list header will tell us */
4633 ics_getting_history = H_REQUESTED;
4634 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4637 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4638 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4640 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4641 /* [HGM] applied this also to an engine that is silently watching */
4642 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4643 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4644 gameInfo.variant == currentlyInitializedVariant) {
4645 takeback = forwardMostMove - moveNum;
4646 for (i = 0; i < takeback; i++) {
4647 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4648 SendToProgram("undo\n", &first);
4653 forwardMostMove = moveNum;
4654 if (!pausing || currentMove > forwardMostMove)
4655 currentMove = forwardMostMove;
4657 /* New part of history that is not contiguous with old part */
4658 if (pausing && gameMode == IcsExamining) {
4659 pauseExamInvalid = TRUE;
4660 forwardMostMove = pauseExamForwardMostMove;
4663 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4665 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4666 // [HGM] when we will receive the move list we now request, it will be
4667 // fed to the engine from the first move on. So if the engine is not
4668 // in the initial position now, bring it there.
4669 InitChessProgram(&first, 0);
4672 ics_getting_history = H_REQUESTED;
4673 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4676 forwardMostMove = backwardMostMove = currentMove = moveNum;
4679 /* Update the clocks */
4680 if (strchr(elapsed_time, '.')) {
4682 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4683 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4685 /* Time is in seconds */
4686 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4687 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4692 if (appData.zippyPlay && newGame &&
4693 gameMode != IcsObserving && gameMode != IcsIdle &&
4694 gameMode != IcsExamining)
4695 ZippyFirstBoard(moveNum, basetime, increment);
4698 /* Put the move on the move list, first converting
4699 to canonical algebraic form. */
4701 if (appData.debugMode) {
4702 int f = forwardMostMove;
4703 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4704 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4705 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4706 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4707 fprintf(debugFP, "moveNum = %d\n", moveNum);
4708 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4709 setbuf(debugFP, NULL);
4711 if (moveNum <= backwardMostMove) {
4712 /* We don't know what the board looked like before
4714 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4715 strcat(parseList[moveNum - 1], " ");
4716 strcat(parseList[moveNum - 1], elapsed_time);
4717 moveList[moveNum - 1][0] = NULLCHAR;
4718 } else if (strcmp(move_str, "none") == 0) {
4719 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4720 /* Again, we don't know what the board looked like;
4721 this is really the start of the game. */
4722 parseList[moveNum - 1][0] = NULLCHAR;
4723 moveList[moveNum - 1][0] = NULLCHAR;
4724 backwardMostMove = moveNum;
4725 startedFromSetupPosition = TRUE;
4726 fromX = fromY = toX = toY = -1;
4728 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4729 // So we parse the long-algebraic move string in stead of the SAN move
4730 int valid; char buf[MSG_SIZ], *prom;
4732 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4733 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4734 // str looks something like "Q/a1-a2"; kill the slash
4736 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4737 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4738 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4739 strcat(buf, prom); // long move lacks promo specification!
4740 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4741 if(appData.debugMode)
4742 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4743 safeStrCpy(move_str, buf, MSG_SIZ);
4745 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4746 &fromX, &fromY, &toX, &toY, &promoChar)
4747 || ParseOneMove(buf, moveNum - 1, &moveType,
4748 &fromX, &fromY, &toX, &toY, &promoChar);
4749 // end of long SAN patch
4751 (void) CoordsToAlgebraic(boards[moveNum - 1],
4752 PosFlags(moveNum - 1),
4753 fromY, fromX, toY, toX, promoChar,
4754 parseList[moveNum-1]);
4755 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4761 if(gameInfo.variant != VariantShogi)
4762 strcat(parseList[moveNum - 1], "+");
4765 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4766 strcat(parseList[moveNum - 1], "#");
4769 strcat(parseList[moveNum - 1], " ");
4770 strcat(parseList[moveNum - 1], elapsed_time);
4771 /* currentMoveString is set as a side-effect of ParseOneMove */
4772 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4773 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4774 strcat(moveList[moveNum - 1], "\n");
4776 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4777 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4778 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4779 ChessSquare old, new = boards[moveNum][k][j];
4780 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4781 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4782 if(old == new) continue;
4783 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4784 else if(new == WhiteWazir || new == BlackWazir) {
4785 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4786 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4787 else boards[moveNum][k][j] = old; // preserve type of Gold
4788 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4789 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4792 /* Move from ICS was illegal!? Punt. */
4793 if (appData.debugMode) {
4794 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4795 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4797 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4798 strcat(parseList[moveNum - 1], " ");
4799 strcat(parseList[moveNum - 1], elapsed_time);
4800 moveList[moveNum - 1][0] = NULLCHAR;
4801 fromX = fromY = toX = toY = -1;
4804 if (appData.debugMode) {
4805 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4806 setbuf(debugFP, NULL);
4810 /* Send move to chess program (BEFORE animating it). */
4811 if (appData.zippyPlay && !newGame && newMove &&
4812 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4814 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4815 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4816 if (moveList[moveNum - 1][0] == NULLCHAR) {
4817 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4819 DisplayError(str, 0);
4821 if (first.sendTime) {
4822 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4824 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4825 if (firstMove && !bookHit) {
4827 if (first.useColors) {
4828 SendToProgram(gameMode == IcsPlayingWhite ?
4830 "black\ngo\n", &first);
4832 SendToProgram("go\n", &first);
4834 first.maybeThinking = TRUE;
4837 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4838 if (moveList[moveNum - 1][0] == NULLCHAR) {
4839 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4840 DisplayError(str, 0);
4842 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4843 SendMoveToProgram(moveNum - 1, &first);
4850 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4851 /* If move comes from a remote source, animate it. If it
4852 isn't remote, it will have already been animated. */
4853 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4854 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4856 if (!pausing && appData.highlightLastMove) {
4857 SetHighlights(fromX, fromY, toX, toY);
4861 /* Start the clocks */
4862 whiteFlag = blackFlag = FALSE;
4863 appData.clockMode = !(basetime == 0 && increment == 0);
4865 ics_clock_paused = TRUE;
4867 } else if (ticking == 1) {
4868 ics_clock_paused = FALSE;
4870 if (gameMode == IcsIdle ||
4871 relation == RELATION_OBSERVING_STATIC ||
4872 relation == RELATION_EXAMINING ||
4874 DisplayBothClocks();
4878 /* Display opponents and material strengths */
4879 if (gameInfo.variant != VariantBughouse &&
4880 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4881 if (tinyLayout || smallLayout) {
4882 if(gameInfo.variant == VariantNormal)
4883 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4884 gameInfo.white, white_stren, gameInfo.black, black_stren,
4885 basetime, increment);
4887 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4888 gameInfo.white, white_stren, gameInfo.black, black_stren,
4889 basetime, increment, (int) gameInfo.variant);
4891 if(gameInfo.variant == VariantNormal)
4892 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4893 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4894 basetime, increment);
4896 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4897 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4898 basetime, increment, VariantName(gameInfo.variant));
4901 if (appData.debugMode) {
4902 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4907 /* Display the board */
4908 if (!pausing && !appData.noGUI) {
4910 if (appData.premove)
4912 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4913 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4914 ClearPremoveHighlights();
4916 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4917 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4918 DrawPosition(j, boards[currentMove]);
4920 DisplayMove(moveNum - 1);
4921 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4922 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4923 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4924 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4928 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4930 if(bookHit) { // [HGM] book: simulate book reply
4931 static char bookMove[MSG_SIZ]; // a bit generous?
4933 programStats.nodes = programStats.depth = programStats.time =
4934 programStats.score = programStats.got_only_move = 0;
4935 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4937 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4938 strcat(bookMove, bookHit);
4939 HandleMachineMove(bookMove, &first);
4948 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4949 ics_getting_history = H_REQUESTED;
4950 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4956 SendToBoth (char *msg)
4957 { // to make it easy to keep two engines in step in dual analysis
4958 SendToProgram(msg, &first);
4959 if(second.analyzing) SendToProgram(msg, &second);
4963 AnalysisPeriodicEvent (int force)
4965 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4966 && !force) || !appData.periodicUpdates)
4969 /* Send . command to Crafty to collect stats */
4972 /* Don't send another until we get a response (this makes
4973 us stop sending to old Crafty's which don't understand
4974 the "." command (sending illegal cmds resets node count & time,
4975 which looks bad)) */
4976 programStats.ok_to_send = 0;
4980 ics_update_width (int new_width)
4982 ics_printf("set width %d\n", new_width);
4986 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4990 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4991 // null move in variant where engine does not understand it (for analysis purposes)
4992 SendBoard(cps, moveNum + 1); // send position after move in stead.
4995 if (cps->useUsermove) {
4996 SendToProgram("usermove ", cps);
5000 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5001 int len = space - parseList[moveNum];
5002 memcpy(buf, parseList[moveNum], len);
5004 buf[len] = NULLCHAR;
5006 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5008 SendToProgram(buf, cps);
5010 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5011 AlphaRank(moveList[moveNum], 4);
5012 SendToProgram(moveList[moveNum], cps);
5013 AlphaRank(moveList[moveNum], 4); // and back
5015 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5016 * the engine. It would be nice to have a better way to identify castle
5018 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5019 && cps->useOOCastle) {
5020 int fromX = moveList[moveNum][0] - AAA;
5021 int fromY = moveList[moveNum][1] - ONE;
5022 int toX = moveList[moveNum][2] - AAA;
5023 int toY = moveList[moveNum][3] - ONE;
5024 if((boards[moveNum][fromY][fromX] == WhiteKing
5025 && boards[moveNum][toY][toX] == WhiteRook)
5026 || (boards[moveNum][fromY][fromX] == BlackKing
5027 && boards[moveNum][toY][toX] == BlackRook)) {
5028 if(toX > fromX) SendToProgram("O-O\n", cps);
5029 else SendToProgram("O-O-O\n", cps);
5031 else SendToProgram(moveList[moveNum], cps);
5033 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5034 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5035 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5036 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5037 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5039 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5040 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5041 SendToProgram(buf, cps);
5043 else SendToProgram(moveList[moveNum], cps);
5044 /* End of additions by Tord */
5047 /* [HGM] setting up the opening has brought engine in force mode! */
5048 /* Send 'go' if we are in a mode where machine should play. */
5049 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5050 (gameMode == TwoMachinesPlay ||
5052 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5054 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5055 SendToProgram("go\n", cps);
5056 if (appData.debugMode) {
5057 fprintf(debugFP, "(extra)\n");
5060 setboardSpoiledMachineBlack = 0;
5064 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5066 char user_move[MSG_SIZ];
5069 if(gameInfo.variant == VariantSChess && promoChar) {
5070 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5071 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5072 } else suffix[0] = NULLCHAR;
5076 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5077 (int)moveType, fromX, fromY, toX, toY);
5078 DisplayError(user_move + strlen("say "), 0);
5080 case WhiteKingSideCastle:
5081 case BlackKingSideCastle:
5082 case WhiteQueenSideCastleWild:
5083 case BlackQueenSideCastleWild:
5085 case WhiteHSideCastleFR:
5086 case BlackHSideCastleFR:
5088 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5090 case WhiteQueenSideCastle:
5091 case BlackQueenSideCastle:
5092 case WhiteKingSideCastleWild:
5093 case BlackKingSideCastleWild:
5095 case WhiteASideCastleFR:
5096 case BlackASideCastleFR:
5098 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5100 case WhiteNonPromotion:
5101 case BlackNonPromotion:
5102 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5104 case WhitePromotion:
5105 case BlackPromotion:
5106 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5107 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5108 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5109 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5110 PieceToChar(WhiteFerz));
5111 else if(gameInfo.variant == VariantGreat)
5112 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5113 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5114 PieceToChar(WhiteMan));
5116 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5117 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5123 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5124 ToUpper(PieceToChar((ChessSquare) fromX)),
5125 AAA + toX, ONE + toY);
5127 case IllegalMove: /* could be a variant we don't quite understand */
5128 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5130 case WhiteCapturesEnPassant:
5131 case BlackCapturesEnPassant:
5132 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5133 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5136 SendToICS(user_move);
5137 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5138 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5143 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5144 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5145 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5146 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5147 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5150 if(gameMode != IcsExamining) { // is this ever not the case?
5151 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5153 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5154 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5155 } else { // on FICS we must first go to general examine mode
5156 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5158 if(gameInfo.variant != VariantNormal) {
5159 // try figure out wild number, as xboard names are not always valid on ICS
5160 for(i=1; i<=36; i++) {
5161 snprintf(buf, MSG_SIZ, "wild/%d", i);
5162 if(StringToVariant(buf) == gameInfo.variant) break;
5164 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5165 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5166 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5167 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5168 SendToICS(ics_prefix);
5170 if(startedFromSetupPosition || backwardMostMove != 0) {
5171 fen = PositionToFEN(backwardMostMove, NULL, 1);
5172 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5173 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5175 } else { // FICS: everything has to set by separate bsetup commands
5176 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5177 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5179 if(!WhiteOnMove(backwardMostMove)) {
5180 SendToICS("bsetup tomove black\n");
5182 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5183 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5185 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5186 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5188 i = boards[backwardMostMove][EP_STATUS];
5189 if(i >= 0) { // set e.p.
5190 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5196 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5197 SendToICS("bsetup done\n"); // switch to normal examining.
5199 for(i = backwardMostMove; i<last; i++) {
5201 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5202 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5203 int len = strlen(moveList[i]);
5204 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5205 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5209 SendToICS(ics_prefix);
5210 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5214 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5216 if (rf == DROP_RANK) {
5217 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5218 sprintf(move, "%c@%c%c\n",
5219 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5221 if (promoChar == 'x' || promoChar == NULLCHAR) {
5222 sprintf(move, "%c%c%c%c\n",
5223 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5225 sprintf(move, "%c%c%c%c%c\n",
5226 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5232 ProcessICSInitScript (FILE *f)
5236 while (fgets(buf, MSG_SIZ, f)) {
5237 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5244 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5245 static ClickType lastClickType;
5250 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5251 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5252 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5253 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5254 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5255 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5258 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5259 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5260 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5261 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5262 if(!step) step = -1;
5263 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5264 appData.testLegality && (promoSweep == king ||
5265 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5267 int victim = boards[currentMove][toY][toX];
5268 boards[currentMove][toY][toX] = promoSweep;
5269 DrawPosition(FALSE, boards[currentMove]);
5270 boards[currentMove][toY][toX] = victim;
5272 ChangeDragPiece(promoSweep);
5276 PromoScroll (int x, int y)
5280 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5281 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5282 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5283 if(!step) return FALSE;
5284 lastX = x; lastY = y;
5285 if((promoSweep < BlackPawn) == flipView) step = -step;
5286 if(step > 0) selectFlag = 1;
5287 if(!selectFlag) Sweep(step);
5292 NextPiece (int step)
5294 ChessSquare piece = boards[currentMove][toY][toX];
5297 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5298 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5299 if(!step) step = -1;
5300 } while(PieceToChar(pieceSweep) == '.');
5301 boards[currentMove][toY][toX] = pieceSweep;
5302 DrawPosition(FALSE, boards[currentMove]);
5303 boards[currentMove][toY][toX] = piece;
5305 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5307 AlphaRank (char *move, int n)
5309 // char *p = move, c; int x, y;
5311 if (appData.debugMode) {
5312 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5316 move[2]>='0' && move[2]<='9' &&
5317 move[3]>='a' && move[3]<='x' ) {
5319 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5320 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5322 if(move[0]>='0' && move[0]<='9' &&
5323 move[1]>='a' && move[1]<='x' &&
5324 move[2]>='0' && move[2]<='9' &&
5325 move[3]>='a' && move[3]<='x' ) {
5326 /* input move, Shogi -> normal */
5327 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5328 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5329 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5330 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5333 move[3]>='0' && move[3]<='9' &&
5334 move[2]>='a' && move[2]<='x' ) {
5336 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5337 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5340 move[0]>='a' && move[0]<='x' &&
5341 move[3]>='0' && move[3]<='9' &&
5342 move[2]>='a' && move[2]<='x' ) {
5343 /* output move, normal -> Shogi */
5344 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5345 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5346 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5347 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5348 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5350 if (appData.debugMode) {
5351 fprintf(debugFP, " out = '%s'\n", move);
5355 char yy_textstr[8000];
5357 /* Parser for moves from gnuchess, ICS, or user typein box */
5359 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5361 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5363 switch (*moveType) {
5364 case WhitePromotion:
5365 case BlackPromotion:
5366 case WhiteNonPromotion:
5367 case BlackNonPromotion:
5369 case WhiteCapturesEnPassant:
5370 case BlackCapturesEnPassant:
5371 case WhiteKingSideCastle:
5372 case WhiteQueenSideCastle:
5373 case BlackKingSideCastle:
5374 case BlackQueenSideCastle:
5375 case WhiteKingSideCastleWild:
5376 case WhiteQueenSideCastleWild:
5377 case BlackKingSideCastleWild:
5378 case BlackQueenSideCastleWild:
5379 /* Code added by Tord: */
5380 case WhiteHSideCastleFR:
5381 case WhiteASideCastleFR:
5382 case BlackHSideCastleFR:
5383 case BlackASideCastleFR:
5384 /* End of code added by Tord */
5385 case IllegalMove: /* bug or odd chess variant */
5386 *fromX = currentMoveString[0] - AAA;
5387 *fromY = currentMoveString[1] - ONE;
5388 *toX = currentMoveString[2] - AAA;
5389 *toY = currentMoveString[3] - ONE;
5390 *promoChar = currentMoveString[4];
5391 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5392 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5393 if (appData.debugMode) {
5394 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5396 *fromX = *fromY = *toX = *toY = 0;
5399 if (appData.testLegality) {
5400 return (*moveType != IllegalMove);
5402 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5403 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5408 *fromX = *moveType == WhiteDrop ?
5409 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5410 (int) CharToPiece(ToLower(currentMoveString[0]));
5412 *toX = currentMoveString[2] - AAA;
5413 *toY = currentMoveString[3] - ONE;
5414 *promoChar = NULLCHAR;
5418 case ImpossibleMove:
5428 if (appData.debugMode) {
5429 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5432 *fromX = *fromY = *toX = *toY = 0;
5433 *promoChar = NULLCHAR;
5438 Boolean pushed = FALSE;
5439 char *lastParseAttempt;
5442 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5443 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5444 int fromX, fromY, toX, toY; char promoChar;
5449 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5450 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5451 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5454 endPV = forwardMostMove;
5456 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5457 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5458 lastParseAttempt = pv;
5459 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5460 if(!valid && nr == 0 &&
5461 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5462 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5463 // Hande case where played move is different from leading PV move
5464 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5465 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5466 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5467 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5468 endPV += 2; // if position different, keep this
5469 moveList[endPV-1][0] = fromX + AAA;
5470 moveList[endPV-1][1] = fromY + ONE;
5471 moveList[endPV-1][2] = toX + AAA;
5472 moveList[endPV-1][3] = toY + ONE;
5473 parseList[endPV-1][0] = NULLCHAR;
5474 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5477 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5478 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5479 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5480 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5481 valid++; // allow comments in PV
5485 if(endPV+1 > framePtr) break; // no space, truncate
5488 CopyBoard(boards[endPV], boards[endPV-1]);
5489 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5490 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5491 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5492 CoordsToAlgebraic(boards[endPV - 1],
5493 PosFlags(endPV - 1),
5494 fromY, fromX, toY, toX, promoChar,
5495 parseList[endPV - 1]);
5497 if(atEnd == 2) return; // used hidden, for PV conversion
5498 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5499 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5500 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5501 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5502 DrawPosition(TRUE, boards[currentMove]);
5506 MultiPV (ChessProgramState *cps)
5507 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5509 for(i=0; i<cps->nrOptions; i++)
5510 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5515 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5518 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5520 int startPV, multi, lineStart, origIndex = index;
5521 char *p, buf2[MSG_SIZ];
5522 ChessProgramState *cps = (pane ? &second : &first);
5524 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5525 lastX = x; lastY = y;
5526 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5527 lineStart = startPV = index;
5528 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5529 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5531 do{ while(buf[index] && buf[index] != '\n') index++;
5532 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5534 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5535 int n = cps->option[multi].value;
5536 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5537 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5538 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5539 cps->option[multi].value = n;
5542 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5543 ExcludeClick(origIndex - lineStart);
5546 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5547 *start = startPV; *end = index-1;
5548 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5555 static char buf[10*MSG_SIZ];
5556 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5558 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5559 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5560 for(i = forwardMostMove; i<endPV; i++){
5561 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5562 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5565 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5566 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5567 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5573 LoadPV (int x, int y)
5574 { // called on right mouse click to load PV
5575 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5576 lastX = x; lastY = y;
5577 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5585 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5586 if(endPV < 0) return;
5587 if(appData.autoCopyPV) CopyFENToClipboard();
5589 if(extendGame && currentMove > forwardMostMove) {
5590 Boolean saveAnimate = appData.animate;
5592 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5593 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5594 } else storedGames--; // abandon shelved tail of original game
5597 forwardMostMove = currentMove;
5598 currentMove = oldFMM;
5599 appData.animate = FALSE;
5600 ToNrEvent(forwardMostMove);
5601 appData.animate = saveAnimate;
5603 currentMove = forwardMostMove;
5604 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5605 ClearPremoveHighlights();
5606 DrawPosition(TRUE, boards[currentMove]);
5610 MovePV (int x, int y, int h)
5611 { // step through PV based on mouse coordinates (called on mouse move)
5612 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5614 // we must somehow check if right button is still down (might be released off board!)
5615 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5616 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5617 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5619 lastX = x; lastY = y;
5621 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5622 if(endPV < 0) return;
5623 if(y < margin) step = 1; else
5624 if(y > h - margin) step = -1;
5625 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5626 currentMove += step;
5627 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5628 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5629 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5630 DrawPosition(FALSE, boards[currentMove]);
5634 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5635 // All positions will have equal probability, but the current method will not provide a unique
5636 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5642 int piecesLeft[(int)BlackPawn];
5643 int seed, nrOfShuffles;
5646 GetPositionNumber ()
5647 { // sets global variable seed
5650 seed = appData.defaultFrcPosition;
5651 if(seed < 0) { // randomize based on time for negative FRC position numbers
5652 for(i=0; i<50; i++) seed += random();
5653 seed = random() ^ random() >> 8 ^ random() << 8;
5654 if(seed<0) seed = -seed;
5659 put (Board board, int pieceType, int rank, int n, int shade)
5660 // put the piece on the (n-1)-th empty squares of the given shade
5664 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5665 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5666 board[rank][i] = (ChessSquare) pieceType;
5667 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5669 piecesLeft[pieceType]--;
5678 AddOnePiece (Board board, int pieceType, int rank, int shade)
5679 // calculate where the next piece goes, (any empty square), and put it there
5683 i = seed % squaresLeft[shade];
5684 nrOfShuffles *= squaresLeft[shade];
5685 seed /= squaresLeft[shade];
5686 put(board, pieceType, rank, i, shade);
5690 AddTwoPieces (Board board, int pieceType, int rank)
5691 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5693 int i, n=squaresLeft[ANY], j=n-1, k;
5695 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5696 i = seed % k; // pick one
5699 while(i >= j) i -= j--;
5700 j = n - 1 - j; i += j;
5701 put(board, pieceType, rank, j, ANY);
5702 put(board, pieceType, rank, i, ANY);
5706 SetUpShuffle (Board board, int number)
5710 GetPositionNumber(); nrOfShuffles = 1;
5712 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5713 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5714 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5716 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5718 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5719 p = (int) board[0][i];
5720 if(p < (int) BlackPawn) piecesLeft[p] ++;
5721 board[0][i] = EmptySquare;
5724 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5725 // shuffles restricted to allow normal castling put KRR first
5726 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5727 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5728 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5729 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5730 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5731 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5732 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5733 put(board, WhiteRook, 0, 0, ANY);
5734 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5737 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5738 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5739 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5740 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5741 while(piecesLeft[p] >= 2) {
5742 AddOnePiece(board, p, 0, LITE);
5743 AddOnePiece(board, p, 0, DARK);
5745 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5748 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5749 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5750 // but we leave King and Rooks for last, to possibly obey FRC restriction
5751 if(p == (int)WhiteRook) continue;
5752 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5753 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5756 // now everything is placed, except perhaps King (Unicorn) and Rooks
5758 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5759 // Last King gets castling rights
5760 while(piecesLeft[(int)WhiteUnicorn]) {
5761 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5762 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5765 while(piecesLeft[(int)WhiteKing]) {
5766 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5767 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5772 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5773 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5776 // Only Rooks can be left; simply place them all
5777 while(piecesLeft[(int)WhiteRook]) {
5778 i = put(board, WhiteRook, 0, 0, ANY);
5779 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5782 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5784 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5787 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5788 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5791 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5795 SetCharTable (char *table, const char * map)
5796 /* [HGM] moved here from winboard.c because of its general usefulness */
5797 /* Basically a safe strcpy that uses the last character as King */
5799 int result = FALSE; int NrPieces;
5801 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5802 && NrPieces >= 12 && !(NrPieces&1)) {
5803 int i; /* [HGM] Accept even length from 12 to 34 */
5805 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5806 for( i=0; i<NrPieces/2-1; i++ ) {
5808 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5810 table[(int) WhiteKing] = map[NrPieces/2-1];
5811 table[(int) BlackKing] = map[NrPieces-1];
5820 Prelude (Board board)
5821 { // [HGM] superchess: random selection of exo-pieces
5822 int i, j, k; ChessSquare p;
5823 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5825 GetPositionNumber(); // use FRC position number
5827 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5828 SetCharTable(pieceToChar, appData.pieceToCharTable);
5829 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5830 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5833 j = seed%4; seed /= 4;
5834 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5835 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5836 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5837 j = seed%3 + (seed%3 >= j); seed /= 3;
5838 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5839 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5840 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5841 j = seed%3; seed /= 3;
5842 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5843 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5844 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5845 j = seed%2 + (seed%2 >= j); seed /= 2;
5846 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5847 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5848 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5849 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5850 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5851 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5852 put(board, exoPieces[0], 0, 0, ANY);
5853 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5857 InitPosition (int redraw)
5859 ChessSquare (* pieces)[BOARD_FILES];
5860 int i, j, pawnRow, overrule,
5861 oldx = gameInfo.boardWidth,
5862 oldy = gameInfo.boardHeight,
5863 oldh = gameInfo.holdingsWidth;
5866 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5868 /* [AS] Initialize pv info list [HGM] and game status */
5870 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5871 pvInfoList[i].depth = 0;
5872 boards[i][EP_STATUS] = EP_NONE;
5873 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5876 initialRulePlies = 0; /* 50-move counter start */
5878 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5879 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5883 /* [HGM] logic here is completely changed. In stead of full positions */
5884 /* the initialized data only consist of the two backranks. The switch */
5885 /* selects which one we will use, which is than copied to the Board */
5886 /* initialPosition, which for the rest is initialized by Pawns and */
5887 /* empty squares. This initial position is then copied to boards[0], */
5888 /* possibly after shuffling, so that it remains available. */
5890 gameInfo.holdingsWidth = 0; /* default board sizes */
5891 gameInfo.boardWidth = 8;
5892 gameInfo.boardHeight = 8;
5893 gameInfo.holdingsSize = 0;
5894 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5895 for(i=0; i<BOARD_FILES-2; i++)
5896 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5897 initialPosition[EP_STATUS] = EP_NONE;
5898 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5899 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5900 SetCharTable(pieceNickName, appData.pieceNickNames);
5901 else SetCharTable(pieceNickName, "............");
5904 switch (gameInfo.variant) {
5905 case VariantFischeRandom:
5906 shuffleOpenings = TRUE;
5909 case VariantShatranj:
5910 pieces = ShatranjArray;
5911 nrCastlingRights = 0;
5912 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5915 pieces = makrukArray;
5916 nrCastlingRights = 0;
5917 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5920 pieces = aseanArray;
5921 nrCastlingRights = 0;
5922 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5924 case VariantTwoKings:
5925 pieces = twoKingsArray;
5928 pieces = GrandArray;
5929 nrCastlingRights = 0;
5930 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5931 gameInfo.boardWidth = 10;
5932 gameInfo.boardHeight = 10;
5933 gameInfo.holdingsSize = 7;
5935 case VariantCapaRandom:
5936 shuffleOpenings = TRUE;
5937 case VariantCapablanca:
5938 pieces = CapablancaArray;
5939 gameInfo.boardWidth = 10;
5940 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5943 pieces = GothicArray;
5944 gameInfo.boardWidth = 10;
5945 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5948 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5949 gameInfo.holdingsSize = 7;
5950 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5953 pieces = JanusArray;
5954 gameInfo.boardWidth = 10;
5955 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5956 nrCastlingRights = 6;
5957 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5958 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5959 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5960 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5961 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5962 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5965 pieces = FalconArray;
5966 gameInfo.boardWidth = 10;
5967 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5969 case VariantXiangqi:
5970 pieces = XiangqiArray;
5971 gameInfo.boardWidth = 9;
5972 gameInfo.boardHeight = 10;
5973 nrCastlingRights = 0;
5974 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5977 pieces = ShogiArray;
5978 gameInfo.boardWidth = 9;
5979 gameInfo.boardHeight = 9;
5980 gameInfo.holdingsSize = 7;
5981 nrCastlingRights = 0;
5982 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5984 case VariantCourier:
5985 pieces = CourierArray;
5986 gameInfo.boardWidth = 12;
5987 nrCastlingRights = 0;
5988 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5990 case VariantKnightmate:
5991 pieces = KnightmateArray;
5992 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5994 case VariantSpartan:
5995 pieces = SpartanArray;
5996 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5999 pieces = fairyArray;
6000 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6003 pieces = GreatArray;
6004 gameInfo.boardWidth = 10;
6005 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6006 gameInfo.holdingsSize = 8;
6010 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6011 gameInfo.holdingsSize = 8;
6012 startedFromSetupPosition = TRUE;
6014 case VariantCrazyhouse:
6015 case VariantBughouse:
6017 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6018 gameInfo.holdingsSize = 5;
6020 case VariantWildCastle:
6022 /* !!?shuffle with kings guaranteed to be on d or e file */
6023 shuffleOpenings = 1;
6025 case VariantNoCastle:
6027 nrCastlingRights = 0;
6028 /* !!?unconstrained back-rank shuffle */
6029 shuffleOpenings = 1;
6034 if(appData.NrFiles >= 0) {
6035 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6036 gameInfo.boardWidth = appData.NrFiles;
6038 if(appData.NrRanks >= 0) {
6039 gameInfo.boardHeight = appData.NrRanks;
6041 if(appData.holdingsSize >= 0) {
6042 i = appData.holdingsSize;
6043 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6044 gameInfo.holdingsSize = i;
6046 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6047 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6048 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6050 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6051 if(pawnRow < 1) pawnRow = 1;
6052 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6054 /* User pieceToChar list overrules defaults */
6055 if(appData.pieceToCharTable != NULL)
6056 SetCharTable(pieceToChar, appData.pieceToCharTable);
6058 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6060 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6061 s = (ChessSquare) 0; /* account holding counts in guard band */
6062 for( i=0; i<BOARD_HEIGHT; i++ )
6063 initialPosition[i][j] = s;
6065 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6066 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6067 initialPosition[pawnRow][j] = WhitePawn;
6068 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6069 if(gameInfo.variant == VariantXiangqi) {
6071 initialPosition[pawnRow][j] =
6072 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6073 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6074 initialPosition[2][j] = WhiteCannon;
6075 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6079 if(gameInfo.variant == VariantGrand) {
6080 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6081 initialPosition[0][j] = WhiteRook;
6082 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6085 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
6087 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6090 initialPosition[1][j] = WhiteBishop;
6091 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6093 initialPosition[1][j] = WhiteRook;
6094 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6097 if( nrCastlingRights == -1) {
6098 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6099 /* This sets default castling rights from none to normal corners */
6100 /* Variants with other castling rights must set them themselves above */
6101 nrCastlingRights = 6;
6103 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6104 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6105 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6106 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6107 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6108 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6111 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6112 if(gameInfo.variant == VariantGreat) { // promotion commoners
6113 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6114 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6115 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6116 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6118 if( gameInfo.variant == VariantSChess ) {
6119 initialPosition[1][0] = BlackMarshall;
6120 initialPosition[2][0] = BlackAngel;
6121 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6122 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6123 initialPosition[1][1] = initialPosition[2][1] =
6124 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6126 if (appData.debugMode) {
6127 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6129 if(shuffleOpenings) {
6130 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6131 startedFromSetupPosition = TRUE;
6133 if(startedFromPositionFile) {
6134 /* [HGM] loadPos: use PositionFile for every new game */
6135 CopyBoard(initialPosition, filePosition);
6136 for(i=0; i<nrCastlingRights; i++)
6137 initialRights[i] = filePosition[CASTLING][i];
6138 startedFromSetupPosition = TRUE;
6141 CopyBoard(boards[0], initialPosition);
6143 if(oldx != gameInfo.boardWidth ||
6144 oldy != gameInfo.boardHeight ||
6145 oldv != gameInfo.variant ||
6146 oldh != gameInfo.holdingsWidth
6148 InitDrawingSizes(-2 ,0);
6150 oldv = gameInfo.variant;
6152 DrawPosition(TRUE, boards[currentMove]);
6156 SendBoard (ChessProgramState *cps, int moveNum)
6158 char message[MSG_SIZ];
6160 if (cps->useSetboard) {
6161 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6162 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6163 SendToProgram(message, cps);
6168 int i, j, left=0, right=BOARD_WIDTH;
6169 /* Kludge to set black to move, avoiding the troublesome and now
6170 * deprecated "black" command.
6172 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6173 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6175 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6177 SendToProgram("edit\n", cps);
6178 SendToProgram("#\n", cps);
6179 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6180 bp = &boards[moveNum][i][left];
6181 for (j = left; j < right; j++, bp++) {
6182 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6183 if ((int) *bp < (int) BlackPawn) {
6184 if(j == BOARD_RGHT+1)
6185 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6186 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6187 if(message[0] == '+' || message[0] == '~') {
6188 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6189 PieceToChar((ChessSquare)(DEMOTED *bp)),
6192 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6193 message[1] = BOARD_RGHT - 1 - j + '1';
6194 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6196 SendToProgram(message, cps);
6201 SendToProgram("c\n", cps);
6202 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6203 bp = &boards[moveNum][i][left];
6204 for (j = left; j < right; j++, bp++) {
6205 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6206 if (((int) *bp != (int) EmptySquare)
6207 && ((int) *bp >= (int) BlackPawn)) {
6208 if(j == BOARD_LEFT-2)
6209 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6210 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6212 if(message[0] == '+' || message[0] == '~') {
6213 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6214 PieceToChar((ChessSquare)(DEMOTED *bp)),
6217 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6218 message[1] = BOARD_RGHT - 1 - j + '1';
6219 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6221 SendToProgram(message, cps);
6226 SendToProgram(".\n", cps);
6228 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6231 char exclusionHeader[MSG_SIZ];
6232 int exCnt, excludePtr;
6233 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6234 static Exclusion excluTab[200];
6235 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6241 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6242 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6248 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6249 excludePtr = 24; exCnt = 0;
6254 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6255 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6256 char buf[2*MOVE_LEN], *p;
6257 Exclusion *e = excluTab;
6259 for(i=0; i<exCnt; i++)
6260 if(e[i].ff == fromX && e[i].fr == fromY &&
6261 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6262 if(i == exCnt) { // was not in exclude list; add it
6263 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6264 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6265 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6268 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6269 excludePtr++; e[i].mark = excludePtr++;
6270 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6273 exclusionHeader[e[i].mark] = state;
6277 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6278 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6282 if((signed char)promoChar == -1) { // kludge to indicate best move
6283 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6284 return 1; // if unparsable, abort
6286 // update exclusion map (resolving toggle by consulting existing state)
6287 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6289 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6290 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6291 excludeMap[k] |= 1<<j;
6292 else excludeMap[k] &= ~(1<<j);
6294 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6296 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6297 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6299 return (state == '+');
6303 ExcludeClick (int index)
6306 Exclusion *e = excluTab;
6307 if(index < 25) { // none, best or tail clicked
6308 if(index < 13) { // none: include all
6309 WriteMap(0); // clear map
6310 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6311 SendToBoth("include all\n"); // and inform engine
6312 } else if(index > 18) { // tail
6313 if(exclusionHeader[19] == '-') { // tail was excluded
6314 SendToBoth("include all\n");
6315 WriteMap(0); // clear map completely
6316 // now re-exclude selected moves
6317 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6318 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6319 } else { // tail was included or in mixed state
6320 SendToBoth("exclude all\n");
6321 WriteMap(0xFF); // fill map completely
6322 // now re-include selected moves
6323 j = 0; // count them
6324 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6325 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6326 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6329 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6332 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6333 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6334 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6341 DefaultPromoChoice (int white)
6344 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6345 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6346 result = WhiteFerz; // no choice
6347 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6348 result= WhiteKing; // in Suicide Q is the last thing we want
6349 else if(gameInfo.variant == VariantSpartan)
6350 result = white ? WhiteQueen : WhiteAngel;
6351 else result = WhiteQueen;
6352 if(!white) result = WHITE_TO_BLACK result;
6356 static int autoQueen; // [HGM] oneclick
6359 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6361 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6362 /* [HGM] add Shogi promotions */
6363 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6368 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6369 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6371 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6372 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6375 piece = boards[currentMove][fromY][fromX];
6376 if(gameInfo.variant == VariantShogi) {
6377 promotionZoneSize = BOARD_HEIGHT/3;
6378 highestPromotingPiece = (int)WhiteFerz;
6379 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6380 promotionZoneSize = 3;
6383 // Treat Lance as Pawn when it is not representing Amazon
6384 if(gameInfo.variant != VariantSuper) {
6385 if(piece == WhiteLance) piece = WhitePawn; else
6386 if(piece == BlackLance) piece = BlackPawn;
6389 // next weed out all moves that do not touch the promotion zone at all
6390 if((int)piece >= BlackPawn) {
6391 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6393 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6395 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6396 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6399 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6401 // weed out mandatory Shogi promotions
6402 if(gameInfo.variant == VariantShogi) {
6403 if(piece >= BlackPawn) {
6404 if(toY == 0 && piece == BlackPawn ||
6405 toY == 0 && piece == BlackQueen ||
6406 toY <= 1 && piece == BlackKnight) {
6411 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6412 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6413 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6420 // weed out obviously illegal Pawn moves
6421 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6422 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6423 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6424 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6425 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6426 // note we are not allowed to test for valid (non-)capture, due to premove
6429 // we either have a choice what to promote to, or (in Shogi) whether to promote
6430 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6431 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6432 *promoChoice = PieceToChar(BlackFerz); // no choice
6435 // no sense asking what we must promote to if it is going to explode...
6436 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6437 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6440 // give caller the default choice even if we will not make it
6441 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6442 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6443 if( sweepSelect && gameInfo.variant != VariantGreat
6444 && gameInfo.variant != VariantGrand
6445 && gameInfo.variant != VariantSuper) return FALSE;
6446 if(autoQueen) return FALSE; // predetermined
6448 // suppress promotion popup on illegal moves that are not premoves
6449 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6450 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6451 if(appData.testLegality && !premove) {
6452 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6453 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6454 if(moveType != WhitePromotion && moveType != BlackPromotion)
6462 InPalace (int row, int column)
6463 { /* [HGM] for Xiangqi */
6464 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6465 column < (BOARD_WIDTH + 4)/2 &&
6466 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6471 PieceForSquare (int x, int y)
6473 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6476 return boards[currentMove][y][x];
6480 OKToStartUserMove (int x, int y)
6482 ChessSquare from_piece;
6485 if (matchMode) return FALSE;
6486 if (gameMode == EditPosition) return TRUE;
6488 if (x >= 0 && y >= 0)
6489 from_piece = boards[currentMove][y][x];
6491 from_piece = EmptySquare;
6493 if (from_piece == EmptySquare) return FALSE;
6495 white_piece = (int)from_piece >= (int)WhitePawn &&
6496 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6500 case TwoMachinesPlay:
6508 case MachinePlaysWhite:
6509 case IcsPlayingBlack:
6510 if (appData.zippyPlay) return FALSE;
6512 DisplayMoveError(_("You are playing Black"));
6517 case MachinePlaysBlack:
6518 case IcsPlayingWhite:
6519 if (appData.zippyPlay) return FALSE;
6521 DisplayMoveError(_("You are playing White"));
6526 case PlayFromGameFile:
6527 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6529 if (!white_piece && WhiteOnMove(currentMove)) {
6530 DisplayMoveError(_("It is White's turn"));
6533 if (white_piece && !WhiteOnMove(currentMove)) {
6534 DisplayMoveError(_("It is Black's turn"));
6537 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6538 /* Editing correspondence game history */
6539 /* Could disallow this or prompt for confirmation */
6544 case BeginningOfGame:
6545 if (appData.icsActive) return FALSE;
6546 if (!appData.noChessProgram) {
6548 DisplayMoveError(_("You are playing White"));
6555 if (!white_piece && WhiteOnMove(currentMove)) {
6556 DisplayMoveError(_("It is White's turn"));
6559 if (white_piece && !WhiteOnMove(currentMove)) {
6560 DisplayMoveError(_("It is Black's turn"));
6569 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6570 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6571 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6572 && gameMode != AnalyzeFile && gameMode != Training) {
6573 DisplayMoveError(_("Displayed position is not current"));
6580 OnlyMove (int *x, int *y, Boolean captures)
6582 DisambiguateClosure cl;
6583 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6585 case MachinePlaysBlack:
6586 case IcsPlayingWhite:
6587 case BeginningOfGame:
6588 if(!WhiteOnMove(currentMove)) return FALSE;
6590 case MachinePlaysWhite:
6591 case IcsPlayingBlack:
6592 if(WhiteOnMove(currentMove)) return FALSE;
6599 cl.pieceIn = EmptySquare;
6604 cl.promoCharIn = NULLCHAR;
6605 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6606 if( cl.kind == NormalMove ||
6607 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6608 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6609 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6616 if(cl.kind != ImpossibleMove) return FALSE;
6617 cl.pieceIn = EmptySquare;
6622 cl.promoCharIn = NULLCHAR;
6623 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6624 if( cl.kind == NormalMove ||
6625 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6626 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6627 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6632 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6638 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6639 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6640 int lastLoadGameUseList = FALSE;
6641 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6642 ChessMove lastLoadGameStart = EndOfFile;
6646 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6650 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6652 /* Check if the user is playing in turn. This is complicated because we
6653 let the user "pick up" a piece before it is his turn. So the piece he
6654 tried to pick up may have been captured by the time he puts it down!
6655 Therefore we use the color the user is supposed to be playing in this
6656 test, not the color of the piece that is currently on the starting
6657 square---except in EditGame mode, where the user is playing both
6658 sides; fortunately there the capture race can't happen. (It can
6659 now happen in IcsExamining mode, but that's just too bad. The user
6660 will get a somewhat confusing message in that case.)
6665 case TwoMachinesPlay:
6669 /* We switched into a game mode where moves are not accepted,
6670 perhaps while the mouse button was down. */
6673 case MachinePlaysWhite:
6674 /* User is moving for Black */
6675 if (WhiteOnMove(currentMove)) {
6676 DisplayMoveError(_("It is White's turn"));
6681 case MachinePlaysBlack:
6682 /* User is moving for White */
6683 if (!WhiteOnMove(currentMove)) {
6684 DisplayMoveError(_("It is Black's turn"));
6689 case PlayFromGameFile:
6690 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6693 case BeginningOfGame:
6696 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6697 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6698 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6699 /* User is moving for Black */
6700 if (WhiteOnMove(currentMove)) {
6701 DisplayMoveError(_("It is White's turn"));
6705 /* User is moving for White */
6706 if (!WhiteOnMove(currentMove)) {
6707 DisplayMoveError(_("It is Black's turn"));
6713 case IcsPlayingBlack:
6714 /* User is moving for Black */
6715 if (WhiteOnMove(currentMove)) {
6716 if (!appData.premove) {
6717 DisplayMoveError(_("It is White's turn"));
6718 } else if (toX >= 0 && toY >= 0) {
6721 premoveFromX = fromX;
6722 premoveFromY = fromY;
6723 premovePromoChar = promoChar;
6725 if (appData.debugMode)
6726 fprintf(debugFP, "Got premove: fromX %d,"
6727 "fromY %d, toX %d, toY %d\n",
6728 fromX, fromY, toX, toY);
6734 case IcsPlayingWhite:
6735 /* User is moving for White */
6736 if (!WhiteOnMove(currentMove)) {
6737 if (!appData.premove) {
6738 DisplayMoveError(_("It is Black's turn"));
6739 } else if (toX >= 0 && toY >= 0) {
6742 premoveFromX = fromX;
6743 premoveFromY = fromY;
6744 premovePromoChar = promoChar;
6746 if (appData.debugMode)
6747 fprintf(debugFP, "Got premove: fromX %d,"
6748 "fromY %d, toX %d, toY %d\n",
6749 fromX, fromY, toX, toY);
6759 /* EditPosition, empty square, or different color piece;
6760 click-click move is possible */
6761 if (toX == -2 || toY == -2) {
6762 boards[0][fromY][fromX] = EmptySquare;
6763 DrawPosition(FALSE, boards[currentMove]);
6765 } else if (toX >= 0 && toY >= 0) {
6766 boards[0][toY][toX] = boards[0][fromY][fromX];
6767 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6768 if(boards[0][fromY][0] != EmptySquare) {
6769 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6770 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6773 if(fromX == BOARD_RGHT+1) {
6774 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6775 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6776 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6779 boards[0][fromY][fromX] = gatingPiece;
6780 DrawPosition(FALSE, boards[currentMove]);
6786 if(toX < 0 || toY < 0) return;
6787 pup = boards[currentMove][toY][toX];
6789 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6790 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6791 if( pup != EmptySquare ) return;
6792 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6793 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6794 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6795 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6796 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6797 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6798 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6802 /* [HGM] always test for legality, to get promotion info */
6803 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6804 fromY, fromX, toY, toX, promoChar);
6806 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6808 /* [HGM] but possibly ignore an IllegalMove result */
6809 if (appData.testLegality) {
6810 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6811 DisplayMoveError(_("Illegal move"));
6816 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6817 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6818 ClearPremoveHighlights(); // was included
6819 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6823 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6826 /* Common tail of UserMoveEvent and DropMenuEvent */
6828 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6832 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6833 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6834 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6835 if(WhiteOnMove(currentMove)) {
6836 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6838 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6842 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6843 move type in caller when we know the move is a legal promotion */
6844 if(moveType == NormalMove && promoChar)
6845 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6847 /* [HGM] <popupFix> The following if has been moved here from
6848 UserMoveEvent(). Because it seemed to belong here (why not allow
6849 piece drops in training games?), and because it can only be
6850 performed after it is known to what we promote. */
6851 if (gameMode == Training) {
6852 /* compare the move played on the board to the next move in the
6853 * game. If they match, display the move and the opponent's response.
6854 * If they don't match, display an error message.
6858 CopyBoard(testBoard, boards[currentMove]);
6859 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6861 if (CompareBoards(testBoard, boards[currentMove+1])) {
6862 ForwardInner(currentMove+1);
6864 /* Autoplay the opponent's response.
6865 * if appData.animate was TRUE when Training mode was entered,
6866 * the response will be animated.
6868 saveAnimate = appData.animate;
6869 appData.animate = animateTraining;
6870 ForwardInner(currentMove+1);
6871 appData.animate = saveAnimate;
6873 /* check for the end of the game */
6874 if (currentMove >= forwardMostMove) {
6875 gameMode = PlayFromGameFile;
6877 SetTrainingModeOff();
6878 DisplayInformation(_("End of game"));
6881 DisplayError(_("Incorrect move"), 0);
6886 /* Ok, now we know that the move is good, so we can kill
6887 the previous line in Analysis Mode */
6888 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6889 && currentMove < forwardMostMove) {
6890 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6891 else forwardMostMove = currentMove;
6896 /* If we need the chess program but it's dead, restart it */
6897 ResurrectChessProgram();
6899 /* A user move restarts a paused game*/
6903 thinkOutput[0] = NULLCHAR;
6905 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6907 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6908 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6912 if (gameMode == BeginningOfGame) {
6913 if (appData.noChessProgram) {
6914 gameMode = EditGame;
6918 gameMode = MachinePlaysBlack;
6921 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6923 if (first.sendName) {
6924 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6925 SendToProgram(buf, &first);
6932 /* Relay move to ICS or chess engine */
6933 if (appData.icsActive) {
6934 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6935 gameMode == IcsExamining) {
6936 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6937 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6939 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6941 // also send plain move, in case ICS does not understand atomic claims
6942 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6946 if (first.sendTime && (gameMode == BeginningOfGame ||
6947 gameMode == MachinePlaysWhite ||
6948 gameMode == MachinePlaysBlack)) {
6949 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6951 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6952 // [HGM] book: if program might be playing, let it use book
6953 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6954 first.maybeThinking = TRUE;
6955 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6956 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6957 SendBoard(&first, currentMove+1);
6958 if(second.analyzing) {
6959 if(!second.useSetboard) SendToProgram("undo\n", &second);
6960 SendBoard(&second, currentMove+1);
6963 SendMoveToProgram(forwardMostMove-1, &first);
6964 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6966 if (currentMove == cmailOldMove + 1) {
6967 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6971 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6975 if(appData.testLegality)
6976 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6982 if (WhiteOnMove(currentMove)) {
6983 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6985 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6989 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6994 case MachinePlaysBlack:
6995 case MachinePlaysWhite:
6996 /* disable certain menu options while machine is thinking */
6997 SetMachineThinkingEnables();
7004 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7005 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7007 if(bookHit) { // [HGM] book: simulate book reply
7008 static char bookMove[MSG_SIZ]; // a bit generous?
7010 programStats.nodes = programStats.depth = programStats.time =
7011 programStats.score = programStats.got_only_move = 0;
7012 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7014 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7015 strcat(bookMove, bookHit);
7016 HandleMachineMove(bookMove, &first);
7022 MarkByFEN(char *fen)
7025 if(!appData.markers || !appData.highlightDragging) return;
7026 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7027 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7031 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7032 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7033 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7034 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7035 if(*fen == 'T') marker[r][f++] = 0; else
7036 if(*fen == 'Y') marker[r][f++] = 1; else
7037 if(*fen == 'G') marker[r][f++] = 3; else
7038 if(*fen == 'B') marker[r][f++] = 4; else
7039 if(*fen == 'C') marker[r][f++] = 5; else
7040 if(*fen == 'M') marker[r][f++] = 6; else
7041 if(*fen == 'W') marker[r][f++] = 7; else
7042 if(*fen == 'D') marker[r][f++] = 8; else
7043 if(*fen == 'R') marker[r][f++] = 2; else {
7044 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7047 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7051 DrawPosition(TRUE, NULL);
7055 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7057 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7058 Markers *m = (Markers *) closure;
7059 if(rf == fromY && ff == fromX)
7060 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7061 || kind == WhiteCapturesEnPassant
7062 || kind == BlackCapturesEnPassant);
7063 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7067 MarkTargetSquares (int clear)
7070 if(clear) { // no reason to ever suppress clearing
7071 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7072 if(!sum) return; // nothing was cleared,no redraw needed
7075 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7076 !appData.testLegality || gameMode == EditPosition) return;
7077 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7078 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7079 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7081 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7084 DrawPosition(FALSE, NULL);
7088 Explode (Board board, int fromX, int fromY, int toX, int toY)
7090 if(gameInfo.variant == VariantAtomic &&
7091 (board[toY][toX] != EmptySquare || // capture?
7092 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7093 board[fromY][fromX] == BlackPawn )
7095 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7101 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7104 CanPromote (ChessSquare piece, int y)
7106 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7107 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7108 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
7109 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7110 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7111 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7112 return (piece == BlackPawn && y == 1 ||
7113 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7114 piece == BlackLance && y == 1 ||
7115 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7119 HoverEvent (int hiX, int hiY, int x, int y)
7121 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7123 if(!first.highlight) return;
7124 if(hiX == -1 && hiY == -1 && x == fromX && y == fromY) // record markings
7125 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7126 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7127 else if(hiX != x || hiY != y) {
7128 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7129 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7130 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7131 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7133 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7134 SendToProgram(buf, &first);
7136 SetHighlights(fromX, fromY, x, y);
7140 void ReportClick(char *action, int x, int y)
7142 char buf[MSG_SIZ]; // Inform engine of what user does
7144 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7145 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7146 if(!first.highlight || gameMode == EditPosition) return;
7147 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7148 SendToProgram(buf, &first);
7152 LeftClick (ClickType clickType, int xPix, int yPix)
7155 Boolean saveAnimate;
7156 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7157 char promoChoice = NULLCHAR;
7159 static TimeMark lastClickTime, prevClickTime;
7161 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7163 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7165 if (clickType == Press) ErrorPopDown();
7166 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7168 x = EventToSquare(xPix, BOARD_WIDTH);
7169 y = EventToSquare(yPix, BOARD_HEIGHT);
7170 if (!flipView && y >= 0) {
7171 y = BOARD_HEIGHT - 1 - y;
7173 if (flipView && x >= 0) {
7174 x = BOARD_WIDTH - 1 - x;
7177 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7178 defaultPromoChoice = promoSweep;
7179 promoSweep = EmptySquare; // terminate sweep
7180 promoDefaultAltered = TRUE;
7181 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7184 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7185 if(clickType == Release) return; // ignore upclick of click-click destination
7186 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7187 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7188 if(gameInfo.holdingsWidth &&
7189 (WhiteOnMove(currentMove)
7190 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7191 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7192 // click in right holdings, for determining promotion piece
7193 ChessSquare p = boards[currentMove][y][x];
7194 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7195 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7196 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7197 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7202 DrawPosition(FALSE, boards[currentMove]);
7206 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7207 if(clickType == Press
7208 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7209 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7210 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7213 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7214 // could be static click on premove from-square: abort premove
7216 ClearPremoveHighlights();
7219 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7220 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7222 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7223 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7224 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7225 defaultPromoChoice = DefaultPromoChoice(side);
7228 autoQueen = appData.alwaysPromoteToQueen;
7232 gatingPiece = EmptySquare;
7233 if (clickType != Press) {
7234 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7235 DragPieceEnd(xPix, yPix); dragging = 0;
7236 DrawPosition(FALSE, NULL);
7240 doubleClick = FALSE;
7241 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7242 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7244 fromX = x; fromY = y; toX = toY = -1;
7245 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7246 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7247 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7249 if (OKToStartUserMove(fromX, fromY)) {
7251 ReportClick("lift", x, y);
7252 MarkTargetSquares(0);
7253 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7254 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7255 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7256 promoSweep = defaultPromoChoice;
7257 selectFlag = 0; lastX = xPix; lastY = yPix;
7258 Sweep(0); // Pawn that is going to promote: preview promotion piece
7259 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7261 if (appData.highlightDragging) {
7262 SetHighlights(fromX, fromY, -1, -1);
7266 } else fromX = fromY = -1;
7272 if (clickType == Press && gameMode != EditPosition) {
7277 // ignore off-board to clicks
7278 if(y < 0 || x < 0) return;
7280 /* Check if clicking again on the same color piece */
7281 fromP = boards[currentMove][fromY][fromX];
7282 toP = boards[currentMove][y][x];
7283 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7284 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7285 WhitePawn <= toP && toP <= WhiteKing &&
7286 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7287 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7288 (BlackPawn <= fromP && fromP <= BlackKing &&
7289 BlackPawn <= toP && toP <= BlackKing &&
7290 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7291 !(fromP == BlackKing && toP == BlackRook && frc))) {
7292 /* Clicked again on same color piece -- changed his mind */
7293 second = (x == fromX && y == fromY);
7294 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7295 second = FALSE; // first double-click rather than scond click
7296 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7298 promoDefaultAltered = FALSE;
7299 MarkTargetSquares(1);
7300 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7301 if (appData.highlightDragging) {
7302 SetHighlights(x, y, -1, -1);
7306 if (OKToStartUserMove(x, y)) {
7307 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7308 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7309 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7310 gatingPiece = boards[currentMove][fromY][fromX];
7311 else gatingPiece = doubleClick ? fromP : EmptySquare;
7313 fromY = y; dragging = 1;
7314 ReportClick("lift", x, y);
7315 MarkTargetSquares(0);
7316 DragPieceBegin(xPix, yPix, FALSE);
7317 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7318 promoSweep = defaultPromoChoice;
7319 selectFlag = 0; lastX = xPix; lastY = yPix;
7320 Sweep(0); // Pawn that is going to promote: preview promotion piece
7324 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7327 // ignore clicks on holdings
7328 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7331 if (clickType == Release && x == fromX && y == fromY) {
7332 DragPieceEnd(xPix, yPix); dragging = 0;
7334 // a deferred attempt to click-click move an empty square on top of a piece
7335 boards[currentMove][y][x] = EmptySquare;
7337 DrawPosition(FALSE, boards[currentMove]);
7338 fromX = fromY = -1; clearFlag = 0;
7341 if (appData.animateDragging) {
7342 /* Undo animation damage if any */
7343 DrawPosition(FALSE, NULL);
7345 if (second || sweepSelecting) {
7346 /* Second up/down in same square; just abort move */
7347 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7348 second = sweepSelecting = 0;
7350 gatingPiece = EmptySquare;
7351 MarkTargetSquares(1);
7354 ClearPremoveHighlights();
7356 /* First upclick in same square; start click-click mode */
7357 SetHighlights(x, y, -1, -1);
7364 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x]) {
7365 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7366 DisplayMessage(_("only marked squares are legal"),"");
7367 DrawPosition(TRUE, NULL);
7368 return; // ignore to-click
7371 /* we now have a different from- and (possibly off-board) to-square */
7372 /* Completed move */
7373 if(!sweepSelecting) {
7376 } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7378 saveAnimate = appData.animate;
7379 if (clickType == Press) {
7380 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7381 // must be Edit Position mode with empty-square selected
7382 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7383 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7386 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7387 if(appData.sweepSelect) {
7388 ChessSquare piece = boards[currentMove][fromY][fromX];
7389 promoSweep = defaultPromoChoice;
7390 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7391 selectFlag = 0; lastX = xPix; lastY = yPix;
7392 Sweep(0); // Pawn that is going to promote: preview promotion piece
7394 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7395 MarkTargetSquares(1);
7397 return; // promo popup appears on up-click
7399 /* Finish clickclick move */
7400 if (appData.animate || appData.highlightLastMove) {
7401 SetHighlights(fromX, fromY, toX, toY);
7407 // [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
7408 /* Finish drag move */
7409 if (appData.highlightLastMove) {
7410 SetHighlights(fromX, fromY, toX, toY);
7415 DragPieceEnd(xPix, yPix); dragging = 0;
7416 /* Don't animate move and drag both */
7417 appData.animate = FALSE;
7420 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7421 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7422 ChessSquare piece = boards[currentMove][fromY][fromX];
7423 if(gameMode == EditPosition && piece != EmptySquare &&
7424 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7427 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7428 n = PieceToNumber(piece - (int)BlackPawn);
7429 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7430 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7431 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7433 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7434 n = PieceToNumber(piece);
7435 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7436 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7437 boards[currentMove][n][BOARD_WIDTH-2]++;
7439 boards[currentMove][fromY][fromX] = EmptySquare;
7443 MarkTargetSquares(1);
7444 DrawPosition(TRUE, boards[currentMove]);
7448 // off-board moves should not be highlighted
7449 if(x < 0 || y < 0) ClearHighlights();
7450 else ReportClick("put", x, y);
7452 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7454 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7455 SetHighlights(fromX, fromY, toX, toY);
7456 MarkTargetSquares(1);
7457 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7458 // [HGM] super: promotion to captured piece selected from holdings
7459 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7460 promotionChoice = TRUE;
7461 // kludge follows to temporarily execute move on display, without promoting yet
7462 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7463 boards[currentMove][toY][toX] = p;
7464 DrawPosition(FALSE, boards[currentMove]);
7465 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7466 boards[currentMove][toY][toX] = q;
7467 DisplayMessage("Click in holdings to choose piece", "");
7472 int oldMove = currentMove;
7473 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7474 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7475 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7476 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7477 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7478 DrawPosition(TRUE, boards[currentMove]);
7479 MarkTargetSquares(1);
7482 appData.animate = saveAnimate;
7483 if (appData.animate || appData.animateDragging) {
7484 /* Undo animation damage if needed */
7485 DrawPosition(FALSE, NULL);
7490 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7491 { // front-end-free part taken out of PieceMenuPopup
7492 int whichMenu; int xSqr, ySqr;
7494 if(seekGraphUp) { // [HGM] seekgraph
7495 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7496 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7500 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7501 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7502 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7503 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7504 if(action == Press) {
7505 originalFlip = flipView;
7506 flipView = !flipView; // temporarily flip board to see game from partners perspective
7507 DrawPosition(TRUE, partnerBoard);
7508 DisplayMessage(partnerStatus, "");
7510 } else if(action == Release) {
7511 flipView = originalFlip;
7512 DrawPosition(TRUE, boards[currentMove]);
7518 xSqr = EventToSquare(x, BOARD_WIDTH);
7519 ySqr = EventToSquare(y, BOARD_HEIGHT);
7520 if (action == Release) {
7521 if(pieceSweep != EmptySquare) {
7522 EditPositionMenuEvent(pieceSweep, toX, toY);
7523 pieceSweep = EmptySquare;
7524 } else UnLoadPV(); // [HGM] pv
7526 if (action != Press) return -2; // return code to be ignored
7529 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7531 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7532 if (xSqr < 0 || ySqr < 0) return -1;
7533 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7534 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7535 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7536 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7540 if(!appData.icsEngineAnalyze) return -1;
7541 case IcsPlayingWhite:
7542 case IcsPlayingBlack:
7543 if(!appData.zippyPlay) goto noZip;
7546 case MachinePlaysWhite:
7547 case MachinePlaysBlack:
7548 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7549 if (!appData.dropMenu) {
7551 return 2; // flag front-end to grab mouse events
7553 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7554 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7557 if (xSqr < 0 || ySqr < 0) return -1;
7558 if (!appData.dropMenu || appData.testLegality &&
7559 gameInfo.variant != VariantBughouse &&
7560 gameInfo.variant != VariantCrazyhouse) return -1;
7561 whichMenu = 1; // drop menu
7567 if (((*fromX = xSqr) < 0) ||
7568 ((*fromY = ySqr) < 0)) {
7569 *fromX = *fromY = -1;
7573 *fromX = BOARD_WIDTH - 1 - *fromX;
7575 *fromY = BOARD_HEIGHT - 1 - *fromY;
7581 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7583 // char * hint = lastHint;
7584 FrontEndProgramStats stats;
7586 stats.which = cps == &first ? 0 : 1;
7587 stats.depth = cpstats->depth;
7588 stats.nodes = cpstats->nodes;
7589 stats.score = cpstats->score;
7590 stats.time = cpstats->time;
7591 stats.pv = cpstats->movelist;
7592 stats.hint = lastHint;
7593 stats.an_move_index = 0;
7594 stats.an_move_count = 0;
7596 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7597 stats.hint = cpstats->move_name;
7598 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7599 stats.an_move_count = cpstats->nr_moves;
7602 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
7604 SetProgramStats( &stats );
7608 ClearEngineOutputPane (int which)
7610 static FrontEndProgramStats dummyStats;
7611 dummyStats.which = which;
7612 dummyStats.pv = "#";
7613 SetProgramStats( &dummyStats );
7616 #define MAXPLAYERS 500
7619 TourneyStandings (int display)
7621 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7622 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7623 char result, *p, *names[MAXPLAYERS];
7625 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7626 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7627 names[0] = p = strdup(appData.participants);
7628 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7630 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7632 while(result = appData.results[nr]) {
7633 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7634 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7635 wScore = bScore = 0;
7637 case '+': wScore = 2; break;
7638 case '-': bScore = 2; break;
7639 case '=': wScore = bScore = 1; break;
7641 case '*': return strdup("busy"); // tourney not finished
7649 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7650 for(w=0; w<nPlayers; w++) {
7652 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7653 ranking[w] = b; points[w] = bScore; score[b] = -2;
7655 p = malloc(nPlayers*34+1);
7656 for(w=0; w<nPlayers && w<display; w++)
7657 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7663 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7664 { // count all piece types
7666 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7667 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7668 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7671 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7672 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7673 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7674 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7675 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7676 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7681 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7683 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7684 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7686 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7687 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7688 if(myPawns == 2 && nMine == 3) // KPP
7689 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7690 if(myPawns == 1 && nMine == 2) // KP
7691 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7692 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7693 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7694 if(myPawns) return FALSE;
7695 if(pCnt[WhiteRook+side])
7696 return pCnt[BlackRook-side] ||
7697 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7698 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7699 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7700 if(pCnt[WhiteCannon+side]) {
7701 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7702 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7704 if(pCnt[WhiteKnight+side])
7705 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7710 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7712 VariantClass v = gameInfo.variant;
7714 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7715 if(v == VariantShatranj) return TRUE; // always winnable through baring
7716 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7717 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7719 if(v == VariantXiangqi) {
7720 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7722 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7723 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7724 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7725 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7726 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7727 if(stale) // we have at least one last-rank P plus perhaps C
7728 return majors // KPKX
7729 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7731 return pCnt[WhiteFerz+side] // KCAK
7732 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7733 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7734 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7736 } else if(v == VariantKnightmate) {
7737 if(nMine == 1) return FALSE;
7738 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7739 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7740 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7742 if(nMine == 1) return FALSE; // bare King
7743 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
7744 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7745 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7746 // by now we have King + 1 piece (or multiple Bishops on the same color)
7747 if(pCnt[WhiteKnight+side])
7748 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7749 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7750 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7752 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7753 if(pCnt[WhiteAlfil+side])
7754 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7755 if(pCnt[WhiteWazir+side])
7756 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7763 CompareWithRights (Board b1, Board b2)
7766 if(!CompareBoards(b1, b2)) return FALSE;
7767 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7768 /* compare castling rights */
7769 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7770 rights++; /* King lost rights, while rook still had them */
7771 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7772 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7773 rights++; /* but at least one rook lost them */
7775 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7777 if( b1[CASTLING][5] != NoRights ) {
7778 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7785 Adjudicate (ChessProgramState *cps)
7786 { // [HGM] some adjudications useful with buggy engines
7787 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7788 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7789 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7790 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7791 int k, drop, count = 0; static int bare = 1;
7792 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7793 Boolean canAdjudicate = !appData.icsActive;
7795 // most tests only when we understand the game, i.e. legality-checking on
7796 if( appData.testLegality )
7797 { /* [HGM] Some more adjudications for obstinate engines */
7798 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7799 static int moveCount = 6;
7801 char *reason = NULL;
7803 /* Count what is on board. */
7804 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7806 /* Some material-based adjudications that have to be made before stalemate test */
7807 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7808 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7809 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7810 if(canAdjudicate && appData.checkMates) {
7812 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7813 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7814 "Xboard adjudication: King destroyed", GE_XBOARD );
7819 /* Bare King in Shatranj (loses) or Losers (wins) */
7820 if( nrW == 1 || nrB == 1) {
7821 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7822 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7823 if(canAdjudicate && appData.checkMates) {
7825 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7826 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7827 "Xboard adjudication: Bare king", GE_XBOARD );
7831 if( gameInfo.variant == VariantShatranj && --bare < 0)
7833 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7834 if(canAdjudicate && appData.checkMates) {
7835 /* but only adjudicate if adjudication enabled */
7837 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7838 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7839 "Xboard adjudication: Bare king", GE_XBOARD );
7846 // don't wait for engine to announce game end if we can judge ourselves
7847 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7849 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7850 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7851 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7852 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7855 reason = "Xboard adjudication: 3rd check";
7856 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7866 reason = "Xboard adjudication: Stalemate";
7867 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7868 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7869 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7870 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7871 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7872 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7873 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7874 EP_CHECKMATE : EP_WINS);
7875 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7876 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7880 reason = "Xboard adjudication: Checkmate";
7881 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7882 if(gameInfo.variant == VariantShogi) {
7883 if(forwardMostMove > backwardMostMove
7884 && moveList[forwardMostMove-1][1] == '@'
7885 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7886 reason = "XBoard adjudication: pawn-drop mate";
7887 boards[forwardMostMove][EP_STATUS] = EP_WINS;
7893 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7895 result = GameIsDrawn; break;
7897 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7899 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7903 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7905 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7906 GameEnds( result, reason, GE_XBOARD );
7910 /* Next absolutely insufficient mating material. */
7911 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7912 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7913 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7915 /* always flag draws, for judging claims */
7916 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7918 if(canAdjudicate && appData.materialDraws) {
7919 /* but only adjudicate them if adjudication enabled */
7920 if(engineOpponent) {
7921 SendToProgram("force\n", engineOpponent); // suppress reply
7922 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7924 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7929 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7930 if(gameInfo.variant == VariantXiangqi ?
7931 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7933 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7934 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7935 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7936 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7938 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7939 { /* if the first 3 moves do not show a tactical win, declare draw */
7940 if(engineOpponent) {
7941 SendToProgram("force\n", engineOpponent); // suppress reply
7942 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7944 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7947 } else moveCount = 6;
7950 // Repetition draws and 50-move rule can be applied independently of legality testing
7952 /* Check for rep-draws */
7954 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7955 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7956 for(k = forwardMostMove-2;
7957 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7958 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7959 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7962 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7963 /* compare castling rights */
7964 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7965 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7966 rights++; /* King lost rights, while rook still had them */
7967 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7968 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7969 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7970 rights++; /* but at least one rook lost them */
7972 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7973 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7975 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7976 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7977 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7980 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7981 && appData.drawRepeats > 1) {
7982 /* adjudicate after user-specified nr of repeats */
7983 int result = GameIsDrawn;
7984 char *details = "XBoard adjudication: repetition draw";
7985 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7986 // [HGM] xiangqi: check for forbidden perpetuals
7987 int m, ourPerpetual = 1, hisPerpetual = 1;
7988 for(m=forwardMostMove; m>k; m-=2) {
7989 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7990 ourPerpetual = 0; // the current mover did not always check
7991 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7992 hisPerpetual = 0; // the opponent did not always check
7994 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7995 ourPerpetual, hisPerpetual);
7996 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7997 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7998 details = "Xboard adjudication: perpetual checking";
8000 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8001 break; // (or we would have caught him before). Abort repetition-checking loop.
8003 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8004 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8006 details = "Xboard adjudication: repetition";
8008 } else // it must be XQ
8009 // Now check for perpetual chases
8010 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8011 hisPerpetual = PerpetualChase(k, forwardMostMove);
8012 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8013 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8014 static char resdet[MSG_SIZ];
8015 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8017 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8019 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8020 break; // Abort repetition-checking loop.
8022 // if neither of us is checking or chasing all the time, or both are, it is draw
8024 if(engineOpponent) {
8025 SendToProgram("force\n", engineOpponent); // suppress reply
8026 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8028 GameEnds( result, details, GE_XBOARD );
8031 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8032 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8036 /* Now we test for 50-move draws. Determine ply count */
8037 count = forwardMostMove;
8038 /* look for last irreversble move */
8039 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8041 /* if we hit starting position, add initial plies */
8042 if( count == backwardMostMove )
8043 count -= initialRulePlies;
8044 count = forwardMostMove - count;
8045 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8046 // adjust reversible move counter for checks in Xiangqi
8047 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8048 if(i < backwardMostMove) i = backwardMostMove;
8049 while(i <= forwardMostMove) {
8050 lastCheck = inCheck; // check evasion does not count
8051 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8052 if(inCheck || lastCheck) count--; // check does not count
8057 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8058 /* this is used to judge if draw claims are legal */
8059 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8060 if(engineOpponent) {
8061 SendToProgram("force\n", engineOpponent); // suppress reply
8062 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8064 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8068 /* if draw offer is pending, treat it as a draw claim
8069 * when draw condition present, to allow engines a way to
8070 * claim draws before making their move to avoid a race
8071 * condition occurring after their move
8073 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8075 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8076 p = "Draw claim: 50-move rule";
8077 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8078 p = "Draw claim: 3-fold repetition";
8079 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8080 p = "Draw claim: insufficient mating material";
8081 if( p != NULL && canAdjudicate) {
8082 if(engineOpponent) {
8083 SendToProgram("force\n", engineOpponent); // suppress reply
8084 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8086 GameEnds( GameIsDrawn, p, GE_XBOARD );
8091 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8092 if(engineOpponent) {
8093 SendToProgram("force\n", engineOpponent); // suppress reply
8094 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8096 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8103 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8104 { // [HGM] book: this routine intercepts moves to simulate book replies
8105 char *bookHit = NULL;
8107 //first determine if the incoming move brings opponent into his book
8108 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8109 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8110 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8111 if(bookHit != NULL && !cps->bookSuspend) {
8112 // make sure opponent is not going to reply after receiving move to book position
8113 SendToProgram("force\n", cps);
8114 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8116 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8117 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8118 // now arrange restart after book miss
8120 // after a book hit we never send 'go', and the code after the call to this routine
8121 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8122 char buf[MSG_SIZ], *move = bookHit;
8124 int fromX, fromY, toX, toY;
8128 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8129 &fromX, &fromY, &toX, &toY, &promoChar)) {
8130 (void) CoordsToAlgebraic(boards[forwardMostMove],
8131 PosFlags(forwardMostMove),
8132 fromY, fromX, toY, toX, promoChar, move);
8134 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8138 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8139 SendToProgram(buf, cps);
8140 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8141 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8142 SendToProgram("go\n", cps);
8143 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8144 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8145 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8146 SendToProgram("go\n", cps);
8147 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8149 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8153 LoadError (char *errmess, ChessProgramState *cps)
8154 { // unloads engine and switches back to -ncp mode if it was first
8155 if(cps->initDone) return FALSE;
8156 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8157 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8160 appData.noChessProgram = TRUE;
8161 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8162 gameMode = BeginningOfGame; ModeHighlight();
8165 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8166 DisplayMessage("", ""); // erase waiting message
8167 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8172 ChessProgramState *savedState;
8174 DeferredBookMove (void)
8176 if(savedState->lastPing != savedState->lastPong)
8177 ScheduleDelayedEvent(DeferredBookMove, 10);
8179 HandleMachineMove(savedMessage, savedState);
8182 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8183 static ChessProgramState *stalledEngine;
8184 static char stashedInputMove[MSG_SIZ];
8187 HandleMachineMove (char *message, ChessProgramState *cps)
8189 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8190 char realname[MSG_SIZ];
8191 int fromX, fromY, toX, toY;
8195 int machineWhite, oldError;
8198 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8199 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8200 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8201 DisplayError(_("Invalid pairing from pairing engine"), 0);
8204 pairingReceived = 1;
8206 return; // Skim the pairing messages here.
8209 oldError = cps->userError; cps->userError = 0;
8211 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8213 * Kludge to ignore BEL characters
8215 while (*message == '\007') message++;
8218 * [HGM] engine debug message: ignore lines starting with '#' character
8220 if(cps->debug && *message == '#') return;
8223 * Look for book output
8225 if (cps == &first && bookRequested) {
8226 if (message[0] == '\t' || message[0] == ' ') {
8227 /* Part of the book output is here; append it */
8228 strcat(bookOutput, message);
8229 strcat(bookOutput, " \n");
8231 } else if (bookOutput[0] != NULLCHAR) {
8232 /* All of book output has arrived; display it */
8233 char *p = bookOutput;
8234 while (*p != NULLCHAR) {
8235 if (*p == '\t') *p = ' ';
8238 DisplayInformation(bookOutput);
8239 bookRequested = FALSE;
8240 /* Fall through to parse the current output */
8245 * Look for machine move.
8247 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8248 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8250 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8251 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8252 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8253 stalledEngine = cps;
8254 if(appData.ponderNextMove) { // bring opponent out of ponder
8255 if(gameMode == TwoMachinesPlay) {
8256 if(cps->other->pause)
8257 PauseEngine(cps->other);
8259 SendToProgram("easy\n", cps->other);
8266 /* This method is only useful on engines that support ping */
8267 if (cps->lastPing != cps->lastPong) {
8268 if (gameMode == BeginningOfGame) {
8269 /* Extra move from before last new; ignore */
8270 if (appData.debugMode) {
8271 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8274 if (appData.debugMode) {
8275 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8276 cps->which, gameMode);
8279 SendToProgram("undo\n", cps);
8285 case BeginningOfGame:
8286 /* Extra move from before last reset; ignore */
8287 if (appData.debugMode) {
8288 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8295 /* Extra move after we tried to stop. The mode test is
8296 not a reliable way of detecting this problem, but it's
8297 the best we can do on engines that don't support ping.
8299 if (appData.debugMode) {
8300 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8301 cps->which, gameMode);
8303 SendToProgram("undo\n", cps);
8306 case MachinePlaysWhite:
8307 case IcsPlayingWhite:
8308 machineWhite = TRUE;
8311 case MachinePlaysBlack:
8312 case IcsPlayingBlack:
8313 machineWhite = FALSE;
8316 case TwoMachinesPlay:
8317 machineWhite = (cps->twoMachinesColor[0] == 'w');
8320 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8321 if (appData.debugMode) {
8323 "Ignoring move out of turn by %s, gameMode %d"
8324 ", forwardMost %d\n",
8325 cps->which, gameMode, forwardMostMove);
8330 if(cps->alphaRank) AlphaRank(machineMove, 4);
8331 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8332 &fromX, &fromY, &toX, &toY, &promoChar)) {
8333 /* Machine move could not be parsed; ignore it. */
8334 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8335 machineMove, _(cps->which));
8336 DisplayMoveError(buf1);
8337 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8338 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8339 if (gameMode == TwoMachinesPlay) {
8340 GameEnds(machineWhite ? BlackWins : WhiteWins,
8346 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8347 /* So we have to redo legality test with true e.p. status here, */
8348 /* to make sure an illegal e.p. capture does not slip through, */
8349 /* to cause a forfeit on a justified illegal-move complaint */
8350 /* of the opponent. */
8351 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8353 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8354 fromY, fromX, toY, toX, promoChar);
8355 if(moveType == IllegalMove) {
8356 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8357 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8358 GameEnds(machineWhite ? BlackWins : WhiteWins,
8361 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8362 /* [HGM] Kludge to handle engines that send FRC-style castling
8363 when they shouldn't (like TSCP-Gothic) */
8365 case WhiteASideCastleFR:
8366 case BlackASideCastleFR:
8368 currentMoveString[2]++;
8370 case WhiteHSideCastleFR:
8371 case BlackHSideCastleFR:
8373 currentMoveString[2]--;
8375 default: ; // nothing to do, but suppresses warning of pedantic compilers
8378 hintRequested = FALSE;
8379 lastHint[0] = NULLCHAR;
8380 bookRequested = FALSE;
8381 /* Program may be pondering now */
8382 cps->maybeThinking = TRUE;
8383 if (cps->sendTime == 2) cps->sendTime = 1;
8384 if (cps->offeredDraw) cps->offeredDraw--;
8386 /* [AS] Save move info*/
8387 pvInfoList[ forwardMostMove ].score = programStats.score;
8388 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8389 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8391 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8393 /* Test suites abort the 'game' after one move */
8394 if(*appData.finger) {
8396 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8397 if(!f) f = fopen(appData.finger, "w");
8398 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8399 else { DisplayFatalError("Bad output file", errno, 0); return; }
8401 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8404 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8405 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8408 while( count < adjudicateLossPlies ) {
8409 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8412 score = -score; /* Flip score for winning side */
8415 if( score > adjudicateLossThreshold ) {
8422 if( count >= adjudicateLossPlies ) {
8423 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8425 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8426 "Xboard adjudication",
8433 if(Adjudicate(cps)) {
8434 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8435 return; // [HGM] adjudicate: for all automatic game ends
8439 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8441 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8442 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8444 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8446 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8448 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8449 char buf[3*MSG_SIZ];
8451 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8452 programStats.score / 100.,
8454 programStats.time / 100.,
8455 (unsigned int)programStats.nodes,
8456 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8457 programStats.movelist);
8459 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8464 /* [AS] Clear stats for next move */
8465 ClearProgramStats();
8466 thinkOutput[0] = NULLCHAR;
8467 hiddenThinkOutputState = 0;
8470 if (gameMode == TwoMachinesPlay) {
8471 /* [HGM] relaying draw offers moved to after reception of move */
8472 /* and interpreting offer as claim if it brings draw condition */
8473 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8474 SendToProgram("draw\n", cps->other);
8476 if (cps->other->sendTime) {
8477 SendTimeRemaining(cps->other,
8478 cps->other->twoMachinesColor[0] == 'w');
8480 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8481 if (firstMove && !bookHit) {
8483 if (cps->other->useColors) {
8484 SendToProgram(cps->other->twoMachinesColor, cps->other);
8486 SendToProgram("go\n", cps->other);
8488 cps->other->maybeThinking = TRUE;
8491 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8493 if (!pausing && appData.ringBellAfterMoves) {
8498 * Reenable menu items that were disabled while
8499 * machine was thinking
8501 if (gameMode != TwoMachinesPlay)
8502 SetUserThinkingEnables();
8504 // [HGM] book: after book hit opponent has received move and is now in force mode
8505 // force the book reply into it, and then fake that it outputted this move by jumping
8506 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8508 static char bookMove[MSG_SIZ]; // a bit generous?
8510 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8511 strcat(bookMove, bookHit);
8514 programStats.nodes = programStats.depth = programStats.time =
8515 programStats.score = programStats.got_only_move = 0;
8516 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8518 if(cps->lastPing != cps->lastPong) {
8519 savedMessage = message; // args for deferred call
8521 ScheduleDelayedEvent(DeferredBookMove, 10);
8530 /* Set special modes for chess engines. Later something general
8531 * could be added here; for now there is just one kludge feature,
8532 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8533 * when "xboard" is given as an interactive command.
8535 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8536 cps->useSigint = FALSE;
8537 cps->useSigterm = FALSE;
8539 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8540 ParseFeatures(message+8, cps);
8541 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8544 if (!strncmp(message, "setup ", 6) &&
8545 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8546 ) { // [HGM] allow first engine to define opening position
8547 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8548 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8550 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8551 if(startedFromSetupPosition) return;
8552 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8554 while(message[s] && message[s++] != ' ');
8555 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8556 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8557 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8558 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8559 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8560 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8563 ParseFEN(boards[0], &dummy, message+s);
8564 DrawPosition(TRUE, boards[0]);
8565 startedFromSetupPosition = TRUE;
8568 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8569 * want this, I was asked to put it in, and obliged.
8571 if (!strncmp(message, "setboard ", 9)) {
8572 Board initial_position;
8574 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8576 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8577 DisplayError(_("Bad FEN received from engine"), 0);
8581 CopyBoard(boards[0], initial_position);
8582 initialRulePlies = FENrulePlies;
8583 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8584 else gameMode = MachinePlaysBlack;
8585 DrawPosition(FALSE, boards[currentMove]);
8591 * Look for communication commands
8593 if (!strncmp(message, "telluser ", 9)) {
8594 if(message[9] == '\\' && message[10] == '\\')
8595 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8597 DisplayNote(message + 9);
8600 if (!strncmp(message, "tellusererror ", 14)) {
8602 if(message[14] == '\\' && message[15] == '\\')
8603 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8605 DisplayError(message + 14, 0);
8608 if (!strncmp(message, "tellopponent ", 13)) {
8609 if (appData.icsActive) {
8611 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8615 DisplayNote(message + 13);
8619 if (!strncmp(message, "tellothers ", 11)) {
8620 if (appData.icsActive) {
8622 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8625 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8628 if (!strncmp(message, "tellall ", 8)) {
8629 if (appData.icsActive) {
8631 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8635 DisplayNote(message + 8);
8639 if (strncmp(message, "warning", 7) == 0) {
8640 /* Undocumented feature, use tellusererror in new code */
8641 DisplayError(message, 0);
8644 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8645 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8646 strcat(realname, " query");
8647 AskQuestion(realname, buf2, buf1, cps->pr);
8650 /* Commands from the engine directly to ICS. We don't allow these to be
8651 * sent until we are logged on. Crafty kibitzes have been known to
8652 * interfere with the login process.
8655 if (!strncmp(message, "tellics ", 8)) {
8656 SendToICS(message + 8);
8660 if (!strncmp(message, "tellicsnoalias ", 15)) {
8661 SendToICS(ics_prefix);
8662 SendToICS(message + 15);
8666 /* The following are for backward compatibility only */
8667 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8668 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8669 SendToICS(ics_prefix);
8675 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8678 if(!strncmp(message, "highlight ", 10)) {
8679 if(appData.testLegality && appData.markers) return;
8680 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8683 if(!strncmp(message, "click ", 6)) {
8684 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8685 if(appData.testLegality || !appData.oneClick) return;
8686 sscanf(message+6, "%c%d%c", &f, &y, &c);
8687 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8688 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8689 x = x*squareSize + (x+1)*lineGap + squareSize/2;
8690 y = y*squareSize + (y+1)*lineGap + squareSize/2;
8691 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8692 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8693 LeftClick(Release, lastLeftX, lastLeftY);
8694 controlKey = (c == ',');
8695 LeftClick(Press, x, y);
8696 LeftClick(Release, x, y);
8697 first.highlight = f;
8701 * If the move is illegal, cancel it and redraw the board.
8702 * Also deal with other error cases. Matching is rather loose
8703 * here to accommodate engines written before the spec.
8705 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8706 strncmp(message, "Error", 5) == 0) {
8707 if (StrStr(message, "name") ||
8708 StrStr(message, "rating") || StrStr(message, "?") ||
8709 StrStr(message, "result") || StrStr(message, "board") ||
8710 StrStr(message, "bk") || StrStr(message, "computer") ||
8711 StrStr(message, "variant") || StrStr(message, "hint") ||
8712 StrStr(message, "random") || StrStr(message, "depth") ||
8713 StrStr(message, "accepted")) {
8716 if (StrStr(message, "protover")) {
8717 /* Program is responding to input, so it's apparently done
8718 initializing, and this error message indicates it is
8719 protocol version 1. So we don't need to wait any longer
8720 for it to initialize and send feature commands. */
8721 FeatureDone(cps, 1);
8722 cps->protocolVersion = 1;
8725 cps->maybeThinking = FALSE;
8727 if (StrStr(message, "draw")) {
8728 /* Program doesn't have "draw" command */
8729 cps->sendDrawOffers = 0;
8732 if (cps->sendTime != 1 &&
8733 (StrStr(message, "time") || StrStr(message, "otim"))) {
8734 /* Program apparently doesn't have "time" or "otim" command */
8738 if (StrStr(message, "analyze")) {
8739 cps->analysisSupport = FALSE;
8740 cps->analyzing = FALSE;
8741 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8742 EditGameEvent(); // [HGM] try to preserve loaded game
8743 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8744 DisplayError(buf2, 0);
8747 if (StrStr(message, "(no matching move)st")) {
8748 /* Special kludge for GNU Chess 4 only */
8749 cps->stKludge = TRUE;
8750 SendTimeControl(cps, movesPerSession, timeControl,
8751 timeIncrement, appData.searchDepth,
8755 if (StrStr(message, "(no matching move)sd")) {
8756 /* Special kludge for GNU Chess 4 only */
8757 cps->sdKludge = TRUE;
8758 SendTimeControl(cps, movesPerSession, timeControl,
8759 timeIncrement, appData.searchDepth,
8763 if (!StrStr(message, "llegal")) {
8766 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8767 gameMode == IcsIdle) return;
8768 if (forwardMostMove <= backwardMostMove) return;
8769 if (pausing) PauseEvent();
8770 if(appData.forceIllegal) {
8771 // [HGM] illegal: machine refused move; force position after move into it
8772 SendToProgram("force\n", cps);
8773 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8774 // we have a real problem now, as SendBoard will use the a2a3 kludge
8775 // when black is to move, while there might be nothing on a2 or black
8776 // might already have the move. So send the board as if white has the move.
8777 // But first we must change the stm of the engine, as it refused the last move
8778 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8779 if(WhiteOnMove(forwardMostMove)) {
8780 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8781 SendBoard(cps, forwardMostMove); // kludgeless board
8783 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8784 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8785 SendBoard(cps, forwardMostMove+1); // kludgeless board
8787 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8788 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8789 gameMode == TwoMachinesPlay)
8790 SendToProgram("go\n", cps);
8793 if (gameMode == PlayFromGameFile) {
8794 /* Stop reading this game file */
8795 gameMode = EditGame;
8798 /* [HGM] illegal-move claim should forfeit game when Xboard */
8799 /* only passes fully legal moves */
8800 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8801 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8802 "False illegal-move claim", GE_XBOARD );
8803 return; // do not take back move we tested as valid
8805 currentMove = forwardMostMove-1;
8806 DisplayMove(currentMove-1); /* before DisplayMoveError */
8807 SwitchClocks(forwardMostMove-1); // [HGM] race
8808 DisplayBothClocks();
8809 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8810 parseList[currentMove], _(cps->which));
8811 DisplayMoveError(buf1);
8812 DrawPosition(FALSE, boards[currentMove]);
8814 SetUserThinkingEnables();
8817 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8818 /* Program has a broken "time" command that
8819 outputs a string not ending in newline.
8825 * If chess program startup fails, exit with an error message.
8826 * Attempts to recover here are futile. [HGM] Well, we try anyway
8828 if ((StrStr(message, "unknown host") != NULL)
8829 || (StrStr(message, "No remote directory") != NULL)
8830 || (StrStr(message, "not found") != NULL)
8831 || (StrStr(message, "No such file") != NULL)
8832 || (StrStr(message, "can't alloc") != NULL)
8833 || (StrStr(message, "Permission denied") != NULL)) {
8835 cps->maybeThinking = FALSE;
8836 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8837 _(cps->which), cps->program, cps->host, message);
8838 RemoveInputSource(cps->isr);
8839 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8840 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8841 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8847 * Look for hint output
8849 if (sscanf(message, "Hint: %s", buf1) == 1) {
8850 if (cps == &first && hintRequested) {
8851 hintRequested = FALSE;
8852 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8853 &fromX, &fromY, &toX, &toY, &promoChar)) {
8854 (void) CoordsToAlgebraic(boards[forwardMostMove],
8855 PosFlags(forwardMostMove),
8856 fromY, fromX, toY, toX, promoChar, buf1);
8857 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8858 DisplayInformation(buf2);
8860 /* Hint move could not be parsed!? */
8861 snprintf(buf2, sizeof(buf2),
8862 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8863 buf1, _(cps->which));
8864 DisplayError(buf2, 0);
8867 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8873 * Ignore other messages if game is not in progress
8875 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8876 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8879 * look for win, lose, draw, or draw offer
8881 if (strncmp(message, "1-0", 3) == 0) {
8882 char *p, *q, *r = "";
8883 p = strchr(message, '{');
8891 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8893 } else if (strncmp(message, "0-1", 3) == 0) {
8894 char *p, *q, *r = "";
8895 p = strchr(message, '{');
8903 /* Kludge for Arasan 4.1 bug */
8904 if (strcmp(r, "Black resigns") == 0) {
8905 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8908 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8910 } else if (strncmp(message, "1/2", 3) == 0) {
8911 char *p, *q, *r = "";
8912 p = strchr(message, '{');
8921 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8924 } else if (strncmp(message, "White resign", 12) == 0) {
8925 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8927 } else if (strncmp(message, "Black resign", 12) == 0) {
8928 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8930 } else if (strncmp(message, "White matches", 13) == 0 ||
8931 strncmp(message, "Black matches", 13) == 0 ) {
8932 /* [HGM] ignore GNUShogi noises */
8934 } else if (strncmp(message, "White", 5) == 0 &&
8935 message[5] != '(' &&
8936 StrStr(message, "Black") == NULL) {
8937 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8939 } else if (strncmp(message, "Black", 5) == 0 &&
8940 message[5] != '(') {
8941 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8943 } else if (strcmp(message, "resign") == 0 ||
8944 strcmp(message, "computer resigns") == 0) {
8946 case MachinePlaysBlack:
8947 case IcsPlayingBlack:
8948 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8950 case MachinePlaysWhite:
8951 case IcsPlayingWhite:
8952 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8954 case TwoMachinesPlay:
8955 if (cps->twoMachinesColor[0] == 'w')
8956 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8958 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8965 } else if (strncmp(message, "opponent mates", 14) == 0) {
8967 case MachinePlaysBlack:
8968 case IcsPlayingBlack:
8969 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8971 case MachinePlaysWhite:
8972 case IcsPlayingWhite:
8973 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8975 case TwoMachinesPlay:
8976 if (cps->twoMachinesColor[0] == 'w')
8977 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8979 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8986 } else if (strncmp(message, "computer mates", 14) == 0) {
8988 case MachinePlaysBlack:
8989 case IcsPlayingBlack:
8990 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8992 case MachinePlaysWhite:
8993 case IcsPlayingWhite:
8994 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8996 case TwoMachinesPlay:
8997 if (cps->twoMachinesColor[0] == 'w')
8998 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9000 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9007 } else if (strncmp(message, "checkmate", 9) == 0) {
9008 if (WhiteOnMove(forwardMostMove)) {
9009 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9011 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9014 } else if (strstr(message, "Draw") != NULL ||
9015 strstr(message, "game is a draw") != NULL) {
9016 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9018 } else if (strstr(message, "offer") != NULL &&
9019 strstr(message, "draw") != NULL) {
9021 if (appData.zippyPlay && first.initDone) {
9022 /* Relay offer to ICS */
9023 SendToICS(ics_prefix);
9024 SendToICS("draw\n");
9027 cps->offeredDraw = 2; /* valid until this engine moves twice */
9028 if (gameMode == TwoMachinesPlay) {
9029 if (cps->other->offeredDraw) {
9030 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9031 /* [HGM] in two-machine mode we delay relaying draw offer */
9032 /* until after we also have move, to see if it is really claim */
9034 } else if (gameMode == MachinePlaysWhite ||
9035 gameMode == MachinePlaysBlack) {
9036 if (userOfferedDraw) {
9037 DisplayInformation(_("Machine accepts your draw offer"));
9038 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9040 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
9047 * Look for thinking output
9049 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9050 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9052 int plylev, mvleft, mvtot, curscore, time;
9053 char mvname[MOVE_LEN];
9057 int prefixHint = FALSE;
9058 mvname[0] = NULLCHAR;
9061 case MachinePlaysBlack:
9062 case IcsPlayingBlack:
9063 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9065 case MachinePlaysWhite:
9066 case IcsPlayingWhite:
9067 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9072 case IcsObserving: /* [DM] icsEngineAnalyze */
9073 if (!appData.icsEngineAnalyze) ignore = TRUE;
9075 case TwoMachinesPlay:
9076 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9086 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9088 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9089 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9091 if (plyext != ' ' && plyext != '\t') {
9095 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9096 if( cps->scoreIsAbsolute &&
9097 ( gameMode == MachinePlaysBlack ||
9098 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9099 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9100 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9101 !WhiteOnMove(currentMove)
9104 curscore = -curscore;
9107 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9109 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9112 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9113 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9114 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9115 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9116 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9117 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9119 } else DisplayError(_("failed writing PV"), 0);
9122 tempStats.depth = plylev;
9123 tempStats.nodes = nodes;
9124 tempStats.time = time;
9125 tempStats.score = curscore;
9126 tempStats.got_only_move = 0;
9128 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9131 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9132 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9133 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9134 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9135 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9136 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9137 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9138 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9141 /* Buffer overflow protection */
9142 if (pv[0] != NULLCHAR) {
9143 if (strlen(pv) >= sizeof(tempStats.movelist)
9144 && appData.debugMode) {
9146 "PV is too long; using the first %u bytes.\n",
9147 (unsigned) sizeof(tempStats.movelist) - 1);
9150 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9152 sprintf(tempStats.movelist, " no PV\n");
9155 if (tempStats.seen_stat) {
9156 tempStats.ok_to_send = 1;
9159 if (strchr(tempStats.movelist, '(') != NULL) {
9160 tempStats.line_is_book = 1;
9161 tempStats.nr_moves = 0;
9162 tempStats.moves_left = 0;
9164 tempStats.line_is_book = 0;
9167 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9168 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9170 SendProgramStatsToFrontend( cps, &tempStats );
9173 [AS] Protect the thinkOutput buffer from overflow... this
9174 is only useful if buf1 hasn't overflowed first!
9176 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9178 (gameMode == TwoMachinesPlay ?
9179 ToUpper(cps->twoMachinesColor[0]) : ' '),
9180 ((double) curscore) / 100.0,
9181 prefixHint ? lastHint : "",
9182 prefixHint ? " " : "" );
9184 if( buf1[0] != NULLCHAR ) {
9185 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9187 if( strlen(pv) > max_len ) {
9188 if( appData.debugMode) {
9189 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9191 pv[max_len+1] = '\0';
9194 strcat( thinkOutput, pv);
9197 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9198 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9199 DisplayMove(currentMove - 1);
9203 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9204 /* crafty (9.25+) says "(only move) <move>"
9205 * if there is only 1 legal move
9207 sscanf(p, "(only move) %s", buf1);
9208 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9209 sprintf(programStats.movelist, "%s (only move)", buf1);
9210 programStats.depth = 1;
9211 programStats.nr_moves = 1;
9212 programStats.moves_left = 1;
9213 programStats.nodes = 1;
9214 programStats.time = 1;
9215 programStats.got_only_move = 1;
9217 /* Not really, but we also use this member to
9218 mean "line isn't going to change" (Crafty
9219 isn't searching, so stats won't change) */
9220 programStats.line_is_book = 1;
9222 SendProgramStatsToFrontend( cps, &programStats );
9224 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9225 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9226 DisplayMove(currentMove - 1);
9229 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9230 &time, &nodes, &plylev, &mvleft,
9231 &mvtot, mvname) >= 5) {
9232 /* The stat01: line is from Crafty (9.29+) in response
9233 to the "." command */
9234 programStats.seen_stat = 1;
9235 cps->maybeThinking = TRUE;
9237 if (programStats.got_only_move || !appData.periodicUpdates)
9240 programStats.depth = plylev;
9241 programStats.time = time;
9242 programStats.nodes = nodes;
9243 programStats.moves_left = mvleft;
9244 programStats.nr_moves = mvtot;
9245 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9246 programStats.ok_to_send = 1;
9247 programStats.movelist[0] = '\0';
9249 SendProgramStatsToFrontend( cps, &programStats );
9253 } else if (strncmp(message,"++",2) == 0) {
9254 /* Crafty 9.29+ outputs this */
9255 programStats.got_fail = 2;
9258 } else if (strncmp(message,"--",2) == 0) {
9259 /* Crafty 9.29+ outputs this */
9260 programStats.got_fail = 1;
9263 } else if (thinkOutput[0] != NULLCHAR &&
9264 strncmp(message, " ", 4) == 0) {
9265 unsigned message_len;
9268 while (*p && *p == ' ') p++;
9270 message_len = strlen( p );
9272 /* [AS] Avoid buffer overflow */
9273 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9274 strcat(thinkOutput, " ");
9275 strcat(thinkOutput, p);
9278 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9279 strcat(programStats.movelist, " ");
9280 strcat(programStats.movelist, p);
9283 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9284 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9285 DisplayMove(currentMove - 1);
9293 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9294 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9296 ChessProgramStats cpstats;
9298 if (plyext != ' ' && plyext != '\t') {
9302 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9303 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9304 curscore = -curscore;
9307 cpstats.depth = plylev;
9308 cpstats.nodes = nodes;
9309 cpstats.time = time;
9310 cpstats.score = curscore;
9311 cpstats.got_only_move = 0;
9312 cpstats.movelist[0] = '\0';
9314 if (buf1[0] != NULLCHAR) {
9315 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9318 cpstats.ok_to_send = 0;
9319 cpstats.line_is_book = 0;
9320 cpstats.nr_moves = 0;
9321 cpstats.moves_left = 0;
9323 SendProgramStatsToFrontend( cps, &cpstats );
9330 /* Parse a game score from the character string "game", and
9331 record it as the history of the current game. The game
9332 score is NOT assumed to start from the standard position.
9333 The display is not updated in any way.
9336 ParseGameHistory (char *game)
9339 int fromX, fromY, toX, toY, boardIndex;
9344 if (appData.debugMode)
9345 fprintf(debugFP, "Parsing game history: %s\n", game);
9347 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9348 gameInfo.site = StrSave(appData.icsHost);
9349 gameInfo.date = PGNDate();
9350 gameInfo.round = StrSave("-");
9352 /* Parse out names of players */
9353 while (*game == ' ') game++;
9355 while (*game != ' ') *p++ = *game++;
9357 gameInfo.white = StrSave(buf);
9358 while (*game == ' ') game++;
9360 while (*game != ' ' && *game != '\n') *p++ = *game++;
9362 gameInfo.black = StrSave(buf);
9365 boardIndex = blackPlaysFirst ? 1 : 0;
9368 yyboardindex = boardIndex;
9369 moveType = (ChessMove) Myylex();
9371 case IllegalMove: /* maybe suicide chess, etc. */
9372 if (appData.debugMode) {
9373 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9374 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9375 setbuf(debugFP, NULL);
9377 case WhitePromotion:
9378 case BlackPromotion:
9379 case WhiteNonPromotion:
9380 case BlackNonPromotion:
9382 case WhiteCapturesEnPassant:
9383 case BlackCapturesEnPassant:
9384 case WhiteKingSideCastle:
9385 case WhiteQueenSideCastle:
9386 case BlackKingSideCastle:
9387 case BlackQueenSideCastle:
9388 case WhiteKingSideCastleWild:
9389 case WhiteQueenSideCastleWild:
9390 case BlackKingSideCastleWild:
9391 case BlackQueenSideCastleWild:
9393 case WhiteHSideCastleFR:
9394 case WhiteASideCastleFR:
9395 case BlackHSideCastleFR:
9396 case BlackASideCastleFR:
9398 fromX = currentMoveString[0] - AAA;
9399 fromY = currentMoveString[1] - ONE;
9400 toX = currentMoveString[2] - AAA;
9401 toY = currentMoveString[3] - ONE;
9402 promoChar = currentMoveString[4];
9406 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9407 fromX = moveType == WhiteDrop ?
9408 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9409 (int) CharToPiece(ToLower(currentMoveString[0]));
9411 toX = currentMoveString[2] - AAA;
9412 toY = currentMoveString[3] - ONE;
9413 promoChar = NULLCHAR;
9417 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9418 if (appData.debugMode) {
9419 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9420 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9421 setbuf(debugFP, NULL);
9423 DisplayError(buf, 0);
9425 case ImpossibleMove:
9427 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9428 if (appData.debugMode) {
9429 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9430 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9431 setbuf(debugFP, NULL);
9433 DisplayError(buf, 0);
9436 if (boardIndex < backwardMostMove) {
9437 /* Oops, gap. How did that happen? */
9438 DisplayError(_("Gap in move list"), 0);
9441 backwardMostMove = blackPlaysFirst ? 1 : 0;
9442 if (boardIndex > forwardMostMove) {
9443 forwardMostMove = boardIndex;
9447 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9448 strcat(parseList[boardIndex-1], " ");
9449 strcat(parseList[boardIndex-1], yy_text);
9461 case GameUnfinished:
9462 if (gameMode == IcsExamining) {
9463 if (boardIndex < backwardMostMove) {
9464 /* Oops, gap. How did that happen? */
9467 backwardMostMove = blackPlaysFirst ? 1 : 0;
9470 gameInfo.result = moveType;
9471 p = strchr(yy_text, '{');
9472 if (p == NULL) p = strchr(yy_text, '(');
9475 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9477 q = strchr(p, *p == '{' ? '}' : ')');
9478 if (q != NULL) *q = NULLCHAR;
9481 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9482 gameInfo.resultDetails = StrSave(p);
9485 if (boardIndex >= forwardMostMove &&
9486 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9487 backwardMostMove = blackPlaysFirst ? 1 : 0;
9490 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9491 fromY, fromX, toY, toX, promoChar,
9492 parseList[boardIndex]);
9493 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9494 /* currentMoveString is set as a side-effect of yylex */
9495 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9496 strcat(moveList[boardIndex], "\n");
9498 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9499 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9505 if(gameInfo.variant != VariantShogi)
9506 strcat(parseList[boardIndex - 1], "+");
9510 strcat(parseList[boardIndex - 1], "#");
9517 /* Apply a move to the given board */
9519 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9521 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9522 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9524 /* [HGM] compute & store e.p. status and castling rights for new position */
9525 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9527 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9528 oldEP = (signed char)board[EP_STATUS];
9529 board[EP_STATUS] = EP_NONE;
9531 if (fromY == DROP_RANK) {
9533 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9534 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9537 piece = board[toY][toX] = (ChessSquare) fromX;
9541 if( board[toY][toX] != EmptySquare )
9542 board[EP_STATUS] = EP_CAPTURE;
9544 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9545 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9546 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9548 if( board[fromY][fromX] == WhitePawn ) {
9549 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9550 board[EP_STATUS] = EP_PAWN_MOVE;
9552 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9553 gameInfo.variant != VariantBerolina || toX < fromX)
9554 board[EP_STATUS] = toX | berolina;
9555 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9556 gameInfo.variant != VariantBerolina || toX > fromX)
9557 board[EP_STATUS] = toX;
9560 if( board[fromY][fromX] == BlackPawn ) {
9561 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9562 board[EP_STATUS] = EP_PAWN_MOVE;
9563 if( toY-fromY== -2) {
9564 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9565 gameInfo.variant != VariantBerolina || toX < fromX)
9566 board[EP_STATUS] = toX | berolina;
9567 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9568 gameInfo.variant != VariantBerolina || toX > fromX)
9569 board[EP_STATUS] = toX;
9573 for(i=0; i<nrCastlingRights; i++) {
9574 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9575 board[CASTLING][i] == toX && castlingRank[i] == toY
9576 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9579 if(gameInfo.variant == VariantSChess) { // update virginity
9580 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9581 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9582 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9583 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9586 if (fromX == toX && fromY == toY) return;
9588 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9589 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9590 if(gameInfo.variant == VariantKnightmate)
9591 king += (int) WhiteUnicorn - (int) WhiteKing;
9593 /* Code added by Tord: */
9594 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9595 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9596 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9597 board[fromY][fromX] = EmptySquare;
9598 board[toY][toX] = EmptySquare;
9599 if((toX > fromX) != (piece == WhiteRook)) {
9600 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9602 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9604 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9605 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9606 board[fromY][fromX] = EmptySquare;
9607 board[toY][toX] = EmptySquare;
9608 if((toX > fromX) != (piece == BlackRook)) {
9609 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9611 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9613 /* End of code added by Tord */
9615 } else if (board[fromY][fromX] == king
9616 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9617 && toY == fromY && toX > fromX+1) {
9618 board[fromY][fromX] = EmptySquare;
9619 board[toY][toX] = king;
9620 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9621 board[fromY][BOARD_RGHT-1] = EmptySquare;
9622 } else if (board[fromY][fromX] == king
9623 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9624 && toY == fromY && toX < fromX-1) {
9625 board[fromY][fromX] = EmptySquare;
9626 board[toY][toX] = king;
9627 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9628 board[fromY][BOARD_LEFT] = EmptySquare;
9629 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9630 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9631 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9633 /* white pawn promotion */
9634 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9635 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9636 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9637 board[fromY][fromX] = EmptySquare;
9638 } else if ((fromY >= BOARD_HEIGHT>>1)
9639 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9641 && gameInfo.variant != VariantXiangqi
9642 && gameInfo.variant != VariantBerolina
9643 && (board[fromY][fromX] == WhitePawn)
9644 && (board[toY][toX] == EmptySquare)) {
9645 board[fromY][fromX] = EmptySquare;
9646 board[toY][toX] = WhitePawn;
9647 captured = board[toY - 1][toX];
9648 board[toY - 1][toX] = EmptySquare;
9649 } else if ((fromY == BOARD_HEIGHT-4)
9651 && gameInfo.variant == VariantBerolina
9652 && (board[fromY][fromX] == WhitePawn)
9653 && (board[toY][toX] == EmptySquare)) {
9654 board[fromY][fromX] = EmptySquare;
9655 board[toY][toX] = WhitePawn;
9656 if(oldEP & EP_BEROLIN_A) {
9657 captured = board[fromY][fromX-1];
9658 board[fromY][fromX-1] = EmptySquare;
9659 }else{ captured = board[fromY][fromX+1];
9660 board[fromY][fromX+1] = EmptySquare;
9662 } else if (board[fromY][fromX] == king
9663 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9664 && toY == fromY && toX > fromX+1) {
9665 board[fromY][fromX] = EmptySquare;
9666 board[toY][toX] = king;
9667 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9668 board[fromY][BOARD_RGHT-1] = EmptySquare;
9669 } else if (board[fromY][fromX] == king
9670 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9671 && toY == fromY && toX < fromX-1) {
9672 board[fromY][fromX] = EmptySquare;
9673 board[toY][toX] = king;
9674 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9675 board[fromY][BOARD_LEFT] = EmptySquare;
9676 } else if (fromY == 7 && fromX == 3
9677 && board[fromY][fromX] == BlackKing
9678 && toY == 7 && toX == 5) {
9679 board[fromY][fromX] = EmptySquare;
9680 board[toY][toX] = BlackKing;
9681 board[fromY][7] = EmptySquare;
9682 board[toY][4] = BlackRook;
9683 } else if (fromY == 7 && fromX == 3
9684 && board[fromY][fromX] == BlackKing
9685 && toY == 7 && toX == 1) {
9686 board[fromY][fromX] = EmptySquare;
9687 board[toY][toX] = BlackKing;
9688 board[fromY][0] = EmptySquare;
9689 board[toY][2] = BlackRook;
9690 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9691 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9692 && toY < promoRank && promoChar
9694 /* black pawn promotion */
9695 board[toY][toX] = CharToPiece(ToLower(promoChar));
9696 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9697 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9698 board[fromY][fromX] = EmptySquare;
9699 } else if ((fromY < BOARD_HEIGHT>>1)
9700 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9702 && gameInfo.variant != VariantXiangqi
9703 && gameInfo.variant != VariantBerolina
9704 && (board[fromY][fromX] == BlackPawn)
9705 && (board[toY][toX] == EmptySquare)) {
9706 board[fromY][fromX] = EmptySquare;
9707 board[toY][toX] = BlackPawn;
9708 captured = board[toY + 1][toX];
9709 board[toY + 1][toX] = EmptySquare;
9710 } else if ((fromY == 3)
9712 && gameInfo.variant == VariantBerolina
9713 && (board[fromY][fromX] == BlackPawn)
9714 && (board[toY][toX] == EmptySquare)) {
9715 board[fromY][fromX] = EmptySquare;
9716 board[toY][toX] = BlackPawn;
9717 if(oldEP & EP_BEROLIN_A) {
9718 captured = board[fromY][fromX-1];
9719 board[fromY][fromX-1] = EmptySquare;
9720 }else{ captured = board[fromY][fromX+1];
9721 board[fromY][fromX+1] = EmptySquare;
9724 board[toY][toX] = board[fromY][fromX];
9725 board[fromY][fromX] = EmptySquare;
9729 if (gameInfo.holdingsWidth != 0) {
9731 /* !!A lot more code needs to be written to support holdings */
9732 /* [HGM] OK, so I have written it. Holdings are stored in the */
9733 /* penultimate board files, so they are automaticlly stored */
9734 /* in the game history. */
9735 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9736 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9737 /* Delete from holdings, by decreasing count */
9738 /* and erasing image if necessary */
9739 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9740 if(p < (int) BlackPawn) { /* white drop */
9741 p -= (int)WhitePawn;
9742 p = PieceToNumber((ChessSquare)p);
9743 if(p >= gameInfo.holdingsSize) p = 0;
9744 if(--board[p][BOARD_WIDTH-2] <= 0)
9745 board[p][BOARD_WIDTH-1] = EmptySquare;
9746 if((int)board[p][BOARD_WIDTH-2] < 0)
9747 board[p][BOARD_WIDTH-2] = 0;
9748 } else { /* black drop */
9749 p -= (int)BlackPawn;
9750 p = PieceToNumber((ChessSquare)p);
9751 if(p >= gameInfo.holdingsSize) p = 0;
9752 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9753 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9754 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9755 board[BOARD_HEIGHT-1-p][1] = 0;
9758 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9759 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9760 /* [HGM] holdings: Add to holdings, if holdings exist */
9761 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9762 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9763 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9766 if (p >= (int) BlackPawn) {
9767 p -= (int)BlackPawn;
9768 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9769 /* in Shogi restore piece to its original first */
9770 captured = (ChessSquare) (DEMOTED captured);
9773 p = PieceToNumber((ChessSquare)p);
9774 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9775 board[p][BOARD_WIDTH-2]++;
9776 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9778 p -= (int)WhitePawn;
9779 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9780 captured = (ChessSquare) (DEMOTED captured);
9783 p = PieceToNumber((ChessSquare)p);
9784 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9785 board[BOARD_HEIGHT-1-p][1]++;
9786 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9789 } else if (gameInfo.variant == VariantAtomic) {
9790 if (captured != EmptySquare) {
9792 for (y = toY-1; y <= toY+1; y++) {
9793 for (x = toX-1; x <= toX+1; x++) {
9794 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9795 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9796 board[y][x] = EmptySquare;
9800 board[toY][toX] = EmptySquare;
9803 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9804 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9806 if(promoChar == '+') {
9807 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9808 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9809 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9810 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9811 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9812 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9813 board[toY][toX] = newPiece;
9815 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9816 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9817 // [HGM] superchess: take promotion piece out of holdings
9818 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9819 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9820 if(!--board[k][BOARD_WIDTH-2])
9821 board[k][BOARD_WIDTH-1] = EmptySquare;
9823 if(!--board[BOARD_HEIGHT-1-k][1])
9824 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9830 /* Updates forwardMostMove */
9832 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9834 // forwardMostMove++; // [HGM] bare: moved downstream
9836 (void) CoordsToAlgebraic(boards[forwardMostMove],
9837 PosFlags(forwardMostMove),
9838 fromY, fromX, toY, toX, promoChar,
9839 parseList[forwardMostMove]);
9841 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9842 int timeLeft; static int lastLoadFlag=0; int king, piece;
9843 piece = boards[forwardMostMove][fromY][fromX];
9844 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9845 if(gameInfo.variant == VariantKnightmate)
9846 king += (int) WhiteUnicorn - (int) WhiteKing;
9847 if(forwardMostMove == 0) {
9848 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9849 fprintf(serverMoves, "%s;", UserName());
9850 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9851 fprintf(serverMoves, "%s;", second.tidy);
9852 fprintf(serverMoves, "%s;", first.tidy);
9853 if(gameMode == MachinePlaysWhite)
9854 fprintf(serverMoves, "%s;", UserName());
9855 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9856 fprintf(serverMoves, "%s;", second.tidy);
9857 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9858 lastLoadFlag = loadFlag;
9860 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9861 // print castling suffix
9862 if( toY == fromY && piece == king ) {
9864 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9866 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9869 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9870 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9871 boards[forwardMostMove][toY][toX] == EmptySquare
9872 && fromX != toX && fromY != toY)
9873 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9875 if(promoChar != NULLCHAR) {
9876 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9877 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9878 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9879 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9882 char buf[MOVE_LEN*2], *p; int len;
9883 fprintf(serverMoves, "/%d/%d",
9884 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9885 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9886 else timeLeft = blackTimeRemaining/1000;
9887 fprintf(serverMoves, "/%d", timeLeft);
9888 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9889 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9890 if(p = strchr(buf, '=')) *p = NULLCHAR;
9891 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9892 fprintf(serverMoves, "/%s", buf);
9894 fflush(serverMoves);
9897 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9898 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9901 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9902 if (commentList[forwardMostMove+1] != NULL) {
9903 free(commentList[forwardMostMove+1]);
9904 commentList[forwardMostMove+1] = NULL;
9906 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9907 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9908 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9909 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9910 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9911 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9912 adjustedClock = FALSE;
9913 gameInfo.result = GameUnfinished;
9914 if (gameInfo.resultDetails != NULL) {
9915 free(gameInfo.resultDetails);
9916 gameInfo.resultDetails = NULL;
9918 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9919 moveList[forwardMostMove - 1]);
9920 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9926 if(gameInfo.variant != VariantShogi)
9927 strcat(parseList[forwardMostMove - 1], "+");
9931 strcat(parseList[forwardMostMove - 1], "#");
9937 /* Updates currentMove if not pausing */
9939 ShowMove (int fromX, int fromY, int toX, int toY)
9941 int instant = (gameMode == PlayFromGameFile) ?
9942 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9943 if(appData.noGUI) return;
9944 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9946 if (forwardMostMove == currentMove + 1) {
9947 AnimateMove(boards[forwardMostMove - 1],
9948 fromX, fromY, toX, toY);
9951 currentMove = forwardMostMove;
9954 if (instant) return;
9956 DisplayMove(currentMove - 1);
9957 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9958 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9959 SetHighlights(fromX, fromY, toX, toY);
9962 DrawPosition(FALSE, boards[currentMove]);
9963 DisplayBothClocks();
9964 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9968 SendEgtPath (ChessProgramState *cps)
9969 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9970 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9972 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9975 char c, *q = name+1, *r, *s;
9977 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9978 while(*p && *p != ',') *q++ = *p++;
9980 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9981 strcmp(name, ",nalimov:") == 0 ) {
9982 // take nalimov path from the menu-changeable option first, if it is defined
9983 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9984 SendToProgram(buf,cps); // send egtbpath command for nalimov
9986 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9987 (s = StrStr(appData.egtFormats, name)) != NULL) {
9988 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9989 s = r = StrStr(s, ":") + 1; // beginning of path info
9990 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9991 c = *r; *r = 0; // temporarily null-terminate path info
9992 *--q = 0; // strip of trailig ':' from name
9993 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9995 SendToProgram(buf,cps); // send egtbpath command for this format
9997 if(*p == ',') p++; // read away comma to position for next format name
10002 NonStandardBoardSize ()
10004 /* [HGM] Awkward testing. Should really be a table */
10005 int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10006 if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10007 if( gameInfo.variant == VariantXiangqi )
10008 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10009 if( gameInfo.variant == VariantShogi )
10010 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10011 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10012 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10013 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10014 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10015 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10016 if( gameInfo.variant == VariantCourier )
10017 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10018 if( gameInfo.variant == VariantSuper )
10019 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10020 if( gameInfo.variant == VariantGreat )
10021 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10022 if( gameInfo.variant == VariantSChess )
10023 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10024 if( gameInfo.variant == VariantGrand )
10025 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10030 InitChessProgram (ChessProgramState *cps, int setup)
10031 /* setup needed to setup FRC opening position */
10033 char buf[MSG_SIZ], b[MSG_SIZ];
10034 if (appData.noChessProgram) return;
10035 hintRequested = FALSE;
10036 bookRequested = FALSE;
10038 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10039 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10040 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10041 if(cps->memSize) { /* [HGM] memory */
10042 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10043 SendToProgram(buf, cps);
10045 SendEgtPath(cps); /* [HGM] EGT */
10046 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10047 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10048 SendToProgram(buf, cps);
10051 SendToProgram(cps->initString, cps);
10052 if (gameInfo.variant != VariantNormal &&
10053 gameInfo.variant != VariantLoadable
10054 /* [HGM] also send variant if board size non-standard */
10055 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10057 char *v = VariantName(gameInfo.variant);
10058 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10059 /* [HGM] in protocol 1 we have to assume all variants valid */
10060 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10061 DisplayFatalError(buf, 0, 1);
10065 if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10066 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10067 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10068 /* [HGM] varsize: try first if this defiant size variant is specifically known */
10069 if(StrStr(cps->variants, b) == NULL) {
10070 // specific sized variant not known, check if general sizing allowed
10071 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10072 if(StrStr(cps->variants, "boardsize") == NULL) {
10073 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10074 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10075 DisplayFatalError(buf, 0, 1);
10078 /* [HGM] here we really should compare with the maximum supported board size */
10081 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10082 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10083 SendToProgram(buf, cps);
10085 currentlyInitializedVariant = gameInfo.variant;
10087 /* [HGM] send opening position in FRC to first engine */
10089 SendToProgram("force\n", cps);
10091 /* engine is now in force mode! Set flag to wake it up after first move. */
10092 setboardSpoiledMachineBlack = 1;
10095 if (cps->sendICS) {
10096 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10097 SendToProgram(buf, cps);
10099 cps->maybeThinking = FALSE;
10100 cps->offeredDraw = 0;
10101 if (!appData.icsActive) {
10102 SendTimeControl(cps, movesPerSession, timeControl,
10103 timeIncrement, appData.searchDepth,
10106 if (appData.showThinking
10107 // [HGM] thinking: four options require thinking output to be sent
10108 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10110 SendToProgram("post\n", cps);
10112 SendToProgram("hard\n", cps);
10113 if (!appData.ponderNextMove) {
10114 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10115 it without being sure what state we are in first. "hard"
10116 is not a toggle, so that one is OK.
10118 SendToProgram("easy\n", cps);
10120 if (cps->usePing) {
10121 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10122 SendToProgram(buf, cps);
10124 cps->initDone = TRUE;
10125 ClearEngineOutputPane(cps == &second);
10130 ResendOptions (ChessProgramState *cps)
10131 { // send the stored value of the options
10134 Option *opt = cps->option;
10135 for(i=0; i<cps->nrOptions; i++, opt++) {
10136 switch(opt->type) {
10140 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10143 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10146 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10152 SendToProgram(buf, cps);
10157 StartChessProgram (ChessProgramState *cps)
10162 if (appData.noChessProgram) return;
10163 cps->initDone = FALSE;
10165 if (strcmp(cps->host, "localhost") == 0) {
10166 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10167 } else if (*appData.remoteShell == NULLCHAR) {
10168 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10170 if (*appData.remoteUser == NULLCHAR) {
10171 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10174 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10175 cps->host, appData.remoteUser, cps->program);
10177 err = StartChildProcess(buf, "", &cps->pr);
10181 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10182 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10183 if(cps != &first) return;
10184 appData.noChessProgram = TRUE;
10187 // DisplayFatalError(buf, err, 1);
10188 // cps->pr = NoProc;
10189 // cps->isr = NULL;
10193 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10194 if (cps->protocolVersion > 1) {
10195 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10196 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10197 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10198 cps->comboCnt = 0; // and values of combo boxes
10200 SendToProgram(buf, cps);
10201 if(cps->reload) ResendOptions(cps);
10203 SendToProgram("xboard\n", cps);
10208 TwoMachinesEventIfReady P((void))
10210 static int curMess = 0;
10211 if (first.lastPing != first.lastPong) {
10212 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10213 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10216 if (second.lastPing != second.lastPong) {
10217 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10218 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10221 DisplayMessage("", ""); curMess = 0;
10222 TwoMachinesEvent();
10226 MakeName (char *template)
10230 static char buf[MSG_SIZ];
10234 clock = time((time_t *)NULL);
10235 tm = localtime(&clock);
10237 while(*p++ = *template++) if(p[-1] == '%') {
10238 switch(*template++) {
10239 case 0: *p = 0; return buf;
10240 case 'Y': i = tm->tm_year+1900; break;
10241 case 'y': i = tm->tm_year-100; break;
10242 case 'M': i = tm->tm_mon+1; break;
10243 case 'd': i = tm->tm_mday; break;
10244 case 'h': i = tm->tm_hour; break;
10245 case 'm': i = tm->tm_min; break;
10246 case 's': i = tm->tm_sec; break;
10249 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10255 CountPlayers (char *p)
10258 while(p = strchr(p, '\n')) p++, n++; // count participants
10263 WriteTourneyFile (char *results, FILE *f)
10264 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10265 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10266 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10267 // create a file with tournament description
10268 fprintf(f, "-participants {%s}\n", appData.participants);
10269 fprintf(f, "-seedBase %d\n", appData.seedBase);
10270 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10271 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10272 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10273 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10274 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10275 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10276 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10277 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10278 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10279 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10280 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10281 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10282 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10283 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10284 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10285 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10286 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10287 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10288 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10289 fprintf(f, "-smpCores %d\n", appData.smpCores);
10291 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10293 fprintf(f, "-mps %d\n", appData.movesPerSession);
10294 fprintf(f, "-tc %s\n", appData.timeControl);
10295 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10297 fprintf(f, "-results \"%s\"\n", results);
10302 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10305 Substitute (char *participants, int expunge)
10307 int i, changed, changes=0, nPlayers=0;
10308 char *p, *q, *r, buf[MSG_SIZ];
10309 if(participants == NULL) return;
10310 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10311 r = p = participants; q = appData.participants;
10312 while(*p && *p == *q) {
10313 if(*p == '\n') r = p+1, nPlayers++;
10316 if(*p) { // difference
10317 while(*p && *p++ != '\n');
10318 while(*q && *q++ != '\n');
10319 changed = nPlayers;
10320 changes = 1 + (strcmp(p, q) != 0);
10322 if(changes == 1) { // a single engine mnemonic was changed
10323 q = r; while(*q) nPlayers += (*q++ == '\n');
10324 p = buf; while(*r && (*p = *r++) != '\n') p++;
10326 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10327 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10328 if(mnemonic[i]) { // The substitute is valid
10330 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10331 flock(fileno(f), LOCK_EX);
10332 ParseArgsFromFile(f);
10333 fseek(f, 0, SEEK_SET);
10334 FREE(appData.participants); appData.participants = participants;
10335 if(expunge) { // erase results of replaced engine
10336 int len = strlen(appData.results), w, b, dummy;
10337 for(i=0; i<len; i++) {
10338 Pairing(i, nPlayers, &w, &b, &dummy);
10339 if((w == changed || b == changed) && appData.results[i] == '*') {
10340 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10345 for(i=0; i<len; i++) {
10346 Pairing(i, nPlayers, &w, &b, &dummy);
10347 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10350 WriteTourneyFile(appData.results, f);
10351 fclose(f); // release lock
10354 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10356 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10357 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10358 free(participants);
10363 CheckPlayers (char *participants)
10366 char buf[MSG_SIZ], *p;
10367 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10368 while(p = strchr(participants, '\n')) {
10370 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10372 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10374 DisplayError(buf, 0);
10378 participants = p + 1;
10384 CreateTourney (char *name)
10387 if(matchMode && strcmp(name, appData.tourneyFile)) {
10388 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10390 if(name[0] == NULLCHAR) {
10391 if(appData.participants[0])
10392 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10395 f = fopen(name, "r");
10396 if(f) { // file exists
10397 ASSIGN(appData.tourneyFile, name);
10398 ParseArgsFromFile(f); // parse it
10400 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10401 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10402 DisplayError(_("Not enough participants"), 0);
10405 if(CheckPlayers(appData.participants)) return 0;
10406 ASSIGN(appData.tourneyFile, name);
10407 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10408 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10411 appData.noChessProgram = FALSE;
10412 appData.clockMode = TRUE;
10418 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10420 char buf[MSG_SIZ], *p, *q;
10421 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10422 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10423 skip = !all && group[0]; // if group requested, we start in skip mode
10424 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10425 p = names; q = buf; header = 0;
10426 while(*p && *p != '\n') *q++ = *p++;
10428 if(*p == '\n') p++;
10429 if(buf[0] == '#') {
10430 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10431 depth++; // we must be entering a new group
10432 if(all) continue; // suppress printing group headers when complete list requested
10434 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10436 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10437 if(engineList[i]) free(engineList[i]);
10438 engineList[i] = strdup(buf);
10439 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10440 if(engineMnemonic[i]) free(engineMnemonic[i]);
10441 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10443 sscanf(q + 8, "%s", buf + strlen(buf));
10446 engineMnemonic[i] = strdup(buf);
10449 engineList[i] = engineMnemonic[i] = NULL;
10453 // following implemented as macro to avoid type limitations
10454 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10457 SwapEngines (int n)
10458 { // swap settings for first engine and other engine (so far only some selected options)
10463 SWAP(chessProgram, p)
10465 SWAP(hasOwnBookUCI, h)
10466 SWAP(protocolVersion, h)
10468 SWAP(scoreIsAbsolute, h)
10473 SWAP(engOptions, p)
10474 SWAP(engInitString, p)
10475 SWAP(computerString, p)
10477 SWAP(fenOverride, p)
10479 SWAP(accumulateTC, h)
10484 GetEngineLine (char *s, int n)
10488 extern char *icsNames;
10489 if(!s || !*s) return 0;
10490 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10491 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10492 if(!mnemonic[i]) return 0;
10493 if(n == 11) return 1; // just testing if there was a match
10494 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10495 if(n == 1) SwapEngines(n);
10496 ParseArgsFromString(buf);
10497 if(n == 1) SwapEngines(n);
10498 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10499 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10500 ParseArgsFromString(buf);
10506 SetPlayer (int player, char *p)
10507 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10509 char buf[MSG_SIZ], *engineName;
10510 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10511 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10512 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10514 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10515 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10516 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10517 ParseArgsFromString(buf);
10518 } else { // no engine with this nickname is installed!
10519 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10520 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10521 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10523 DisplayError(buf, 0);
10530 char *recentEngines;
10533 RecentEngineEvent (int nr)
10536 // SwapEngines(1); // bump first to second
10537 // ReplaceEngine(&second, 1); // and load it there
10538 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10539 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10540 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10541 ReplaceEngine(&first, 0);
10542 FloatToFront(&appData.recentEngineList, command[n]);
10547 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10548 { // determine players from game number
10549 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10551 if(appData.tourneyType == 0) {
10552 roundsPerCycle = (nPlayers - 1) | 1;
10553 pairingsPerRound = nPlayers / 2;
10554 } else if(appData.tourneyType > 0) {
10555 roundsPerCycle = nPlayers - appData.tourneyType;
10556 pairingsPerRound = appData.tourneyType;
10558 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10559 gamesPerCycle = gamesPerRound * roundsPerCycle;
10560 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10561 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10562 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10563 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10564 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10565 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10567 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10568 if(appData.roundSync) *syncInterval = gamesPerRound;
10570 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10572 if(appData.tourneyType == 0) {
10573 if(curPairing == (nPlayers-1)/2 ) {
10574 *whitePlayer = curRound;
10575 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10577 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10578 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10579 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10580 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10582 } else if(appData.tourneyType > 1) {
10583 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10584 *whitePlayer = curRound + appData.tourneyType;
10585 } else if(appData.tourneyType > 0) {
10586 *whitePlayer = curPairing;
10587 *blackPlayer = curRound + appData.tourneyType;
10590 // take care of white/black alternation per round.
10591 // For cycles and games this is already taken care of by default, derived from matchGame!
10592 return curRound & 1;
10596 NextTourneyGame (int nr, int *swapColors)
10597 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10599 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10601 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10602 tf = fopen(appData.tourneyFile, "r");
10603 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10604 ParseArgsFromFile(tf); fclose(tf);
10605 InitTimeControls(); // TC might be altered from tourney file
10607 nPlayers = CountPlayers(appData.participants); // count participants
10608 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10609 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10612 p = q = appData.results;
10613 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10614 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10615 DisplayMessage(_("Waiting for other game(s)"),"");
10616 waitingForGame = TRUE;
10617 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10620 waitingForGame = FALSE;
10623 if(appData.tourneyType < 0) {
10624 if(nr>=0 && !pairingReceived) {
10626 if(pairing.pr == NoProc) {
10627 if(!appData.pairingEngine[0]) {
10628 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10631 StartChessProgram(&pairing); // starts the pairing engine
10633 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10634 SendToProgram(buf, &pairing);
10635 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10636 SendToProgram(buf, &pairing);
10637 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10639 pairingReceived = 0; // ... so we continue here
10641 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10642 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10643 matchGame = 1; roundNr = nr / syncInterval + 1;
10646 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10648 // redefine engines, engine dir, etc.
10649 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10650 if(first.pr == NoProc) {
10651 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10652 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10654 if(second.pr == NoProc) {
10656 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10657 SwapEngines(1); // and make that valid for second engine by swapping
10658 InitEngine(&second, 1);
10660 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10661 UpdateLogos(FALSE); // leave display to ModeHiglight()
10667 { // performs game initialization that does not invoke engines, and then tries to start the game
10668 int res, firstWhite, swapColors = 0;
10669 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10670 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
10672 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10673 if(strcmp(buf, currentDebugFile)) { // name has changed
10674 FILE *f = fopen(buf, "w");
10675 if(f) { // if opening the new file failed, just keep using the old one
10676 ASSIGN(currentDebugFile, buf);
10680 if(appData.serverFileName) {
10681 if(serverFP) fclose(serverFP);
10682 serverFP = fopen(appData.serverFileName, "w");
10683 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10684 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10688 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10689 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10690 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10691 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10692 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10693 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10694 Reset(FALSE, first.pr != NoProc);
10695 res = LoadGameOrPosition(matchGame); // setup game
10696 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10697 if(!res) return; // abort when bad game/pos file
10698 TwoMachinesEvent();
10702 UserAdjudicationEvent (int result)
10704 ChessMove gameResult = GameIsDrawn;
10707 gameResult = WhiteWins;
10709 else if( result < 0 ) {
10710 gameResult = BlackWins;
10713 if( gameMode == TwoMachinesPlay ) {
10714 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10719 // [HGM] save: calculate checksum of game to make games easily identifiable
10721 StringCheckSum (char *s)
10724 if(s==NULL) return 0;
10725 while(*s) i = i*259 + *s++;
10733 for(i=backwardMostMove; i<forwardMostMove; i++) {
10734 sum += pvInfoList[i].depth;
10735 sum += StringCheckSum(parseList[i]);
10736 sum += StringCheckSum(commentList[i]);
10739 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10740 return sum + StringCheckSum(commentList[i]);
10741 } // end of save patch
10744 GameEnds (ChessMove result, char *resultDetails, int whosays)
10746 GameMode nextGameMode;
10748 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10750 if(endingGame) return; /* [HGM] crash: forbid recursion */
10752 if(twoBoards) { // [HGM] dual: switch back to one board
10753 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10754 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10756 if (appData.debugMode) {
10757 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10758 result, resultDetails ? resultDetails : "(null)", whosays);
10761 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10763 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10765 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10766 /* If we are playing on ICS, the server decides when the
10767 game is over, but the engine can offer to draw, claim
10771 if (appData.zippyPlay && first.initDone) {
10772 if (result == GameIsDrawn) {
10773 /* In case draw still needs to be claimed */
10774 SendToICS(ics_prefix);
10775 SendToICS("draw\n");
10776 } else if (StrCaseStr(resultDetails, "resign")) {
10777 SendToICS(ics_prefix);
10778 SendToICS("resign\n");
10782 endingGame = 0; /* [HGM] crash */
10786 /* If we're loading the game from a file, stop */
10787 if (whosays == GE_FILE) {
10788 (void) StopLoadGameTimer();
10792 /* Cancel draw offers */
10793 first.offeredDraw = second.offeredDraw = 0;
10795 /* If this is an ICS game, only ICS can really say it's done;
10796 if not, anyone can. */
10797 isIcsGame = (gameMode == IcsPlayingWhite ||
10798 gameMode == IcsPlayingBlack ||
10799 gameMode == IcsObserving ||
10800 gameMode == IcsExamining);
10802 if (!isIcsGame || whosays == GE_ICS) {
10803 /* OK -- not an ICS game, or ICS said it was done */
10805 if (!isIcsGame && !appData.noChessProgram)
10806 SetUserThinkingEnables();
10808 /* [HGM] if a machine claims the game end we verify this claim */
10809 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10810 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10812 ChessMove trueResult = (ChessMove) -1;
10814 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10815 first.twoMachinesColor[0] :
10816 second.twoMachinesColor[0] ;
10818 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10819 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10820 /* [HGM] verify: engine mate claims accepted if they were flagged */
10821 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10823 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10824 /* [HGM] verify: engine mate claims accepted if they were flagged */
10825 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10827 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10828 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10831 // now verify win claims, but not in drop games, as we don't understand those yet
10832 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10833 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10834 (result == WhiteWins && claimer == 'w' ||
10835 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10836 if (appData.debugMode) {
10837 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10838 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10840 if(result != trueResult) {
10841 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10842 result = claimer == 'w' ? BlackWins : WhiteWins;
10843 resultDetails = buf;
10846 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10847 && (forwardMostMove <= backwardMostMove ||
10848 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10849 (claimer=='b')==(forwardMostMove&1))
10851 /* [HGM] verify: draws that were not flagged are false claims */
10852 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10853 result = claimer == 'w' ? BlackWins : WhiteWins;
10854 resultDetails = buf;
10856 /* (Claiming a loss is accepted no questions asked!) */
10857 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10858 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10859 result = GameUnfinished;
10860 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10862 /* [HGM] bare: don't allow bare King to win */
10863 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10864 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10865 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10866 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10867 && result != GameIsDrawn)
10868 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10869 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10870 int p = (signed char)boards[forwardMostMove][i][j] - color;
10871 if(p >= 0 && p <= (int)WhiteKing) k++;
10873 if (appData.debugMode) {
10874 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10875 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10878 result = GameIsDrawn;
10879 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10880 resultDetails = buf;
10886 if(serverMoves != NULL && !loadFlag) { char c = '=';
10887 if(result==WhiteWins) c = '+';
10888 if(result==BlackWins) c = '-';
10889 if(resultDetails != NULL)
10890 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10892 if (resultDetails != NULL) {
10893 gameInfo.result = result;
10894 gameInfo.resultDetails = StrSave(resultDetails);
10896 /* display last move only if game was not loaded from file */
10897 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10898 DisplayMove(currentMove - 1);
10900 if (forwardMostMove != 0) {
10901 if (gameMode != PlayFromGameFile && gameMode != EditGame
10902 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10904 if (*appData.saveGameFile != NULLCHAR) {
10905 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10906 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10908 SaveGameToFile(appData.saveGameFile, TRUE);
10909 } else if (appData.autoSaveGames) {
10910 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10912 if (*appData.savePositionFile != NULLCHAR) {
10913 SavePositionToFile(appData.savePositionFile);
10915 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10919 /* Tell program how game ended in case it is learning */
10920 /* [HGM] Moved this to after saving the PGN, just in case */
10921 /* engine died and we got here through time loss. In that */
10922 /* case we will get a fatal error writing the pipe, which */
10923 /* would otherwise lose us the PGN. */
10924 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10925 /* output during GameEnds should never be fatal anymore */
10926 if (gameMode == MachinePlaysWhite ||
10927 gameMode == MachinePlaysBlack ||
10928 gameMode == TwoMachinesPlay ||
10929 gameMode == IcsPlayingWhite ||
10930 gameMode == IcsPlayingBlack ||
10931 gameMode == BeginningOfGame) {
10933 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10935 if (first.pr != NoProc) {
10936 SendToProgram(buf, &first);
10938 if (second.pr != NoProc &&
10939 gameMode == TwoMachinesPlay) {
10940 SendToProgram(buf, &second);
10945 if (appData.icsActive) {
10946 if (appData.quietPlay &&
10947 (gameMode == IcsPlayingWhite ||
10948 gameMode == IcsPlayingBlack)) {
10949 SendToICS(ics_prefix);
10950 SendToICS("set shout 1\n");
10952 nextGameMode = IcsIdle;
10953 ics_user_moved = FALSE;
10954 /* clean up premove. It's ugly when the game has ended and the
10955 * premove highlights are still on the board.
10958 gotPremove = FALSE;
10959 ClearPremoveHighlights();
10960 DrawPosition(FALSE, boards[currentMove]);
10962 if (whosays == GE_ICS) {
10965 if (gameMode == IcsPlayingWhite)
10967 else if(gameMode == IcsPlayingBlack)
10968 PlayIcsLossSound();
10971 if (gameMode == IcsPlayingBlack)
10973 else if(gameMode == IcsPlayingWhite)
10974 PlayIcsLossSound();
10977 PlayIcsDrawSound();
10980 PlayIcsUnfinishedSound();
10983 if(appData.quitNext) { ExitEvent(0); return; }
10984 } else if (gameMode == EditGame ||
10985 gameMode == PlayFromGameFile ||
10986 gameMode == AnalyzeMode ||
10987 gameMode == AnalyzeFile) {
10988 nextGameMode = gameMode;
10990 nextGameMode = EndOfGame;
10995 nextGameMode = gameMode;
10998 if (appData.noChessProgram) {
10999 gameMode = nextGameMode;
11001 endingGame = 0; /* [HGM] crash */
11006 /* Put first chess program into idle state */
11007 if (first.pr != NoProc &&
11008 (gameMode == MachinePlaysWhite ||
11009 gameMode == MachinePlaysBlack ||
11010 gameMode == TwoMachinesPlay ||
11011 gameMode == IcsPlayingWhite ||
11012 gameMode == IcsPlayingBlack ||
11013 gameMode == BeginningOfGame)) {
11014 SendToProgram("force\n", &first);
11015 if (first.usePing) {
11017 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11018 SendToProgram(buf, &first);
11021 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11022 /* Kill off first chess program */
11023 if (first.isr != NULL)
11024 RemoveInputSource(first.isr);
11027 if (first.pr != NoProc) {
11029 DoSleep( appData.delayBeforeQuit );
11030 SendToProgram("quit\n", &first);
11031 DoSleep( appData.delayAfterQuit );
11032 DestroyChildProcess(first.pr, first.useSigterm);
11033 first.reload = TRUE;
11037 if (second.reuse) {
11038 /* Put second chess program into idle state */
11039 if (second.pr != NoProc &&
11040 gameMode == TwoMachinesPlay) {
11041 SendToProgram("force\n", &second);
11042 if (second.usePing) {
11044 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11045 SendToProgram(buf, &second);
11048 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11049 /* Kill off second chess program */
11050 if (second.isr != NULL)
11051 RemoveInputSource(second.isr);
11054 if (second.pr != NoProc) {
11055 DoSleep( appData.delayBeforeQuit );
11056 SendToProgram("quit\n", &second);
11057 DoSleep( appData.delayAfterQuit );
11058 DestroyChildProcess(second.pr, second.useSigterm);
11059 second.reload = TRUE;
11061 second.pr = NoProc;
11064 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11065 char resChar = '=';
11069 if (first.twoMachinesColor[0] == 'w') {
11072 second.matchWins++;
11077 if (first.twoMachinesColor[0] == 'b') {
11080 second.matchWins++;
11083 case GameUnfinished:
11089 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11090 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11091 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11092 ReserveGame(nextGame, resChar); // sets nextGame
11093 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11094 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11095 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11097 if (nextGame <= appData.matchGames && !abortMatch) {
11098 gameMode = nextGameMode;
11099 matchGame = nextGame; // this will be overruled in tourney mode!
11100 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11101 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11102 endingGame = 0; /* [HGM] crash */
11105 gameMode = nextGameMode;
11106 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11107 first.tidy, second.tidy,
11108 first.matchWins, second.matchWins,
11109 appData.matchGames - (first.matchWins + second.matchWins));
11110 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11111 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11112 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11113 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11114 first.twoMachinesColor = "black\n";
11115 second.twoMachinesColor = "white\n";
11117 first.twoMachinesColor = "white\n";
11118 second.twoMachinesColor = "black\n";
11122 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11123 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11125 gameMode = nextGameMode;
11127 endingGame = 0; /* [HGM] crash */
11128 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11129 if(matchMode == TRUE) { // match through command line: exit with or without popup
11131 ToNrEvent(forwardMostMove);
11132 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11134 } else DisplayFatalError(buf, 0, 0);
11135 } else { // match through menu; just stop, with or without popup
11136 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11139 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11140 } else DisplayNote(buf);
11142 if(ranking) free(ranking);
11146 /* Assumes program was just initialized (initString sent).
11147 Leaves program in force mode. */
11149 FeedMovesToProgram (ChessProgramState *cps, int upto)
11153 if (appData.debugMode)
11154 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11155 startedFromSetupPosition ? "position and " : "",
11156 backwardMostMove, upto, cps->which);
11157 if(currentlyInitializedVariant != gameInfo.variant) {
11159 // [HGM] variantswitch: make engine aware of new variant
11160 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11161 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11162 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11163 SendToProgram(buf, cps);
11164 currentlyInitializedVariant = gameInfo.variant;
11166 SendToProgram("force\n", cps);
11167 if (startedFromSetupPosition) {
11168 SendBoard(cps, backwardMostMove);
11169 if (appData.debugMode) {
11170 fprintf(debugFP, "feedMoves\n");
11173 for (i = backwardMostMove; i < upto; i++) {
11174 SendMoveToProgram(i, cps);
11180 ResurrectChessProgram ()
11182 /* The chess program may have exited.
11183 If so, restart it and feed it all the moves made so far. */
11184 static int doInit = 0;
11186 if (appData.noChessProgram) return 1;
11188 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11189 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11190 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11191 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11193 if (first.pr != NoProc) return 1;
11194 StartChessProgram(&first);
11196 InitChessProgram(&first, FALSE);
11197 FeedMovesToProgram(&first, currentMove);
11199 if (!first.sendTime) {
11200 /* can't tell gnuchess what its clock should read,
11201 so we bow to its notion. */
11203 timeRemaining[0][currentMove] = whiteTimeRemaining;
11204 timeRemaining[1][currentMove] = blackTimeRemaining;
11207 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11208 appData.icsEngineAnalyze) && first.analysisSupport) {
11209 SendToProgram("analyze\n", &first);
11210 first.analyzing = TRUE;
11216 * Button procedures
11219 Reset (int redraw, int init)
11223 if (appData.debugMode) {
11224 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11225 redraw, init, gameMode);
11227 CleanupTail(); // [HGM] vari: delete any stored variations
11228 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11229 pausing = pauseExamInvalid = FALSE;
11230 startedFromSetupPosition = blackPlaysFirst = FALSE;
11232 whiteFlag = blackFlag = FALSE;
11233 userOfferedDraw = FALSE;
11234 hintRequested = bookRequested = FALSE;
11235 first.maybeThinking = FALSE;
11236 second.maybeThinking = FALSE;
11237 first.bookSuspend = FALSE; // [HGM] book
11238 second.bookSuspend = FALSE;
11239 thinkOutput[0] = NULLCHAR;
11240 lastHint[0] = NULLCHAR;
11241 ClearGameInfo(&gameInfo);
11242 gameInfo.variant = StringToVariant(appData.variant);
11243 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11244 ics_user_moved = ics_clock_paused = FALSE;
11245 ics_getting_history = H_FALSE;
11247 white_holding[0] = black_holding[0] = NULLCHAR;
11248 ClearProgramStats();
11249 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11253 flipView = appData.flipView;
11254 ClearPremoveHighlights();
11255 gotPremove = FALSE;
11256 alarmSounded = FALSE;
11258 GameEnds(EndOfFile, NULL, GE_PLAYER);
11259 if(appData.serverMovesName != NULL) {
11260 /* [HGM] prepare to make moves file for broadcasting */
11261 clock_t t = clock();
11262 if(serverMoves != NULL) fclose(serverMoves);
11263 serverMoves = fopen(appData.serverMovesName, "r");
11264 if(serverMoves != NULL) {
11265 fclose(serverMoves);
11266 /* delay 15 sec before overwriting, so all clients can see end */
11267 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11269 serverMoves = fopen(appData.serverMovesName, "w");
11273 gameMode = BeginningOfGame;
11275 if(appData.icsActive) gameInfo.variant = VariantNormal;
11276 currentMove = forwardMostMove = backwardMostMove = 0;
11277 MarkTargetSquares(1);
11278 InitPosition(redraw);
11279 for (i = 0; i < MAX_MOVES; i++) {
11280 if (commentList[i] != NULL) {
11281 free(commentList[i]);
11282 commentList[i] = NULL;
11286 timeRemaining[0][0] = whiteTimeRemaining;
11287 timeRemaining[1][0] = blackTimeRemaining;
11289 if (first.pr == NoProc) {
11290 StartChessProgram(&first);
11293 InitChessProgram(&first, startedFromSetupPosition);
11296 DisplayMessage("", "");
11297 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11298 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11299 ClearMap(); // [HGM] exclude: invalidate map
11303 AutoPlayGameLoop ()
11306 if (!AutoPlayOneMove())
11308 if (matchMode || appData.timeDelay == 0)
11310 if (appData.timeDelay < 0)
11312 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11320 ReloadGame(1); // next game
11326 int fromX, fromY, toX, toY;
11328 if (appData.debugMode) {
11329 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11332 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11335 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11336 pvInfoList[currentMove].depth = programStats.depth;
11337 pvInfoList[currentMove].score = programStats.score;
11338 pvInfoList[currentMove].time = 0;
11339 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11340 else { // append analysis of final position as comment
11342 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11343 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11345 programStats.depth = 0;
11348 if (currentMove >= forwardMostMove) {
11349 if(gameMode == AnalyzeFile) {
11350 if(appData.loadGameIndex == -1) {
11351 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11352 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11354 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11357 // gameMode = EndOfGame;
11358 // ModeHighlight();
11360 /* [AS] Clear current move marker at the end of a game */
11361 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11366 toX = moveList[currentMove][2] - AAA;
11367 toY = moveList[currentMove][3] - ONE;
11369 if (moveList[currentMove][1] == '@') {
11370 if (appData.highlightLastMove) {
11371 SetHighlights(-1, -1, toX, toY);
11374 fromX = moveList[currentMove][0] - AAA;
11375 fromY = moveList[currentMove][1] - ONE;
11377 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11379 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11381 if (appData.highlightLastMove) {
11382 SetHighlights(fromX, fromY, toX, toY);
11385 DisplayMove(currentMove);
11386 SendMoveToProgram(currentMove++, &first);
11387 DisplayBothClocks();
11388 DrawPosition(FALSE, boards[currentMove]);
11389 // [HGM] PV info: always display, routine tests if empty
11390 DisplayComment(currentMove - 1, commentList[currentMove]);
11396 LoadGameOneMove (ChessMove readAhead)
11398 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11399 char promoChar = NULLCHAR;
11400 ChessMove moveType;
11401 char move[MSG_SIZ];
11404 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11405 gameMode != AnalyzeMode && gameMode != Training) {
11410 yyboardindex = forwardMostMove;
11411 if (readAhead != EndOfFile) {
11412 moveType = readAhead;
11414 if (gameFileFP == NULL)
11416 moveType = (ChessMove) Myylex();
11420 switch (moveType) {
11422 if (appData.debugMode)
11423 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11426 /* append the comment but don't display it */
11427 AppendComment(currentMove, p, FALSE);
11430 case WhiteCapturesEnPassant:
11431 case BlackCapturesEnPassant:
11432 case WhitePromotion:
11433 case BlackPromotion:
11434 case WhiteNonPromotion:
11435 case BlackNonPromotion:
11437 case WhiteKingSideCastle:
11438 case WhiteQueenSideCastle:
11439 case BlackKingSideCastle:
11440 case BlackQueenSideCastle:
11441 case WhiteKingSideCastleWild:
11442 case WhiteQueenSideCastleWild:
11443 case BlackKingSideCastleWild:
11444 case BlackQueenSideCastleWild:
11446 case WhiteHSideCastleFR:
11447 case WhiteASideCastleFR:
11448 case BlackHSideCastleFR:
11449 case BlackASideCastleFR:
11451 if (appData.debugMode)
11452 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11453 fromX = currentMoveString[0] - AAA;
11454 fromY = currentMoveString[1] - ONE;
11455 toX = currentMoveString[2] - AAA;
11456 toY = currentMoveString[3] - ONE;
11457 promoChar = currentMoveString[4];
11462 if (appData.debugMode)
11463 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11464 fromX = moveType == WhiteDrop ?
11465 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11466 (int) CharToPiece(ToLower(currentMoveString[0]));
11468 toX = currentMoveString[2] - AAA;
11469 toY = currentMoveString[3] - ONE;
11475 case GameUnfinished:
11476 if (appData.debugMode)
11477 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11478 p = strchr(yy_text, '{');
11479 if (p == NULL) p = strchr(yy_text, '(');
11482 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11484 q = strchr(p, *p == '{' ? '}' : ')');
11485 if (q != NULL) *q = NULLCHAR;
11488 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11489 GameEnds(moveType, p, GE_FILE);
11491 if (cmailMsgLoaded) {
11493 flipView = WhiteOnMove(currentMove);
11494 if (moveType == GameUnfinished) flipView = !flipView;
11495 if (appData.debugMode)
11496 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11501 if (appData.debugMode)
11502 fprintf(debugFP, "Parser hit end of file\n");
11503 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11509 if (WhiteOnMove(currentMove)) {
11510 GameEnds(BlackWins, "Black mates", GE_FILE);
11512 GameEnds(WhiteWins, "White mates", GE_FILE);
11516 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11522 case MoveNumberOne:
11523 if (lastLoadGameStart == GNUChessGame) {
11524 /* GNUChessGames have numbers, but they aren't move numbers */
11525 if (appData.debugMode)
11526 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11527 yy_text, (int) moveType);
11528 return LoadGameOneMove(EndOfFile); /* tail recursion */
11530 /* else fall thru */
11535 /* Reached start of next game in file */
11536 if (appData.debugMode)
11537 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11538 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11544 if (WhiteOnMove(currentMove)) {
11545 GameEnds(BlackWins, "Black mates", GE_FILE);
11547 GameEnds(WhiteWins, "White mates", GE_FILE);
11551 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11557 case PositionDiagram: /* should not happen; ignore */
11558 case ElapsedTime: /* ignore */
11559 case NAG: /* ignore */
11560 if (appData.debugMode)
11561 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11562 yy_text, (int) moveType);
11563 return LoadGameOneMove(EndOfFile); /* tail recursion */
11566 if (appData.testLegality) {
11567 if (appData.debugMode)
11568 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11569 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11570 (forwardMostMove / 2) + 1,
11571 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11572 DisplayError(move, 0);
11575 if (appData.debugMode)
11576 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11577 yy_text, currentMoveString);
11578 fromX = currentMoveString[0] - AAA;
11579 fromY = currentMoveString[1] - ONE;
11580 toX = currentMoveString[2] - AAA;
11581 toY = currentMoveString[3] - ONE;
11582 promoChar = currentMoveString[4];
11586 case AmbiguousMove:
11587 if (appData.debugMode)
11588 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11589 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11590 (forwardMostMove / 2) + 1,
11591 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11592 DisplayError(move, 0);
11597 case ImpossibleMove:
11598 if (appData.debugMode)
11599 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11600 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11601 (forwardMostMove / 2) + 1,
11602 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11603 DisplayError(move, 0);
11609 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11610 DrawPosition(FALSE, boards[currentMove]);
11611 DisplayBothClocks();
11612 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11613 DisplayComment(currentMove - 1, commentList[currentMove]);
11615 (void) StopLoadGameTimer();
11617 cmailOldMove = forwardMostMove;
11620 /* currentMoveString is set as a side-effect of yylex */
11622 thinkOutput[0] = NULLCHAR;
11623 MakeMove(fromX, fromY, toX, toY, promoChar);
11624 currentMove = forwardMostMove;
11629 /* Load the nth game from the given file */
11631 LoadGameFromFile (char *filename, int n, char *title, int useList)
11636 if (strcmp(filename, "-") == 0) {
11640 f = fopen(filename, "rb");
11642 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11643 DisplayError(buf, errno);
11647 if (fseek(f, 0, 0) == -1) {
11648 /* f is not seekable; probably a pipe */
11651 if (useList && n == 0) {
11652 int error = GameListBuild(f);
11654 DisplayError(_("Cannot build game list"), error);
11655 } else if (!ListEmpty(&gameList) &&
11656 ((ListGame *) gameList.tailPred)->number > 1) {
11657 GameListPopUp(f, title);
11664 return LoadGame(f, n, title, FALSE);
11669 MakeRegisteredMove ()
11671 int fromX, fromY, toX, toY;
11673 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11674 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11677 if (appData.debugMode)
11678 fprintf(debugFP, "Restoring %s for game %d\n",
11679 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11681 thinkOutput[0] = NULLCHAR;
11682 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11683 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11684 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11685 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11686 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11687 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11688 MakeMove(fromX, fromY, toX, toY, promoChar);
11689 ShowMove(fromX, fromY, toX, toY);
11691 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11698 if (WhiteOnMove(currentMove)) {
11699 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11701 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11706 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11713 if (WhiteOnMove(currentMove)) {
11714 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11716 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11721 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11732 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11734 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11738 if (gameNumber > nCmailGames) {
11739 DisplayError(_("No more games in this message"), 0);
11742 if (f == lastLoadGameFP) {
11743 int offset = gameNumber - lastLoadGameNumber;
11745 cmailMsg[0] = NULLCHAR;
11746 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11747 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11748 nCmailMovesRegistered--;
11750 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11751 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11752 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11755 if (! RegisterMove()) return FALSE;
11759 retVal = LoadGame(f, gameNumber, title, useList);
11761 /* Make move registered during previous look at this game, if any */
11762 MakeRegisteredMove();
11764 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11765 commentList[currentMove]
11766 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11767 DisplayComment(currentMove - 1, commentList[currentMove]);
11773 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11775 ReloadGame (int offset)
11777 int gameNumber = lastLoadGameNumber + offset;
11778 if (lastLoadGameFP == NULL) {
11779 DisplayError(_("No game has been loaded yet"), 0);
11782 if (gameNumber <= 0) {
11783 DisplayError(_("Can't back up any further"), 0);
11786 if (cmailMsgLoaded) {
11787 return CmailLoadGame(lastLoadGameFP, gameNumber,
11788 lastLoadGameTitle, lastLoadGameUseList);
11790 return LoadGame(lastLoadGameFP, gameNumber,
11791 lastLoadGameTitle, lastLoadGameUseList);
11795 int keys[EmptySquare+1];
11798 PositionMatches (Board b1, Board b2)
11801 switch(appData.searchMode) {
11802 case 1: return CompareWithRights(b1, b2);
11804 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11805 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11809 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11810 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11811 sum += keys[b1[r][f]] - keys[b2[r][f]];
11815 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11816 sum += keys[b1[r][f]] - keys[b2[r][f]];
11828 int pieceList[256], quickBoard[256];
11829 ChessSquare pieceType[256] = { EmptySquare };
11830 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11831 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11832 int soughtTotal, turn;
11833 Boolean epOK, flipSearch;
11836 unsigned char piece, to;
11839 #define DSIZE (250000)
11841 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11842 Move *moveDatabase = initialSpace;
11843 unsigned int movePtr, dataSize = DSIZE;
11846 MakePieceList (Board board, int *counts)
11848 int r, f, n=Q_PROMO, total=0;
11849 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11850 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11851 int sq = f + (r<<4);
11852 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11853 quickBoard[sq] = ++n;
11855 pieceType[n] = board[r][f];
11856 counts[board[r][f]]++;
11857 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11858 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11862 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11867 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11869 int sq = fromX + (fromY<<4);
11870 int piece = quickBoard[sq];
11871 quickBoard[sq] = 0;
11872 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11873 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11874 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11875 moveDatabase[movePtr++].piece = Q_WCASTL;
11876 quickBoard[sq] = piece;
11877 piece = quickBoard[from]; quickBoard[from] = 0;
11878 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11880 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11881 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11882 moveDatabase[movePtr++].piece = Q_BCASTL;
11883 quickBoard[sq] = piece;
11884 piece = quickBoard[from]; quickBoard[from] = 0;
11885 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11887 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11888 quickBoard[(fromY<<4)+toX] = 0;
11889 moveDatabase[movePtr].piece = Q_EP;
11890 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11891 moveDatabase[movePtr].to = sq;
11893 if(promoPiece != pieceType[piece]) {
11894 moveDatabase[movePtr++].piece = Q_PROMO;
11895 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11897 moveDatabase[movePtr].piece = piece;
11898 quickBoard[sq] = piece;
11903 PackGame (Board board)
11905 Move *newSpace = NULL;
11906 moveDatabase[movePtr].piece = 0; // terminate previous game
11907 if(movePtr > dataSize) {
11908 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11909 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11910 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11913 Move *p = moveDatabase, *q = newSpace;
11914 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11915 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11916 moveDatabase = newSpace;
11917 } else { // calloc failed, we must be out of memory. Too bad...
11918 dataSize = 0; // prevent calloc events for all subsequent games
11919 return 0; // and signal this one isn't cached
11923 MakePieceList(board, counts);
11928 QuickCompare (Board board, int *minCounts, int *maxCounts)
11929 { // compare according to search mode
11931 switch(appData.searchMode)
11933 case 1: // exact position match
11934 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11935 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11936 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11939 case 2: // can have extra material on empty squares
11940 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11941 if(board[r][f] == EmptySquare) continue;
11942 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11945 case 3: // material with exact Pawn structure
11946 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11947 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11948 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11949 } // fall through to material comparison
11950 case 4: // exact material
11951 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11953 case 6: // material range with given imbalance
11954 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11955 // fall through to range comparison
11956 case 5: // material range
11957 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11963 QuickScan (Board board, Move *move)
11964 { // reconstruct game,and compare all positions in it
11965 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11967 int piece = move->piece;
11968 int to = move->to, from = pieceList[piece];
11969 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11970 if(!piece) return -1;
11971 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11972 piece = (++move)->piece;
11973 from = pieceList[piece];
11974 counts[pieceType[piece]]--;
11975 pieceType[piece] = (ChessSquare) move->to;
11976 counts[move->to]++;
11977 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11978 counts[pieceType[quickBoard[to]]]--;
11979 quickBoard[to] = 0; total--;
11982 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11983 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11984 from = pieceList[piece]; // so this must be King
11985 quickBoard[from] = 0;
11986 pieceList[piece] = to;
11987 from = pieceList[(++move)->piece]; // for FRC this has to be done here
11988 quickBoard[from] = 0; // rook
11989 quickBoard[to] = piece;
11990 to = move->to; piece = move->piece;
11994 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11995 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11996 quickBoard[from] = 0;
11998 quickBoard[to] = piece;
11999 pieceList[piece] = to;
12001 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12002 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12003 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12004 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12006 static int lastCounts[EmptySquare+1];
12008 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12009 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12010 } else stretch = 0;
12011 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12020 flipSearch = FALSE;
12021 CopyBoard(soughtBoard, boards[currentMove]);
12022 soughtTotal = MakePieceList(soughtBoard, maxSought);
12023 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12024 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12025 CopyBoard(reverseBoard, boards[currentMove]);
12026 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12027 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12028 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12029 reverseBoard[r][f] = piece;
12031 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12032 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12033 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12034 || (boards[currentMove][CASTLING][2] == NoRights ||
12035 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12036 && (boards[currentMove][CASTLING][5] == NoRights ||
12037 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12040 CopyBoard(flipBoard, soughtBoard);
12041 CopyBoard(rotateBoard, reverseBoard);
12042 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12043 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12044 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12047 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12048 if(appData.searchMode >= 5) {
12049 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12050 MakePieceList(soughtBoard, minSought);
12051 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12053 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12054 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12057 GameInfo dummyInfo;
12058 static int creatingBook;
12061 GameContainsPosition (FILE *f, ListGame *lg)
12063 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12064 int fromX, fromY, toX, toY;
12066 static int initDone=FALSE;
12068 // weed out games based on numerical tag comparison
12069 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12070 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12071 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12072 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12074 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12077 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
12078 else CopyBoard(boards[scratch], initialPosition); // default start position
12081 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12082 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12085 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12086 fseek(f, lg->offset, 0);
12089 yyboardindex = scratch;
12090 quickFlag = plyNr+1;
12095 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12101 if(plyNr) return -1; // after we have seen moves, this is for new game
12104 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12105 case ImpossibleMove:
12106 case WhiteWins: // game ends here with these four
12109 case GameUnfinished:
12113 if(appData.testLegality) return -1;
12114 case WhiteCapturesEnPassant:
12115 case BlackCapturesEnPassant:
12116 case WhitePromotion:
12117 case BlackPromotion:
12118 case WhiteNonPromotion:
12119 case BlackNonPromotion:
12121 case WhiteKingSideCastle:
12122 case WhiteQueenSideCastle:
12123 case BlackKingSideCastle:
12124 case BlackQueenSideCastle:
12125 case WhiteKingSideCastleWild:
12126 case WhiteQueenSideCastleWild:
12127 case BlackKingSideCastleWild:
12128 case BlackQueenSideCastleWild:
12129 case WhiteHSideCastleFR:
12130 case WhiteASideCastleFR:
12131 case BlackHSideCastleFR:
12132 case BlackASideCastleFR:
12133 fromX = currentMoveString[0] - AAA;
12134 fromY = currentMoveString[1] - ONE;
12135 toX = currentMoveString[2] - AAA;
12136 toY = currentMoveString[3] - ONE;
12137 promoChar = currentMoveString[4];
12141 fromX = next == WhiteDrop ?
12142 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12143 (int) CharToPiece(ToLower(currentMoveString[0]));
12145 toX = currentMoveString[2] - AAA;
12146 toY = currentMoveString[3] - ONE;
12150 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12152 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12153 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12154 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12155 if(appData.findMirror) {
12156 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12157 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12162 /* Load the nth game from open file f */
12164 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12168 int gn = gameNumber;
12169 ListGame *lg = NULL;
12170 int numPGNTags = 0;
12172 GameMode oldGameMode;
12173 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12175 if (appData.debugMode)
12176 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12178 if (gameMode == Training )
12179 SetTrainingModeOff();
12181 oldGameMode = gameMode;
12182 if (gameMode != BeginningOfGame) {
12183 Reset(FALSE, TRUE);
12187 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12188 fclose(lastLoadGameFP);
12192 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12195 fseek(f, lg->offset, 0);
12196 GameListHighlight(gameNumber);
12197 pos = lg->position;
12201 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12202 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12204 DisplayError(_("Game number out of range"), 0);
12209 if (fseek(f, 0, 0) == -1) {
12210 if (f == lastLoadGameFP ?
12211 gameNumber == lastLoadGameNumber + 1 :
12215 DisplayError(_("Can't seek on game file"), 0);
12220 lastLoadGameFP = f;
12221 lastLoadGameNumber = gameNumber;
12222 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12223 lastLoadGameUseList = useList;
12227 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12228 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12229 lg->gameInfo.black);
12231 } else if (*title != NULLCHAR) {
12232 if (gameNumber > 1) {
12233 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12236 DisplayTitle(title);
12240 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12241 gameMode = PlayFromGameFile;
12245 currentMove = forwardMostMove = backwardMostMove = 0;
12246 CopyBoard(boards[0], initialPosition);
12250 * Skip the first gn-1 games in the file.
12251 * Also skip over anything that precedes an identifiable
12252 * start of game marker, to avoid being confused by
12253 * garbage at the start of the file. Currently
12254 * recognized start of game markers are the move number "1",
12255 * the pattern "gnuchess .* game", the pattern
12256 * "^[#;%] [^ ]* game file", and a PGN tag block.
12257 * A game that starts with one of the latter two patterns
12258 * will also have a move number 1, possibly
12259 * following a position diagram.
12260 * 5-4-02: Let's try being more lenient and allowing a game to
12261 * start with an unnumbered move. Does that break anything?
12263 cm = lastLoadGameStart = EndOfFile;
12265 yyboardindex = forwardMostMove;
12266 cm = (ChessMove) Myylex();
12269 if (cmailMsgLoaded) {
12270 nCmailGames = CMAIL_MAX_GAMES - gn;
12273 DisplayError(_("Game not found in file"), 0);
12280 lastLoadGameStart = cm;
12283 case MoveNumberOne:
12284 switch (lastLoadGameStart) {
12289 case MoveNumberOne:
12291 gn--; /* count this game */
12292 lastLoadGameStart = cm;
12301 switch (lastLoadGameStart) {
12304 case MoveNumberOne:
12306 gn--; /* count this game */
12307 lastLoadGameStart = cm;
12310 lastLoadGameStart = cm; /* game counted already */
12318 yyboardindex = forwardMostMove;
12319 cm = (ChessMove) Myylex();
12320 } while (cm == PGNTag || cm == Comment);
12327 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12328 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12329 != CMAIL_OLD_RESULT) {
12331 cmailResult[ CMAIL_MAX_GAMES
12332 - gn - 1] = CMAIL_OLD_RESULT;
12338 /* Only a NormalMove can be at the start of a game
12339 * without a position diagram. */
12340 if (lastLoadGameStart == EndOfFile ) {
12342 lastLoadGameStart = MoveNumberOne;
12351 if (appData.debugMode)
12352 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12354 if (cm == XBoardGame) {
12355 /* Skip any header junk before position diagram and/or move 1 */
12357 yyboardindex = forwardMostMove;
12358 cm = (ChessMove) Myylex();
12360 if (cm == EndOfFile ||
12361 cm == GNUChessGame || cm == XBoardGame) {
12362 /* Empty game; pretend end-of-file and handle later */
12367 if (cm == MoveNumberOne || cm == PositionDiagram ||
12368 cm == PGNTag || cm == Comment)
12371 } else if (cm == GNUChessGame) {
12372 if (gameInfo.event != NULL) {
12373 free(gameInfo.event);
12375 gameInfo.event = StrSave(yy_text);
12378 startedFromSetupPosition = FALSE;
12379 while (cm == PGNTag) {
12380 if (appData.debugMode)
12381 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12382 err = ParsePGNTag(yy_text, &gameInfo);
12383 if (!err) numPGNTags++;
12385 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12386 if(gameInfo.variant != oldVariant) {
12387 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12388 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12389 InitPosition(TRUE);
12390 oldVariant = gameInfo.variant;
12391 if (appData.debugMode)
12392 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12396 if (gameInfo.fen != NULL) {
12397 Board initial_position;
12398 startedFromSetupPosition = TRUE;
12399 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12401 DisplayError(_("Bad FEN position in file"), 0);
12404 CopyBoard(boards[0], initial_position);
12405 if (blackPlaysFirst) {
12406 currentMove = forwardMostMove = backwardMostMove = 1;
12407 CopyBoard(boards[1], initial_position);
12408 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12409 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12410 timeRemaining[0][1] = whiteTimeRemaining;
12411 timeRemaining[1][1] = blackTimeRemaining;
12412 if (commentList[0] != NULL) {
12413 commentList[1] = commentList[0];
12414 commentList[0] = NULL;
12417 currentMove = forwardMostMove = backwardMostMove = 0;
12419 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12421 initialRulePlies = FENrulePlies;
12422 for( i=0; i< nrCastlingRights; i++ )
12423 initialRights[i] = initial_position[CASTLING][i];
12425 yyboardindex = forwardMostMove;
12426 free(gameInfo.fen);
12427 gameInfo.fen = NULL;
12430 yyboardindex = forwardMostMove;
12431 cm = (ChessMove) Myylex();
12433 /* Handle comments interspersed among the tags */
12434 while (cm == Comment) {
12436 if (appData.debugMode)
12437 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12439 AppendComment(currentMove, p, FALSE);
12440 yyboardindex = forwardMostMove;
12441 cm = (ChessMove) Myylex();
12445 /* don't rely on existence of Event tag since if game was
12446 * pasted from clipboard the Event tag may not exist
12448 if (numPGNTags > 0){
12450 if (gameInfo.variant == VariantNormal) {
12451 VariantClass v = StringToVariant(gameInfo.event);
12452 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12453 if(v < VariantShogi) gameInfo.variant = v;
12456 if( appData.autoDisplayTags ) {
12457 tags = PGNTags(&gameInfo);
12458 TagsPopUp(tags, CmailMsg());
12463 /* Make something up, but don't display it now */
12468 if (cm == PositionDiagram) {
12471 Board initial_position;
12473 if (appData.debugMode)
12474 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12476 if (!startedFromSetupPosition) {
12478 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12479 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12490 initial_position[i][j++] = CharToPiece(*p);
12493 while (*p == ' ' || *p == '\t' ||
12494 *p == '\n' || *p == '\r') p++;
12496 if (strncmp(p, "black", strlen("black"))==0)
12497 blackPlaysFirst = TRUE;
12499 blackPlaysFirst = FALSE;
12500 startedFromSetupPosition = TRUE;
12502 CopyBoard(boards[0], initial_position);
12503 if (blackPlaysFirst) {
12504 currentMove = forwardMostMove = backwardMostMove = 1;
12505 CopyBoard(boards[1], initial_position);
12506 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12507 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12508 timeRemaining[0][1] = whiteTimeRemaining;
12509 timeRemaining[1][1] = blackTimeRemaining;
12510 if (commentList[0] != NULL) {
12511 commentList[1] = commentList[0];
12512 commentList[0] = NULL;
12515 currentMove = forwardMostMove = backwardMostMove = 0;
12518 yyboardindex = forwardMostMove;
12519 cm = (ChessMove) Myylex();
12522 if(!creatingBook) {
12523 if (first.pr == NoProc) {
12524 StartChessProgram(&first);
12526 InitChessProgram(&first, FALSE);
12527 SendToProgram("force\n", &first);
12528 if (startedFromSetupPosition) {
12529 SendBoard(&first, forwardMostMove);
12530 if (appData.debugMode) {
12531 fprintf(debugFP, "Load Game\n");
12533 DisplayBothClocks();
12537 /* [HGM] server: flag to write setup moves in broadcast file as one */
12538 loadFlag = appData.suppressLoadMoves;
12540 while (cm == Comment) {
12542 if (appData.debugMode)
12543 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12545 AppendComment(currentMove, p, FALSE);
12546 yyboardindex = forwardMostMove;
12547 cm = (ChessMove) Myylex();
12550 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12551 cm == WhiteWins || cm == BlackWins ||
12552 cm == GameIsDrawn || cm == GameUnfinished) {
12553 DisplayMessage("", _("No moves in game"));
12554 if (cmailMsgLoaded) {
12555 if (appData.debugMode)
12556 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12560 DrawPosition(FALSE, boards[currentMove]);
12561 DisplayBothClocks();
12562 gameMode = EditGame;
12569 // [HGM] PV info: routine tests if comment empty
12570 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12571 DisplayComment(currentMove - 1, commentList[currentMove]);
12573 if (!matchMode && appData.timeDelay != 0)
12574 DrawPosition(FALSE, boards[currentMove]);
12576 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12577 programStats.ok_to_send = 1;
12580 /* if the first token after the PGN tags is a move
12581 * and not move number 1, retrieve it from the parser
12583 if (cm != MoveNumberOne)
12584 LoadGameOneMove(cm);
12586 /* load the remaining moves from the file */
12587 while (LoadGameOneMove(EndOfFile)) {
12588 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12589 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12592 /* rewind to the start of the game */
12593 currentMove = backwardMostMove;
12595 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12597 if (oldGameMode == AnalyzeFile) {
12598 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12599 AnalyzeFileEvent();
12601 if (oldGameMode == AnalyzeMode) {
12602 AnalyzeFileEvent();
12605 if(creatingBook) return TRUE;
12606 if (!matchMode && pos > 0) {
12607 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12609 if (matchMode || appData.timeDelay == 0) {
12611 } else if (appData.timeDelay > 0) {
12612 AutoPlayGameLoop();
12615 if (appData.debugMode)
12616 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12618 loadFlag = 0; /* [HGM] true game starts */
12622 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12624 ReloadPosition (int offset)
12626 int positionNumber = lastLoadPositionNumber + offset;
12627 if (lastLoadPositionFP == NULL) {
12628 DisplayError(_("No position has been loaded yet"), 0);
12631 if (positionNumber <= 0) {
12632 DisplayError(_("Can't back up any further"), 0);
12635 return LoadPosition(lastLoadPositionFP, positionNumber,
12636 lastLoadPositionTitle);
12639 /* Load the nth position from the given file */
12641 LoadPositionFromFile (char *filename, int n, char *title)
12646 if (strcmp(filename, "-") == 0) {
12647 return LoadPosition(stdin, n, "stdin");
12649 f = fopen(filename, "rb");
12651 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12652 DisplayError(buf, errno);
12655 return LoadPosition(f, n, title);
12660 /* Load the nth position from the given open file, and close it */
12662 LoadPosition (FILE *f, int positionNumber, char *title)
12664 char *p, line[MSG_SIZ];
12665 Board initial_position;
12666 int i, j, fenMode, pn;
12668 if (gameMode == Training )
12669 SetTrainingModeOff();
12671 if (gameMode != BeginningOfGame) {
12672 Reset(FALSE, TRUE);
12674 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12675 fclose(lastLoadPositionFP);
12677 if (positionNumber == 0) positionNumber = 1;
12678 lastLoadPositionFP = f;
12679 lastLoadPositionNumber = positionNumber;
12680 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12681 if (first.pr == NoProc && !appData.noChessProgram) {
12682 StartChessProgram(&first);
12683 InitChessProgram(&first, FALSE);
12685 pn = positionNumber;
12686 if (positionNumber < 0) {
12687 /* Negative position number means to seek to that byte offset */
12688 if (fseek(f, -positionNumber, 0) == -1) {
12689 DisplayError(_("Can't seek on position file"), 0);
12694 if (fseek(f, 0, 0) == -1) {
12695 if (f == lastLoadPositionFP ?
12696 positionNumber == lastLoadPositionNumber + 1 :
12697 positionNumber == 1) {
12700 DisplayError(_("Can't seek on position file"), 0);
12705 /* See if this file is FEN or old-style xboard */
12706 if (fgets(line, MSG_SIZ, f) == NULL) {
12707 DisplayError(_("Position not found in file"), 0);
12710 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12711 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12714 if (fenMode || line[0] == '#') pn--;
12716 /* skip positions before number pn */
12717 if (fgets(line, MSG_SIZ, f) == NULL) {
12719 DisplayError(_("Position not found in file"), 0);
12722 if (fenMode || line[0] == '#') pn--;
12727 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12728 DisplayError(_("Bad FEN position in file"), 0);
12732 (void) fgets(line, MSG_SIZ, f);
12733 (void) fgets(line, MSG_SIZ, f);
12735 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12736 (void) fgets(line, MSG_SIZ, f);
12737 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12740 initial_position[i][j++] = CharToPiece(*p);
12744 blackPlaysFirst = FALSE;
12746 (void) fgets(line, MSG_SIZ, f);
12747 if (strncmp(line, "black", strlen("black"))==0)
12748 blackPlaysFirst = TRUE;
12751 startedFromSetupPosition = TRUE;
12753 CopyBoard(boards[0], initial_position);
12754 if (blackPlaysFirst) {
12755 currentMove = forwardMostMove = backwardMostMove = 1;
12756 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12757 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12758 CopyBoard(boards[1], initial_position);
12759 DisplayMessage("", _("Black to play"));
12761 currentMove = forwardMostMove = backwardMostMove = 0;
12762 DisplayMessage("", _("White to play"));
12764 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12765 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12766 SendToProgram("force\n", &first);
12767 SendBoard(&first, forwardMostMove);
12769 if (appData.debugMode) {
12771 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12772 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12773 fprintf(debugFP, "Load Position\n");
12776 if (positionNumber > 1) {
12777 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12778 DisplayTitle(line);
12780 DisplayTitle(title);
12782 gameMode = EditGame;
12785 timeRemaining[0][1] = whiteTimeRemaining;
12786 timeRemaining[1][1] = blackTimeRemaining;
12787 DrawPosition(FALSE, boards[currentMove]);
12794 CopyPlayerNameIntoFileName (char **dest, char *src)
12796 while (*src != NULLCHAR && *src != ',') {
12801 *(*dest)++ = *src++;
12807 DefaultFileName (char *ext)
12809 static char def[MSG_SIZ];
12812 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12814 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12816 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12818 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12825 /* Save the current game to the given file */
12827 SaveGameToFile (char *filename, int append)
12831 int result, i, t,tot=0;
12833 if (strcmp(filename, "-") == 0) {
12834 return SaveGame(stdout, 0, NULL);
12836 for(i=0; i<10; i++) { // upto 10 tries
12837 f = fopen(filename, append ? "a" : "w");
12838 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12839 if(f || errno != 13) break;
12840 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12844 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12845 DisplayError(buf, errno);
12848 safeStrCpy(buf, lastMsg, MSG_SIZ);
12849 DisplayMessage(_("Waiting for access to save file"), "");
12850 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12851 DisplayMessage(_("Saving game"), "");
12852 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12853 result = SaveGame(f, 0, NULL);
12854 DisplayMessage(buf, "");
12861 SavePart (char *str)
12863 static char buf[MSG_SIZ];
12866 p = strchr(str, ' ');
12867 if (p == NULL) return str;
12868 strncpy(buf, str, p - str);
12869 buf[p - str] = NULLCHAR;
12873 #define PGN_MAX_LINE 75
12875 #define PGN_SIDE_WHITE 0
12876 #define PGN_SIDE_BLACK 1
12879 FindFirstMoveOutOfBook (int side)
12883 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12884 int index = backwardMostMove;
12885 int has_book_hit = 0;
12887 if( (index % 2) != side ) {
12891 while( index < forwardMostMove ) {
12892 /* Check to see if engine is in book */
12893 int depth = pvInfoList[index].depth;
12894 int score = pvInfoList[index].score;
12900 else if( score == 0 && depth == 63 ) {
12901 in_book = 1; /* Zappa */
12903 else if( score == 2 && depth == 99 ) {
12904 in_book = 1; /* Abrok */
12907 has_book_hit += in_book;
12923 GetOutOfBookInfo (char * buf)
12927 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12929 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12930 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12934 if( oob[0] >= 0 || oob[1] >= 0 ) {
12935 for( i=0; i<2; i++ ) {
12939 if( i > 0 && oob[0] >= 0 ) {
12940 strcat( buf, " " );
12943 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12944 sprintf( buf+strlen(buf), "%s%.2f",
12945 pvInfoList[idx].score >= 0 ? "+" : "",
12946 pvInfoList[idx].score / 100.0 );
12952 /* Save game in PGN style and close the file */
12954 SaveGamePGN (FILE *f)
12956 int i, offset, linelen, newblock;
12959 int movelen, numlen, blank;
12960 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12962 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12964 PrintPGNTags(f, &gameInfo);
12966 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12968 if (backwardMostMove > 0 || startedFromSetupPosition) {
12969 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12970 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12971 fprintf(f, "\n{--------------\n");
12972 PrintPosition(f, backwardMostMove);
12973 fprintf(f, "--------------}\n");
12977 /* [AS] Out of book annotation */
12978 if( appData.saveOutOfBookInfo ) {
12981 GetOutOfBookInfo( buf );
12983 if( buf[0] != '\0' ) {
12984 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12991 i = backwardMostMove;
12995 while (i < forwardMostMove) {
12996 /* Print comments preceding this move */
12997 if (commentList[i] != NULL) {
12998 if (linelen > 0) fprintf(f, "\n");
12999 fprintf(f, "%s", commentList[i]);
13004 /* Format move number */
13006 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13009 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13011 numtext[0] = NULLCHAR;
13013 numlen = strlen(numtext);
13016 /* Print move number */
13017 blank = linelen > 0 && numlen > 0;
13018 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13027 fprintf(f, "%s", numtext);
13031 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13032 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13035 blank = linelen > 0 && movelen > 0;
13036 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13045 fprintf(f, "%s", move_buffer);
13046 linelen += movelen;
13048 /* [AS] Add PV info if present */
13049 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13050 /* [HGM] add time */
13051 char buf[MSG_SIZ]; int seconds;
13053 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13059 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13062 seconds = (seconds + 4)/10; // round to full seconds
13064 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13066 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13069 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13070 pvInfoList[i].score >= 0 ? "+" : "",
13071 pvInfoList[i].score / 100.0,
13072 pvInfoList[i].depth,
13075 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13077 /* Print score/depth */
13078 blank = linelen > 0 && movelen > 0;
13079 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13088 fprintf(f, "%s", move_buffer);
13089 linelen += movelen;
13095 /* Start a new line */
13096 if (linelen > 0) fprintf(f, "\n");
13098 /* Print comments after last move */
13099 if (commentList[i] != NULL) {
13100 fprintf(f, "%s\n", commentList[i]);
13104 if (gameInfo.resultDetails != NULL &&
13105 gameInfo.resultDetails[0] != NULLCHAR) {
13106 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
13107 PGNResult(gameInfo.result));
13109 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13113 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13117 /* Save game in old style and close the file */
13119 SaveGameOldStyle (FILE *f)
13124 tm = time((time_t *) NULL);
13126 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13129 if (backwardMostMove > 0 || startedFromSetupPosition) {
13130 fprintf(f, "\n[--------------\n");
13131 PrintPosition(f, backwardMostMove);
13132 fprintf(f, "--------------]\n");
13137 i = backwardMostMove;
13138 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13140 while (i < forwardMostMove) {
13141 if (commentList[i] != NULL) {
13142 fprintf(f, "[%s]\n", commentList[i]);
13145 if ((i % 2) == 1) {
13146 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13149 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13151 if (commentList[i] != NULL) {
13155 if (i >= forwardMostMove) {
13159 fprintf(f, "%s\n", parseList[i]);
13164 if (commentList[i] != NULL) {
13165 fprintf(f, "[%s]\n", commentList[i]);
13168 /* This isn't really the old style, but it's close enough */
13169 if (gameInfo.resultDetails != NULL &&
13170 gameInfo.resultDetails[0] != NULLCHAR) {
13171 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13172 gameInfo.resultDetails);
13174 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13181 /* Save the current game to open file f and close the file */
13183 SaveGame (FILE *f, int dummy, char *dummy2)
13185 if (gameMode == EditPosition) EditPositionDone(TRUE);
13186 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13187 if (appData.oldSaveStyle)
13188 return SaveGameOldStyle(f);
13190 return SaveGamePGN(f);
13193 /* Save the current position to the given file */
13195 SavePositionToFile (char *filename)
13200 if (strcmp(filename, "-") == 0) {
13201 return SavePosition(stdout, 0, NULL);
13203 f = fopen(filename, "a");
13205 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13206 DisplayError(buf, errno);
13209 safeStrCpy(buf, lastMsg, MSG_SIZ);
13210 DisplayMessage(_("Waiting for access to save file"), "");
13211 flock(fileno(f), LOCK_EX); // [HGM] lock
13212 DisplayMessage(_("Saving position"), "");
13213 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13214 SavePosition(f, 0, NULL);
13215 DisplayMessage(buf, "");
13221 /* Save the current position to the given open file and close the file */
13223 SavePosition (FILE *f, int dummy, char *dummy2)
13228 if (gameMode == EditPosition) EditPositionDone(TRUE);
13229 if (appData.oldSaveStyle) {
13230 tm = time((time_t *) NULL);
13232 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13234 fprintf(f, "[--------------\n");
13235 PrintPosition(f, currentMove);
13236 fprintf(f, "--------------]\n");
13238 fen = PositionToFEN(currentMove, NULL, 1);
13239 fprintf(f, "%s\n", fen);
13247 ReloadCmailMsgEvent (int unregister)
13250 static char *inFilename = NULL;
13251 static char *outFilename;
13253 struct stat inbuf, outbuf;
13256 /* Any registered moves are unregistered if unregister is set, */
13257 /* i.e. invoked by the signal handler */
13259 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13260 cmailMoveRegistered[i] = FALSE;
13261 if (cmailCommentList[i] != NULL) {
13262 free(cmailCommentList[i]);
13263 cmailCommentList[i] = NULL;
13266 nCmailMovesRegistered = 0;
13269 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13270 cmailResult[i] = CMAIL_NOT_RESULT;
13274 if (inFilename == NULL) {
13275 /* Because the filenames are static they only get malloced once */
13276 /* and they never get freed */
13277 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13278 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13280 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13281 sprintf(outFilename, "%s.out", appData.cmailGameName);
13284 status = stat(outFilename, &outbuf);
13286 cmailMailedMove = FALSE;
13288 status = stat(inFilename, &inbuf);
13289 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13292 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13293 counts the games, notes how each one terminated, etc.
13295 It would be nice to remove this kludge and instead gather all
13296 the information while building the game list. (And to keep it
13297 in the game list nodes instead of having a bunch of fixed-size
13298 parallel arrays.) Note this will require getting each game's
13299 termination from the PGN tags, as the game list builder does
13300 not process the game moves. --mann
13302 cmailMsgLoaded = TRUE;
13303 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13305 /* Load first game in the file or popup game menu */
13306 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13308 #endif /* !WIN32 */
13316 char string[MSG_SIZ];
13318 if ( cmailMailedMove
13319 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13320 return TRUE; /* Allow free viewing */
13323 /* Unregister move to ensure that we don't leave RegisterMove */
13324 /* with the move registered when the conditions for registering no */
13326 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13327 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13328 nCmailMovesRegistered --;
13330 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13332 free(cmailCommentList[lastLoadGameNumber - 1]);
13333 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13337 if (cmailOldMove == -1) {
13338 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13342 if (currentMove > cmailOldMove + 1) {
13343 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13347 if (currentMove < cmailOldMove) {
13348 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13352 if (forwardMostMove > currentMove) {
13353 /* Silently truncate extra moves */
13357 if ( (currentMove == cmailOldMove + 1)
13358 || ( (currentMove == cmailOldMove)
13359 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13360 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13361 if (gameInfo.result != GameUnfinished) {
13362 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13365 if (commentList[currentMove] != NULL) {
13366 cmailCommentList[lastLoadGameNumber - 1]
13367 = StrSave(commentList[currentMove]);
13369 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13371 if (appData.debugMode)
13372 fprintf(debugFP, "Saving %s for game %d\n",
13373 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13375 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13377 f = fopen(string, "w");
13378 if (appData.oldSaveStyle) {
13379 SaveGameOldStyle(f); /* also closes the file */
13381 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13382 f = fopen(string, "w");
13383 SavePosition(f, 0, NULL); /* also closes the file */
13385 fprintf(f, "{--------------\n");
13386 PrintPosition(f, currentMove);
13387 fprintf(f, "--------------}\n\n");
13389 SaveGame(f, 0, NULL); /* also closes the file*/
13392 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13393 nCmailMovesRegistered ++;
13394 } else if (nCmailGames == 1) {
13395 DisplayError(_("You have not made a move yet"), 0);
13406 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13407 FILE *commandOutput;
13408 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13409 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13415 if (! cmailMsgLoaded) {
13416 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13420 if (nCmailGames == nCmailResults) {
13421 DisplayError(_("No unfinished games"), 0);
13425 #if CMAIL_PROHIBIT_REMAIL
13426 if (cmailMailedMove) {
13427 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);
13428 DisplayError(msg, 0);
13433 if (! (cmailMailedMove || RegisterMove())) return;
13435 if ( cmailMailedMove
13436 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13437 snprintf(string, MSG_SIZ, partCommandString,
13438 appData.debugMode ? " -v" : "", appData.cmailGameName);
13439 commandOutput = popen(string, "r");
13441 if (commandOutput == NULL) {
13442 DisplayError(_("Failed to invoke cmail"), 0);
13444 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13445 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13447 if (nBuffers > 1) {
13448 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13449 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13450 nBytes = MSG_SIZ - 1;
13452 (void) memcpy(msg, buffer, nBytes);
13454 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13456 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13457 cmailMailedMove = TRUE; /* Prevent >1 moves */
13460 for (i = 0; i < nCmailGames; i ++) {
13461 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13466 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13468 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13470 appData.cmailGameName,
13472 LoadGameFromFile(buffer, 1, buffer, FALSE);
13473 cmailMsgLoaded = FALSE;
13477 DisplayInformation(msg);
13478 pclose(commandOutput);
13481 if ((*cmailMsg) != '\0') {
13482 DisplayInformation(cmailMsg);
13487 #endif /* !WIN32 */
13496 int prependComma = 0;
13498 char string[MSG_SIZ]; /* Space for game-list */
13501 if (!cmailMsgLoaded) return "";
13503 if (cmailMailedMove) {
13504 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13506 /* Create a list of games left */
13507 snprintf(string, MSG_SIZ, "[");
13508 for (i = 0; i < nCmailGames; i ++) {
13509 if (! ( cmailMoveRegistered[i]
13510 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13511 if (prependComma) {
13512 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13514 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13518 strcat(string, number);
13521 strcat(string, "]");
13523 if (nCmailMovesRegistered + nCmailResults == 0) {
13524 switch (nCmailGames) {
13526 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13530 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13534 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13539 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13541 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13546 if (nCmailResults == nCmailGames) {
13547 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13549 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13554 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13566 if (gameMode == Training)
13567 SetTrainingModeOff();
13570 cmailMsgLoaded = FALSE;
13571 if (appData.icsActive) {
13572 SendToICS(ics_prefix);
13573 SendToICS("refresh\n");
13578 ExitEvent (int status)
13582 /* Give up on clean exit */
13586 /* Keep trying for clean exit */
13590 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13592 if (telnetISR != NULL) {
13593 RemoveInputSource(telnetISR);
13595 if (icsPR != NoProc) {
13596 DestroyChildProcess(icsPR, TRUE);
13599 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13600 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13602 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13603 /* make sure this other one finishes before killing it! */
13604 if(endingGame) { int count = 0;
13605 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13606 while(endingGame && count++ < 10) DoSleep(1);
13607 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13610 /* Kill off chess programs */
13611 if (first.pr != NoProc) {
13614 DoSleep( appData.delayBeforeQuit );
13615 SendToProgram("quit\n", &first);
13616 DoSleep( appData.delayAfterQuit );
13617 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13619 if (second.pr != NoProc) {
13620 DoSleep( appData.delayBeforeQuit );
13621 SendToProgram("quit\n", &second);
13622 DoSleep( appData.delayAfterQuit );
13623 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13625 if (first.isr != NULL) {
13626 RemoveInputSource(first.isr);
13628 if (second.isr != NULL) {
13629 RemoveInputSource(second.isr);
13632 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13633 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13635 ShutDownFrontEnd();
13640 PauseEngine (ChessProgramState *cps)
13642 SendToProgram("pause\n", cps);
13647 UnPauseEngine (ChessProgramState *cps)
13649 SendToProgram("resume\n", cps);
13656 if (appData.debugMode)
13657 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13661 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13663 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13664 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13665 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13667 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13668 HandleMachineMove(stashedInputMove, stalledEngine);
13669 stalledEngine = NULL;
13672 if (gameMode == MachinePlaysWhite ||
13673 gameMode == TwoMachinesPlay ||
13674 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13675 if(first.pause) UnPauseEngine(&first);
13676 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13677 if(second.pause) UnPauseEngine(&second);
13678 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13681 DisplayBothClocks();
13683 if (gameMode == PlayFromGameFile) {
13684 if (appData.timeDelay >= 0)
13685 AutoPlayGameLoop();
13686 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13687 Reset(FALSE, TRUE);
13688 SendToICS(ics_prefix);
13689 SendToICS("refresh\n");
13690 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13691 ForwardInner(forwardMostMove);
13693 pauseExamInvalid = FALSE;
13695 switch (gameMode) {
13699 pauseExamForwardMostMove = forwardMostMove;
13700 pauseExamInvalid = FALSE;
13703 case IcsPlayingWhite:
13704 case IcsPlayingBlack:
13708 case PlayFromGameFile:
13709 (void) StopLoadGameTimer();
13713 case BeginningOfGame:
13714 if (appData.icsActive) return;
13715 /* else fall through */
13716 case MachinePlaysWhite:
13717 case MachinePlaysBlack:
13718 case TwoMachinesPlay:
13719 if (forwardMostMove == 0)
13720 return; /* don't pause if no one has moved */
13721 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13722 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13723 if(onMove->pause) { // thinking engine can be paused
13724 PauseEngine(onMove); // do it
13725 if(onMove->other->pause) // pondering opponent can always be paused immediately
13726 PauseEngine(onMove->other);
13728 SendToProgram("easy\n", onMove->other);
13730 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13731 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13733 PauseEngine(&first);
13735 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13736 } else { // human on move, pause pondering by either method
13738 PauseEngine(&first);
13739 else if(appData.ponderNextMove)
13740 SendToProgram("easy\n", &first);
13743 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13753 EditCommentEvent ()
13755 char title[MSG_SIZ];
13757 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13758 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13760 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13761 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13762 parseList[currentMove - 1]);
13765 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13772 char *tags = PGNTags(&gameInfo);
13774 EditTagsPopUp(tags, NULL);
13781 if(second.analyzing) {
13782 SendToProgram("exit\n", &second);
13783 second.analyzing = FALSE;
13785 if (second.pr == NoProc) StartChessProgram(&second);
13786 InitChessProgram(&second, FALSE);
13787 FeedMovesToProgram(&second, currentMove);
13789 SendToProgram("analyze\n", &second);
13790 second.analyzing = TRUE;
13794 /* Toggle ShowThinking */
13796 ToggleShowThinking()
13798 appData.showThinking = !appData.showThinking;
13799 ShowThinkingEvent();
13803 AnalyzeModeEvent ()
13807 if (!first.analysisSupport) {
13808 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13809 DisplayError(buf, 0);
13812 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13813 if (appData.icsActive) {
13814 if (gameMode != IcsObserving) {
13815 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13816 DisplayError(buf, 0);
13818 if (appData.icsEngineAnalyze) {
13819 if (appData.debugMode)
13820 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13826 /* if enable, user wants to disable icsEngineAnalyze */
13827 if (appData.icsEngineAnalyze) {
13832 appData.icsEngineAnalyze = TRUE;
13833 if (appData.debugMode)
13834 fprintf(debugFP, "ICS engine analyze starting... \n");
13837 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13838 if (appData.noChessProgram || gameMode == AnalyzeMode)
13841 if (gameMode != AnalyzeFile) {
13842 if (!appData.icsEngineAnalyze) {
13844 if (gameMode != EditGame) return 0;
13846 if (!appData.showThinking) ToggleShowThinking();
13847 ResurrectChessProgram();
13848 SendToProgram("analyze\n", &first);
13849 first.analyzing = TRUE;
13850 /*first.maybeThinking = TRUE;*/
13851 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13852 EngineOutputPopUp();
13854 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13859 StartAnalysisClock();
13860 GetTimeMark(&lastNodeCountTime);
13866 AnalyzeFileEvent ()
13868 if (appData.noChessProgram || gameMode == AnalyzeFile)
13871 if (!first.analysisSupport) {
13873 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13874 DisplayError(buf, 0);
13878 if (gameMode != AnalyzeMode) {
13879 keepInfo = 1; // mere annotating should not alter PGN tags
13882 if (gameMode != EditGame) return;
13883 if (!appData.showThinking) ToggleShowThinking();
13884 ResurrectChessProgram();
13885 SendToProgram("analyze\n", &first);
13886 first.analyzing = TRUE;
13887 /*first.maybeThinking = TRUE;*/
13888 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13889 EngineOutputPopUp();
13891 gameMode = AnalyzeFile;
13895 StartAnalysisClock();
13896 GetTimeMark(&lastNodeCountTime);
13898 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13899 AnalysisPeriodicEvent(1);
13903 MachineWhiteEvent ()
13906 char *bookHit = NULL;
13908 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13912 if (gameMode == PlayFromGameFile ||
13913 gameMode == TwoMachinesPlay ||
13914 gameMode == Training ||
13915 gameMode == AnalyzeMode ||
13916 gameMode == EndOfGame)
13919 if (gameMode == EditPosition)
13920 EditPositionDone(TRUE);
13922 if (!WhiteOnMove(currentMove)) {
13923 DisplayError(_("It is not White's turn"), 0);
13927 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13930 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13931 gameMode == AnalyzeFile)
13934 ResurrectChessProgram(); /* in case it isn't running */
13935 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13936 gameMode = MachinePlaysWhite;
13939 gameMode = MachinePlaysWhite;
13943 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13945 if (first.sendName) {
13946 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13947 SendToProgram(buf, &first);
13949 if (first.sendTime) {
13950 if (first.useColors) {
13951 SendToProgram("black\n", &first); /*gnu kludge*/
13953 SendTimeRemaining(&first, TRUE);
13955 if (first.useColors) {
13956 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13958 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13959 SetMachineThinkingEnables();
13960 first.maybeThinking = TRUE;
13964 if (appData.autoFlipView && !flipView) {
13965 flipView = !flipView;
13966 DrawPosition(FALSE, NULL);
13967 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13970 if(bookHit) { // [HGM] book: simulate book reply
13971 static char bookMove[MSG_SIZ]; // a bit generous?
13973 programStats.nodes = programStats.depth = programStats.time =
13974 programStats.score = programStats.got_only_move = 0;
13975 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13977 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13978 strcat(bookMove, bookHit);
13979 HandleMachineMove(bookMove, &first);
13984 MachineBlackEvent ()
13987 char *bookHit = NULL;
13989 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13993 if (gameMode == PlayFromGameFile ||
13994 gameMode == TwoMachinesPlay ||
13995 gameMode == Training ||
13996 gameMode == AnalyzeMode ||
13997 gameMode == EndOfGame)
14000 if (gameMode == EditPosition)
14001 EditPositionDone(TRUE);
14003 if (WhiteOnMove(currentMove)) {
14004 DisplayError(_("It is not Black's turn"), 0);
14008 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14011 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14012 gameMode == AnalyzeFile)
14015 ResurrectChessProgram(); /* in case it isn't running */
14016 gameMode = MachinePlaysBlack;
14020 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14022 if (first.sendName) {
14023 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14024 SendToProgram(buf, &first);
14026 if (first.sendTime) {
14027 if (first.useColors) {
14028 SendToProgram("white\n", &first); /*gnu kludge*/
14030 SendTimeRemaining(&first, FALSE);
14032 if (first.useColors) {
14033 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14035 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14036 SetMachineThinkingEnables();
14037 first.maybeThinking = TRUE;
14040 if (appData.autoFlipView && flipView) {
14041 flipView = !flipView;
14042 DrawPosition(FALSE, NULL);
14043 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14045 if(bookHit) { // [HGM] book: simulate book reply
14046 static char bookMove[MSG_SIZ]; // a bit generous?
14048 programStats.nodes = programStats.depth = programStats.time =
14049 programStats.score = programStats.got_only_move = 0;
14050 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14052 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14053 strcat(bookMove, bookHit);
14054 HandleMachineMove(bookMove, &first);
14060 DisplayTwoMachinesTitle ()
14063 if (appData.matchGames > 0) {
14064 if(appData.tourneyFile[0]) {
14065 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14066 gameInfo.white, _("vs."), gameInfo.black,
14067 nextGame+1, appData.matchGames+1,
14068 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14070 if (first.twoMachinesColor[0] == 'w') {
14071 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14072 gameInfo.white, _("vs."), gameInfo.black,
14073 first.matchWins, second.matchWins,
14074 matchGame - 1 - (first.matchWins + second.matchWins));
14076 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14077 gameInfo.white, _("vs."), gameInfo.black,
14078 second.matchWins, first.matchWins,
14079 matchGame - 1 - (first.matchWins + second.matchWins));
14082 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14088 SettingsMenuIfReady ()
14090 if (second.lastPing != second.lastPong) {
14091 DisplayMessage("", _("Waiting for second chess program"));
14092 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14096 DisplayMessage("", "");
14097 SettingsPopUp(&second);
14101 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14104 if (cps->pr == NoProc) {
14105 StartChessProgram(cps);
14106 if (cps->protocolVersion == 1) {
14108 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14110 /* kludge: allow timeout for initial "feature" command */
14111 if(retry != TwoMachinesEventIfReady) FreezeUI();
14112 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14113 DisplayMessage("", buf);
14114 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14122 TwoMachinesEvent P((void))
14126 ChessProgramState *onmove;
14127 char *bookHit = NULL;
14128 static int stalling = 0;
14132 if (appData.noChessProgram) return;
14134 switch (gameMode) {
14135 case TwoMachinesPlay:
14137 case MachinePlaysWhite:
14138 case MachinePlaysBlack:
14139 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14140 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14144 case BeginningOfGame:
14145 case PlayFromGameFile:
14148 if (gameMode != EditGame) return;
14151 EditPositionDone(TRUE);
14162 // forwardMostMove = currentMove;
14163 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14164 startingEngine = TRUE;
14166 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14168 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14169 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14170 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14173 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14175 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14176 startingEngine = FALSE;
14177 DisplayError("second engine does not play this", 0);
14182 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14183 SendToProgram("force\n", &second);
14185 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14188 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14189 if(appData.matchPause>10000 || appData.matchPause<10)
14190 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14191 wait = SubtractTimeMarks(&now, &pauseStart);
14192 if(wait < appData.matchPause) {
14193 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14196 // we are now committed to starting the game
14198 DisplayMessage("", "");
14199 if (startedFromSetupPosition) {
14200 SendBoard(&second, backwardMostMove);
14201 if (appData.debugMode) {
14202 fprintf(debugFP, "Two Machines\n");
14205 for (i = backwardMostMove; i < forwardMostMove; i++) {
14206 SendMoveToProgram(i, &second);
14209 gameMode = TwoMachinesPlay;
14210 pausing = startingEngine = FALSE;
14211 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14213 DisplayTwoMachinesTitle();
14215 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14220 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14221 SendToProgram(first.computerString, &first);
14222 if (first.sendName) {
14223 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14224 SendToProgram(buf, &first);
14226 SendToProgram(second.computerString, &second);
14227 if (second.sendName) {
14228 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14229 SendToProgram(buf, &second);
14233 if (!first.sendTime || !second.sendTime) {
14234 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14235 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14237 if (onmove->sendTime) {
14238 if (onmove->useColors) {
14239 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14241 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14243 if (onmove->useColors) {
14244 SendToProgram(onmove->twoMachinesColor, onmove);
14246 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14247 // SendToProgram("go\n", onmove);
14248 onmove->maybeThinking = TRUE;
14249 SetMachineThinkingEnables();
14253 if(bookHit) { // [HGM] book: simulate book reply
14254 static char bookMove[MSG_SIZ]; // a bit generous?
14256 programStats.nodes = programStats.depth = programStats.time =
14257 programStats.score = programStats.got_only_move = 0;
14258 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14260 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14261 strcat(bookMove, bookHit);
14262 savedMessage = bookMove; // args for deferred call
14263 savedState = onmove;
14264 ScheduleDelayedEvent(DeferredBookMove, 1);
14271 if (gameMode == Training) {
14272 SetTrainingModeOff();
14273 gameMode = PlayFromGameFile;
14274 DisplayMessage("", _("Training mode off"));
14276 gameMode = Training;
14277 animateTraining = appData.animate;
14279 /* make sure we are not already at the end of the game */
14280 if (currentMove < forwardMostMove) {
14281 SetTrainingModeOn();
14282 DisplayMessage("", _("Training mode on"));
14284 gameMode = PlayFromGameFile;
14285 DisplayError(_("Already at end of game"), 0);
14294 if (!appData.icsActive) return;
14295 switch (gameMode) {
14296 case IcsPlayingWhite:
14297 case IcsPlayingBlack:
14300 case BeginningOfGame:
14308 EditPositionDone(TRUE);
14321 gameMode = IcsIdle;
14331 switch (gameMode) {
14333 SetTrainingModeOff();
14335 case MachinePlaysWhite:
14336 case MachinePlaysBlack:
14337 case BeginningOfGame:
14338 SendToProgram("force\n", &first);
14339 SetUserThinkingEnables();
14341 case PlayFromGameFile:
14342 (void) StopLoadGameTimer();
14343 if (gameFileFP != NULL) {
14348 EditPositionDone(TRUE);
14353 SendToProgram("force\n", &first);
14355 case TwoMachinesPlay:
14356 GameEnds(EndOfFile, NULL, GE_PLAYER);
14357 ResurrectChessProgram();
14358 SetUserThinkingEnables();
14361 ResurrectChessProgram();
14363 case IcsPlayingBlack:
14364 case IcsPlayingWhite:
14365 DisplayError(_("Warning: You are still playing a game"), 0);
14368 DisplayError(_("Warning: You are still observing a game"), 0);
14371 DisplayError(_("Warning: You are still examining a game"), 0);
14382 first.offeredDraw = second.offeredDraw = 0;
14384 if (gameMode == PlayFromGameFile) {
14385 whiteTimeRemaining = timeRemaining[0][currentMove];
14386 blackTimeRemaining = timeRemaining[1][currentMove];
14390 if (gameMode == MachinePlaysWhite ||
14391 gameMode == MachinePlaysBlack ||
14392 gameMode == TwoMachinesPlay ||
14393 gameMode == EndOfGame) {
14394 i = forwardMostMove;
14395 while (i > currentMove) {
14396 SendToProgram("undo\n", &first);
14399 if(!adjustedClock) {
14400 whiteTimeRemaining = timeRemaining[0][currentMove];
14401 blackTimeRemaining = timeRemaining[1][currentMove];
14402 DisplayBothClocks();
14404 if (whiteFlag || blackFlag) {
14405 whiteFlag = blackFlag = 0;
14410 gameMode = EditGame;
14417 EditPositionEvent ()
14419 if (gameMode == EditPosition) {
14425 if (gameMode != EditGame) return;
14427 gameMode = EditPosition;
14430 if (currentMove > 0)
14431 CopyBoard(boards[0], boards[currentMove]);
14433 blackPlaysFirst = !WhiteOnMove(currentMove);
14435 currentMove = forwardMostMove = backwardMostMove = 0;
14436 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14438 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14444 /* [DM] icsEngineAnalyze - possible call from other functions */
14445 if (appData.icsEngineAnalyze) {
14446 appData.icsEngineAnalyze = FALSE;
14448 DisplayMessage("",_("Close ICS engine analyze..."));
14450 if (first.analysisSupport && first.analyzing) {
14451 SendToBoth("exit\n");
14452 first.analyzing = second.analyzing = FALSE;
14454 thinkOutput[0] = NULLCHAR;
14458 EditPositionDone (Boolean fakeRights)
14460 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14462 startedFromSetupPosition = TRUE;
14463 InitChessProgram(&first, FALSE);
14464 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14465 boards[0][EP_STATUS] = EP_NONE;
14466 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14467 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14468 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14469 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14470 } else boards[0][CASTLING][2] = NoRights;
14471 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14472 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14473 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14474 } else boards[0][CASTLING][5] = NoRights;
14475 if(gameInfo.variant == VariantSChess) {
14477 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14478 boards[0][VIRGIN][i] = 0;
14479 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14480 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14484 SendToProgram("force\n", &first);
14485 if (blackPlaysFirst) {
14486 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14487 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14488 currentMove = forwardMostMove = backwardMostMove = 1;
14489 CopyBoard(boards[1], boards[0]);
14491 currentMove = forwardMostMove = backwardMostMove = 0;
14493 SendBoard(&first, forwardMostMove);
14494 if (appData.debugMode) {
14495 fprintf(debugFP, "EditPosDone\n");
14498 DisplayMessage("", "");
14499 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14500 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14501 gameMode = EditGame;
14503 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14504 ClearHighlights(); /* [AS] */
14507 /* Pause for `ms' milliseconds */
14508 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14510 TimeDelay (long ms)
14517 } while (SubtractTimeMarks(&m2, &m1) < ms);
14520 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14522 SendMultiLineToICS (char *buf)
14524 char temp[MSG_SIZ+1], *p;
14531 strncpy(temp, buf, len);
14536 if (*p == '\n' || *p == '\r')
14541 strcat(temp, "\n");
14543 SendToPlayer(temp, strlen(temp));
14547 SetWhiteToPlayEvent ()
14549 if (gameMode == EditPosition) {
14550 blackPlaysFirst = FALSE;
14551 DisplayBothClocks(); /* works because currentMove is 0 */
14552 } else if (gameMode == IcsExamining) {
14553 SendToICS(ics_prefix);
14554 SendToICS("tomove white\n");
14559 SetBlackToPlayEvent ()
14561 if (gameMode == EditPosition) {
14562 blackPlaysFirst = TRUE;
14563 currentMove = 1; /* kludge */
14564 DisplayBothClocks();
14566 } else if (gameMode == IcsExamining) {
14567 SendToICS(ics_prefix);
14568 SendToICS("tomove black\n");
14573 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14576 ChessSquare piece = boards[0][y][x];
14578 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14580 switch (selection) {
14582 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14583 SendToICS(ics_prefix);
14584 SendToICS("bsetup clear\n");
14585 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14586 SendToICS(ics_prefix);
14587 SendToICS("clearboard\n");
14589 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14590 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14591 for (y = 0; y < BOARD_HEIGHT; y++) {
14592 if (gameMode == IcsExamining) {
14593 if (boards[currentMove][y][x] != EmptySquare) {
14594 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14599 boards[0][y][x] = p;
14604 if (gameMode == EditPosition) {
14605 DrawPosition(FALSE, boards[0]);
14610 SetWhiteToPlayEvent();
14614 SetBlackToPlayEvent();
14618 if (gameMode == IcsExamining) {
14619 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14620 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14623 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14624 if(x == BOARD_LEFT-2) {
14625 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14626 boards[0][y][1] = 0;
14628 if(x == BOARD_RGHT+1) {
14629 if(y >= gameInfo.holdingsSize) break;
14630 boards[0][y][BOARD_WIDTH-2] = 0;
14633 boards[0][y][x] = EmptySquare;
14634 DrawPosition(FALSE, boards[0]);
14639 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14640 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14641 selection = (ChessSquare) (PROMOTED piece);
14642 } else if(piece == EmptySquare) selection = WhiteSilver;
14643 else selection = (ChessSquare)((int)piece - 1);
14647 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14648 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14649 selection = (ChessSquare) (DEMOTED piece);
14650 } else if(piece == EmptySquare) selection = BlackSilver;
14651 else selection = (ChessSquare)((int)piece + 1);
14656 if(gameInfo.variant == VariantShatranj ||
14657 gameInfo.variant == VariantXiangqi ||
14658 gameInfo.variant == VariantCourier ||
14659 gameInfo.variant == VariantASEAN ||
14660 gameInfo.variant == VariantMakruk )
14661 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14666 if(gameInfo.variant == VariantXiangqi)
14667 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14668 if(gameInfo.variant == VariantKnightmate)
14669 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14672 if (gameMode == IcsExamining) {
14673 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14674 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14675 PieceToChar(selection), AAA + x, ONE + y);
14678 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14680 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14681 n = PieceToNumber(selection - BlackPawn);
14682 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14683 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14684 boards[0][BOARD_HEIGHT-1-n][1]++;
14686 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14687 n = PieceToNumber(selection);
14688 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14689 boards[0][n][BOARD_WIDTH-1] = selection;
14690 boards[0][n][BOARD_WIDTH-2]++;
14693 boards[0][y][x] = selection;
14694 DrawPosition(TRUE, boards[0]);
14696 fromX = fromY = -1;
14704 DropMenuEvent (ChessSquare selection, int x, int y)
14706 ChessMove moveType;
14708 switch (gameMode) {
14709 case IcsPlayingWhite:
14710 case MachinePlaysBlack:
14711 if (!WhiteOnMove(currentMove)) {
14712 DisplayMoveError(_("It is Black's turn"));
14715 moveType = WhiteDrop;
14717 case IcsPlayingBlack:
14718 case MachinePlaysWhite:
14719 if (WhiteOnMove(currentMove)) {
14720 DisplayMoveError(_("It is White's turn"));
14723 moveType = BlackDrop;
14726 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14732 if (moveType == BlackDrop && selection < BlackPawn) {
14733 selection = (ChessSquare) ((int) selection
14734 + (int) BlackPawn - (int) WhitePawn);
14736 if (boards[currentMove][y][x] != EmptySquare) {
14737 DisplayMoveError(_("That square is occupied"));
14741 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14747 /* Accept a pending offer of any kind from opponent */
14749 if (appData.icsActive) {
14750 SendToICS(ics_prefix);
14751 SendToICS("accept\n");
14752 } else if (cmailMsgLoaded) {
14753 if (currentMove == cmailOldMove &&
14754 commentList[cmailOldMove] != NULL &&
14755 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14756 "Black offers a draw" : "White offers a draw")) {
14758 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14759 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14761 DisplayError(_("There is no pending offer on this move"), 0);
14762 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14765 /* Not used for offers from chess program */
14772 /* Decline a pending offer of any kind from opponent */
14774 if (appData.icsActive) {
14775 SendToICS(ics_prefix);
14776 SendToICS("decline\n");
14777 } else if (cmailMsgLoaded) {
14778 if (currentMove == cmailOldMove &&
14779 commentList[cmailOldMove] != NULL &&
14780 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14781 "Black offers a draw" : "White offers a draw")) {
14783 AppendComment(cmailOldMove, "Draw declined", TRUE);
14784 DisplayComment(cmailOldMove - 1, "Draw declined");
14787 DisplayError(_("There is no pending offer on this move"), 0);
14790 /* Not used for offers from chess program */
14797 /* Issue ICS rematch command */
14798 if (appData.icsActive) {
14799 SendToICS(ics_prefix);
14800 SendToICS("rematch\n");
14807 /* Call your opponent's flag (claim a win on time) */
14808 if (appData.icsActive) {
14809 SendToICS(ics_prefix);
14810 SendToICS("flag\n");
14812 switch (gameMode) {
14815 case MachinePlaysWhite:
14818 GameEnds(GameIsDrawn, "Both players ran out of time",
14821 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14823 DisplayError(_("Your opponent is not out of time"), 0);
14826 case MachinePlaysBlack:
14829 GameEnds(GameIsDrawn, "Both players ran out of time",
14832 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14834 DisplayError(_("Your opponent is not out of time"), 0);
14842 ClockClick (int which)
14843 { // [HGM] code moved to back-end from winboard.c
14844 if(which) { // black clock
14845 if (gameMode == EditPosition || gameMode == IcsExamining) {
14846 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14847 SetBlackToPlayEvent();
14848 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14849 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14850 } else if (shiftKey) {
14851 AdjustClock(which, -1);
14852 } else if (gameMode == IcsPlayingWhite ||
14853 gameMode == MachinePlaysBlack) {
14856 } else { // white clock
14857 if (gameMode == EditPosition || gameMode == IcsExamining) {
14858 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14859 SetWhiteToPlayEvent();
14860 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14861 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14862 } else if (shiftKey) {
14863 AdjustClock(which, -1);
14864 } else if (gameMode == IcsPlayingBlack ||
14865 gameMode == MachinePlaysWhite) {
14874 /* Offer draw or accept pending draw offer from opponent */
14876 if (appData.icsActive) {
14877 /* Note: tournament rules require draw offers to be
14878 made after you make your move but before you punch
14879 your clock. Currently ICS doesn't let you do that;
14880 instead, you immediately punch your clock after making
14881 a move, but you can offer a draw at any time. */
14883 SendToICS(ics_prefix);
14884 SendToICS("draw\n");
14885 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14886 } else if (cmailMsgLoaded) {
14887 if (currentMove == cmailOldMove &&
14888 commentList[cmailOldMove] != NULL &&
14889 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14890 "Black offers a draw" : "White offers a draw")) {
14891 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14892 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14893 } else if (currentMove == cmailOldMove + 1) {
14894 char *offer = WhiteOnMove(cmailOldMove) ?
14895 "White offers a draw" : "Black offers a draw";
14896 AppendComment(currentMove, offer, TRUE);
14897 DisplayComment(currentMove - 1, offer);
14898 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14900 DisplayError(_("You must make your move before offering a draw"), 0);
14901 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14903 } else if (first.offeredDraw) {
14904 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14906 if (first.sendDrawOffers) {
14907 SendToProgram("draw\n", &first);
14908 userOfferedDraw = TRUE;
14916 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14918 if (appData.icsActive) {
14919 SendToICS(ics_prefix);
14920 SendToICS("adjourn\n");
14922 /* Currently GNU Chess doesn't offer or accept Adjourns */
14930 /* Offer Abort or accept pending Abort offer from opponent */
14932 if (appData.icsActive) {
14933 SendToICS(ics_prefix);
14934 SendToICS("abort\n");
14936 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14943 /* Resign. You can do this even if it's not your turn. */
14945 if (appData.icsActive) {
14946 SendToICS(ics_prefix);
14947 SendToICS("resign\n");
14949 switch (gameMode) {
14950 case MachinePlaysWhite:
14951 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14953 case MachinePlaysBlack:
14954 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14957 if (cmailMsgLoaded) {
14959 if (WhiteOnMove(cmailOldMove)) {
14960 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14962 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14964 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14975 StopObservingEvent ()
14977 /* Stop observing current games */
14978 SendToICS(ics_prefix);
14979 SendToICS("unobserve\n");
14983 StopExaminingEvent ()
14985 /* Stop observing current game */
14986 SendToICS(ics_prefix);
14987 SendToICS("unexamine\n");
14991 ForwardInner (int target)
14993 int limit; int oldSeekGraphUp = seekGraphUp;
14995 if (appData.debugMode)
14996 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14997 target, currentMove, forwardMostMove);
14999 if (gameMode == EditPosition)
15002 seekGraphUp = FALSE;
15003 MarkTargetSquares(1);
15005 if (gameMode == PlayFromGameFile && !pausing)
15008 if (gameMode == IcsExamining && pausing)
15009 limit = pauseExamForwardMostMove;
15011 limit = forwardMostMove;
15013 if (target > limit) target = limit;
15015 if (target > 0 && moveList[target - 1][0]) {
15016 int fromX, fromY, toX, toY;
15017 toX = moveList[target - 1][2] - AAA;
15018 toY = moveList[target - 1][3] - ONE;
15019 if (moveList[target - 1][1] == '@') {
15020 if (appData.highlightLastMove) {
15021 SetHighlights(-1, -1, toX, toY);
15024 fromX = moveList[target - 1][0] - AAA;
15025 fromY = moveList[target - 1][1] - ONE;
15026 if (target == currentMove + 1) {
15027 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15029 if (appData.highlightLastMove) {
15030 SetHighlights(fromX, fromY, toX, toY);
15034 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15035 gameMode == Training || gameMode == PlayFromGameFile ||
15036 gameMode == AnalyzeFile) {
15037 while (currentMove < target) {
15038 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15039 SendMoveToProgram(currentMove++, &first);
15042 currentMove = target;
15045 if (gameMode == EditGame || gameMode == EndOfGame) {
15046 whiteTimeRemaining = timeRemaining[0][currentMove];
15047 blackTimeRemaining = timeRemaining[1][currentMove];
15049 DisplayBothClocks();
15050 DisplayMove(currentMove - 1);
15051 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15052 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15053 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15054 DisplayComment(currentMove - 1, commentList[currentMove]);
15056 ClearMap(); // [HGM] exclude: invalidate map
15063 if (gameMode == IcsExamining && !pausing) {
15064 SendToICS(ics_prefix);
15065 SendToICS("forward\n");
15067 ForwardInner(currentMove + 1);
15074 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15075 /* to optimze, we temporarily turn off analysis mode while we feed
15076 * the remaining moves to the engine. Otherwise we get analysis output
15079 if (first.analysisSupport) {
15080 SendToProgram("exit\nforce\n", &first);
15081 first.analyzing = FALSE;
15085 if (gameMode == IcsExamining && !pausing) {
15086 SendToICS(ics_prefix);
15087 SendToICS("forward 999999\n");
15089 ForwardInner(forwardMostMove);
15092 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15093 /* we have fed all the moves, so reactivate analysis mode */
15094 SendToProgram("analyze\n", &first);
15095 first.analyzing = TRUE;
15096 /*first.maybeThinking = TRUE;*/
15097 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15102 BackwardInner (int target)
15104 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15106 if (appData.debugMode)
15107 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15108 target, currentMove, forwardMostMove);
15110 if (gameMode == EditPosition) return;
15111 seekGraphUp = FALSE;
15112 MarkTargetSquares(1);
15113 if (currentMove <= backwardMostMove) {
15115 DrawPosition(full_redraw, boards[currentMove]);
15118 if (gameMode == PlayFromGameFile && !pausing)
15121 if (moveList[target][0]) {
15122 int fromX, fromY, toX, toY;
15123 toX = moveList[target][2] - AAA;
15124 toY = moveList[target][3] - ONE;
15125 if (moveList[target][1] == '@') {
15126 if (appData.highlightLastMove) {
15127 SetHighlights(-1, -1, toX, toY);
15130 fromX = moveList[target][0] - AAA;
15131 fromY = moveList[target][1] - ONE;
15132 if (target == currentMove - 1) {
15133 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15135 if (appData.highlightLastMove) {
15136 SetHighlights(fromX, fromY, toX, toY);
15140 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15141 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15142 while (currentMove > target) {
15143 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15144 // null move cannot be undone. Reload program with move history before it.
15146 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15147 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15149 SendBoard(&first, i);
15150 if(second.analyzing) SendBoard(&second, i);
15151 for(currentMove=i; currentMove<target; currentMove++) {
15152 SendMoveToProgram(currentMove, &first);
15153 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15157 SendToBoth("undo\n");
15161 currentMove = target;
15164 if (gameMode == EditGame || gameMode == EndOfGame) {
15165 whiteTimeRemaining = timeRemaining[0][currentMove];
15166 blackTimeRemaining = timeRemaining[1][currentMove];
15168 DisplayBothClocks();
15169 DisplayMove(currentMove - 1);
15170 DrawPosition(full_redraw, boards[currentMove]);
15171 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15172 // [HGM] PV info: routine tests if comment empty
15173 DisplayComment(currentMove - 1, commentList[currentMove]);
15174 ClearMap(); // [HGM] exclude: invalidate map
15180 if (gameMode == IcsExamining && !pausing) {
15181 SendToICS(ics_prefix);
15182 SendToICS("backward\n");
15184 BackwardInner(currentMove - 1);
15191 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15192 /* to optimize, we temporarily turn off analysis mode while we undo
15193 * all the moves. Otherwise we get analysis output after each undo.
15195 if (first.analysisSupport) {
15196 SendToProgram("exit\nforce\n", &first);
15197 first.analyzing = FALSE;
15201 if (gameMode == IcsExamining && !pausing) {
15202 SendToICS(ics_prefix);
15203 SendToICS("backward 999999\n");
15205 BackwardInner(backwardMostMove);
15208 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15209 /* we have fed all the moves, so reactivate analysis mode */
15210 SendToProgram("analyze\n", &first);
15211 first.analyzing = TRUE;
15212 /*first.maybeThinking = TRUE;*/
15213 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15220 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15221 if (to >= forwardMostMove) to = forwardMostMove;
15222 if (to <= backwardMostMove) to = backwardMostMove;
15223 if (to < currentMove) {
15231 RevertEvent (Boolean annotate)
15233 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15236 if (gameMode != IcsExamining) {
15237 DisplayError(_("You are not examining a game"), 0);
15241 DisplayError(_("You can't revert while pausing"), 0);
15244 SendToICS(ics_prefix);
15245 SendToICS("revert\n");
15249 RetractMoveEvent ()
15251 switch (gameMode) {
15252 case MachinePlaysWhite:
15253 case MachinePlaysBlack:
15254 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15255 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15258 if (forwardMostMove < 2) return;
15259 currentMove = forwardMostMove = forwardMostMove - 2;
15260 whiteTimeRemaining = timeRemaining[0][currentMove];
15261 blackTimeRemaining = timeRemaining[1][currentMove];
15262 DisplayBothClocks();
15263 DisplayMove(currentMove - 1);
15264 ClearHighlights();/*!! could figure this out*/
15265 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15266 SendToProgram("remove\n", &first);
15267 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15270 case BeginningOfGame:
15274 case IcsPlayingWhite:
15275 case IcsPlayingBlack:
15276 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15277 SendToICS(ics_prefix);
15278 SendToICS("takeback 2\n");
15280 SendToICS(ics_prefix);
15281 SendToICS("takeback 1\n");
15290 ChessProgramState *cps;
15292 switch (gameMode) {
15293 case MachinePlaysWhite:
15294 if (!WhiteOnMove(forwardMostMove)) {
15295 DisplayError(_("It is your turn"), 0);
15300 case MachinePlaysBlack:
15301 if (WhiteOnMove(forwardMostMove)) {
15302 DisplayError(_("It is your turn"), 0);
15307 case TwoMachinesPlay:
15308 if (WhiteOnMove(forwardMostMove) ==
15309 (first.twoMachinesColor[0] == 'w')) {
15315 case BeginningOfGame:
15319 SendToProgram("?\n", cps);
15323 TruncateGameEvent ()
15326 if (gameMode != EditGame) return;
15333 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15334 if (forwardMostMove > currentMove) {
15335 if (gameInfo.resultDetails != NULL) {
15336 free(gameInfo.resultDetails);
15337 gameInfo.resultDetails = NULL;
15338 gameInfo.result = GameUnfinished;
15340 forwardMostMove = currentMove;
15341 HistorySet(parseList, backwardMostMove, forwardMostMove,
15349 if (appData.noChessProgram) return;
15350 switch (gameMode) {
15351 case MachinePlaysWhite:
15352 if (WhiteOnMove(forwardMostMove)) {
15353 DisplayError(_("Wait until your turn"), 0);
15357 case BeginningOfGame:
15358 case MachinePlaysBlack:
15359 if (!WhiteOnMove(forwardMostMove)) {
15360 DisplayError(_("Wait until your turn"), 0);
15365 DisplayError(_("No hint available"), 0);
15368 SendToProgram("hint\n", &first);
15369 hintRequested = TRUE;
15375 ListGame * lg = (ListGame *) gameList.head;
15378 static int secondTime = FALSE;
15380 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15381 DisplayError(_("Game list not loaded or empty"), 0);
15385 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15388 DisplayNote(_("Book file exists! Try again for overwrite."));
15392 creatingBook = TRUE;
15393 secondTime = FALSE;
15395 /* Get list size */
15396 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15397 LoadGame(f, nItem, "", TRUE);
15398 AddGameToBook(TRUE);
15399 lg = (ListGame *) lg->node.succ;
15402 creatingBook = FALSE;
15409 if (appData.noChessProgram) return;
15410 switch (gameMode) {
15411 case MachinePlaysWhite:
15412 if (WhiteOnMove(forwardMostMove)) {
15413 DisplayError(_("Wait until your turn"), 0);
15417 case BeginningOfGame:
15418 case MachinePlaysBlack:
15419 if (!WhiteOnMove(forwardMostMove)) {
15420 DisplayError(_("Wait until your turn"), 0);
15425 EditPositionDone(TRUE);
15427 case TwoMachinesPlay:
15432 SendToProgram("bk\n", &first);
15433 bookOutput[0] = NULLCHAR;
15434 bookRequested = TRUE;
15440 char *tags = PGNTags(&gameInfo);
15441 TagsPopUp(tags, CmailMsg());
15445 /* end button procedures */
15448 PrintPosition (FILE *fp, int move)
15452 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15453 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15454 char c = PieceToChar(boards[move][i][j]);
15455 fputc(c == 'x' ? '.' : c, fp);
15456 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15459 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15460 fprintf(fp, "white to play\n");
15462 fprintf(fp, "black to play\n");
15466 PrintOpponents (FILE *fp)
15468 if (gameInfo.white != NULL) {
15469 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15475 /* Find last component of program's own name, using some heuristics */
15477 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15480 int local = (strcmp(host, "localhost") == 0);
15481 while (!local && (p = strchr(prog, ';')) != NULL) {
15483 while (*p == ' ') p++;
15486 if (*prog == '"' || *prog == '\'') {
15487 q = strchr(prog + 1, *prog);
15489 q = strchr(prog, ' ');
15491 if (q == NULL) q = prog + strlen(prog);
15493 while (p >= prog && *p != '/' && *p != '\\') p--;
15495 if(p == prog && *p == '"') p++;
15497 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15498 memcpy(buf, p, q - p);
15499 buf[q - p] = NULLCHAR;
15507 TimeControlTagValue ()
15510 if (!appData.clockMode) {
15511 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15512 } else if (movesPerSession > 0) {
15513 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15514 } else if (timeIncrement == 0) {
15515 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15517 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15519 return StrSave(buf);
15525 /* This routine is used only for certain modes */
15526 VariantClass v = gameInfo.variant;
15527 ChessMove r = GameUnfinished;
15530 if(keepInfo) return;
15532 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15533 r = gameInfo.result;
15534 p = gameInfo.resultDetails;
15535 gameInfo.resultDetails = NULL;
15537 ClearGameInfo(&gameInfo);
15538 gameInfo.variant = v;
15540 switch (gameMode) {
15541 case MachinePlaysWhite:
15542 gameInfo.event = StrSave( appData.pgnEventHeader );
15543 gameInfo.site = StrSave(HostName());
15544 gameInfo.date = PGNDate();
15545 gameInfo.round = StrSave("-");
15546 gameInfo.white = StrSave(first.tidy);
15547 gameInfo.black = StrSave(UserName());
15548 gameInfo.timeControl = TimeControlTagValue();
15551 case MachinePlaysBlack:
15552 gameInfo.event = StrSave( appData.pgnEventHeader );
15553 gameInfo.site = StrSave(HostName());
15554 gameInfo.date = PGNDate();
15555 gameInfo.round = StrSave("-");
15556 gameInfo.white = StrSave(UserName());
15557 gameInfo.black = StrSave(first.tidy);
15558 gameInfo.timeControl = TimeControlTagValue();
15561 case TwoMachinesPlay:
15562 gameInfo.event = StrSave( appData.pgnEventHeader );
15563 gameInfo.site = StrSave(HostName());
15564 gameInfo.date = PGNDate();
15567 snprintf(buf, MSG_SIZ, "%d", roundNr);
15568 gameInfo.round = StrSave(buf);
15570 gameInfo.round = StrSave("-");
15572 if (first.twoMachinesColor[0] == 'w') {
15573 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15574 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15576 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15577 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15579 gameInfo.timeControl = TimeControlTagValue();
15583 gameInfo.event = StrSave("Edited game");
15584 gameInfo.site = StrSave(HostName());
15585 gameInfo.date = PGNDate();
15586 gameInfo.round = StrSave("-");
15587 gameInfo.white = StrSave("-");
15588 gameInfo.black = StrSave("-");
15589 gameInfo.result = r;
15590 gameInfo.resultDetails = p;
15594 gameInfo.event = StrSave("Edited position");
15595 gameInfo.site = StrSave(HostName());
15596 gameInfo.date = PGNDate();
15597 gameInfo.round = StrSave("-");
15598 gameInfo.white = StrSave("-");
15599 gameInfo.black = StrSave("-");
15602 case IcsPlayingWhite:
15603 case IcsPlayingBlack:
15608 case PlayFromGameFile:
15609 gameInfo.event = StrSave("Game from non-PGN file");
15610 gameInfo.site = StrSave(HostName());
15611 gameInfo.date = PGNDate();
15612 gameInfo.round = StrSave("-");
15613 gameInfo.white = StrSave("?");
15614 gameInfo.black = StrSave("?");
15623 ReplaceComment (int index, char *text)
15629 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15630 pvInfoList[index-1].depth == len &&
15631 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15632 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15633 while (*text == '\n') text++;
15634 len = strlen(text);
15635 while (len > 0 && text[len - 1] == '\n') len--;
15637 if (commentList[index] != NULL)
15638 free(commentList[index]);
15641 commentList[index] = NULL;
15644 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15645 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15646 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15647 commentList[index] = (char *) malloc(len + 2);
15648 strncpy(commentList[index], text, len);
15649 commentList[index][len] = '\n';
15650 commentList[index][len + 1] = NULLCHAR;
15652 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15654 commentList[index] = (char *) malloc(len + 7);
15655 safeStrCpy(commentList[index], "{\n", 3);
15656 safeStrCpy(commentList[index]+2, text, len+1);
15657 commentList[index][len+2] = NULLCHAR;
15658 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15659 strcat(commentList[index], "\n}\n");
15664 CrushCRs (char *text)
15672 if (ch == '\r') continue;
15674 } while (ch != '\0');
15678 AppendComment (int index, char *text, Boolean addBraces)
15679 /* addBraces tells if we should add {} */
15684 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15685 if(addBraces == 3) addBraces = 0; else // force appending literally
15686 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15689 while (*text == '\n') text++;
15690 len = strlen(text);
15691 while (len > 0 && text[len - 1] == '\n') len--;
15692 text[len] = NULLCHAR;
15694 if (len == 0) return;
15696 if (commentList[index] != NULL) {
15697 Boolean addClosingBrace = addBraces;
15698 old = commentList[index];
15699 oldlen = strlen(old);
15700 while(commentList[index][oldlen-1] == '\n')
15701 commentList[index][--oldlen] = NULLCHAR;
15702 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15703 safeStrCpy(commentList[index], old, oldlen + len + 6);
15705 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15706 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15707 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15708 while (*text == '\n') { text++; len--; }
15709 commentList[index][--oldlen] = NULLCHAR;
15711 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15712 else strcat(commentList[index], "\n");
15713 strcat(commentList[index], text);
15714 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15715 else strcat(commentList[index], "\n");
15717 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15719 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15720 else commentList[index][0] = NULLCHAR;
15721 strcat(commentList[index], text);
15722 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15723 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15728 FindStr (char * text, char * sub_text)
15730 char * result = strstr( text, sub_text );
15732 if( result != NULL ) {
15733 result += strlen( sub_text );
15739 /* [AS] Try to extract PV info from PGN comment */
15740 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15742 GetInfoFromComment (int index, char * text)
15744 char * sep = text, *p;
15746 if( text != NULL && index > 0 ) {
15749 int time = -1, sec = 0, deci;
15750 char * s_eval = FindStr( text, "[%eval " );
15751 char * s_emt = FindStr( text, "[%emt " );
15753 if( s_eval != NULL || s_emt != NULL ) {
15755 if(0) { // [HGM] this code is not finished, and could actually be detrimental
15760 if( s_eval != NULL ) {
15761 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15765 if( delim != ']' ) {
15770 if( s_emt != NULL ) {
15775 /* We expect something like: [+|-]nnn.nn/dd */
15778 if(*text != '{') return text; // [HGM] braces: must be normal comment
15780 sep = strchr( text, '/' );
15781 if( sep == NULL || sep < (text+4) ) {
15786 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15787 if(p[1] == '(') { // comment starts with PV
15788 p = strchr(p, ')'); // locate end of PV
15789 if(p == NULL || sep < p+5) return text;
15790 // at this point we have something like "{(.*) +0.23/6 ..."
15791 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15792 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15793 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15795 time = -1; sec = -1; deci = -1;
15796 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15797 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15798 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15799 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15803 if( score_lo < 0 || score_lo >= 100 ) {
15807 if(sec >= 0) time = 600*time + 10*sec; else
15808 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15810 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15812 /* [HGM] PV time: now locate end of PV info */
15813 while( *++sep >= '0' && *sep <= '9'); // strip depth
15815 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15817 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15819 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15820 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15831 pvInfoList[index-1].depth = depth;
15832 pvInfoList[index-1].score = score;
15833 pvInfoList[index-1].time = 10*time; // centi-sec
15834 if(*sep == '}') *sep = 0; else *--sep = '{';
15835 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15841 SendToProgram (char *message, ChessProgramState *cps)
15843 int count, outCount, error;
15846 if (cps->pr == NoProc) return;
15849 if (appData.debugMode) {
15852 fprintf(debugFP, "%ld >%-6s: %s",
15853 SubtractTimeMarks(&now, &programStartTime),
15854 cps->which, message);
15856 fprintf(serverFP, "%ld >%-6s: %s",
15857 SubtractTimeMarks(&now, &programStartTime),
15858 cps->which, message), fflush(serverFP);
15861 count = strlen(message);
15862 outCount = OutputToProcess(cps->pr, message, count, &error);
15863 if (outCount < count && !exiting
15864 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15865 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15866 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15867 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15868 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15869 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15870 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15871 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15873 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15874 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15875 gameInfo.result = res;
15877 gameInfo.resultDetails = StrSave(buf);
15879 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15880 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15885 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15889 ChessProgramState *cps = (ChessProgramState *)closure;
15891 if (isr != cps->isr) return; /* Killed intentionally */
15894 RemoveInputSource(cps->isr);
15895 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15896 _(cps->which), cps->program);
15897 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15898 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15899 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15900 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15901 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15902 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15904 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15905 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15906 gameInfo.result = res;
15908 gameInfo.resultDetails = StrSave(buf);
15910 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15911 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15913 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15914 _(cps->which), cps->program);
15915 RemoveInputSource(cps->isr);
15917 /* [AS] Program is misbehaving badly... kill it */
15918 if( count == -2 ) {
15919 DestroyChildProcess( cps->pr, 9 );
15923 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15928 if ((end_str = strchr(message, '\r')) != NULL)
15929 *end_str = NULLCHAR;
15930 if ((end_str = strchr(message, '\n')) != NULL)
15931 *end_str = NULLCHAR;
15933 if (appData.debugMode) {
15934 TimeMark now; int print = 1;
15935 char *quote = ""; char c; int i;
15937 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15938 char start = message[0];
15939 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15940 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15941 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15942 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15943 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15944 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15945 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15946 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15947 sscanf(message, "hint: %c", &c)!=1 &&
15948 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15949 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15950 print = (appData.engineComments >= 2);
15952 message[0] = start; // restore original message
15956 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15957 SubtractTimeMarks(&now, &programStartTime), cps->which,
15961 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15962 SubtractTimeMarks(&now, &programStartTime), cps->which,
15964 message), fflush(serverFP);
15968 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15969 if (appData.icsEngineAnalyze) {
15970 if (strstr(message, "whisper") != NULL ||
15971 strstr(message, "kibitz") != NULL ||
15972 strstr(message, "tellics") != NULL) return;
15975 HandleMachineMove(message, cps);
15980 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15985 if( timeControl_2 > 0 ) {
15986 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15987 tc = timeControl_2;
15990 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15991 inc /= cps->timeOdds;
15992 st /= cps->timeOdds;
15994 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15997 /* Set exact time per move, normally using st command */
15998 if (cps->stKludge) {
15999 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16001 if (seconds == 0) {
16002 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16004 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16007 snprintf(buf, MSG_SIZ, "st %d\n", st);
16010 /* Set conventional or incremental time control, using level command */
16011 if (seconds == 0) {
16012 /* Note old gnuchess bug -- minutes:seconds used to not work.
16013 Fixed in later versions, but still avoid :seconds
16014 when seconds is 0. */
16015 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16017 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16018 seconds, inc/1000.);
16021 SendToProgram(buf, cps);
16023 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16024 /* Orthogonally, limit search to given depth */
16026 if (cps->sdKludge) {
16027 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16029 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16031 SendToProgram(buf, cps);
16034 if(cps->nps >= 0) { /* [HGM] nps */
16035 if(cps->supportsNPS == FALSE)
16036 cps->nps = -1; // don't use if engine explicitly says not supported!
16038 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16039 SendToProgram(buf, cps);
16044 ChessProgramState *
16046 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16048 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16049 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16055 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16057 char message[MSG_SIZ];
16060 /* Note: this routine must be called when the clocks are stopped
16061 or when they have *just* been set or switched; otherwise
16062 it will be off by the time since the current tick started.
16064 if (machineWhite) {
16065 time = whiteTimeRemaining / 10;
16066 otime = blackTimeRemaining / 10;
16068 time = blackTimeRemaining / 10;
16069 otime = whiteTimeRemaining / 10;
16071 /* [HGM] translate opponent's time by time-odds factor */
16072 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16074 if (time <= 0) time = 1;
16075 if (otime <= 0) otime = 1;
16077 snprintf(message, MSG_SIZ, "time %ld\n", time);
16078 SendToProgram(message, cps);
16080 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16081 SendToProgram(message, cps);
16085 EngineDefinedVariant (ChessProgramState *cps, int n)
16086 { // return name of n-th unknown variant that engine supports
16087 static char buf[MSG_SIZ];
16088 char *p, *s = cps->variants;
16089 if(!s) return NULL;
16090 do { // parse string from variants feature
16092 p = strchr(s, ',');
16093 if(p) *p = NULLCHAR;
16094 v = StringToVariant(s);
16095 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16096 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16097 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16100 if(n < 0) return buf;
16106 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16109 int len = strlen(name);
16112 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16114 sscanf(*p, "%d", &val);
16116 while (**p && **p != ' ')
16118 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16119 SendToProgram(buf, cps);
16126 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16129 int len = strlen(name);
16130 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16132 sscanf(*p, "%d", loc);
16133 while (**p && **p != ' ') (*p)++;
16134 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16135 SendToProgram(buf, cps);
16142 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16145 int len = strlen(name);
16146 if (strncmp((*p), name, len) == 0
16147 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16149 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16150 sscanf(*p, "%[^\"]", *loc);
16151 while (**p && **p != '\"') (*p)++;
16152 if (**p == '\"') (*p)++;
16153 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16154 SendToProgram(buf, cps);
16161 ParseOption (Option *opt, ChessProgramState *cps)
16162 // [HGM] options: process the string that defines an engine option, and determine
16163 // name, type, default value, and allowed value range
16165 char *p, *q, buf[MSG_SIZ];
16166 int n, min = (-1)<<31, max = 1<<31, def;
16168 if(p = strstr(opt->name, " -spin ")) {
16169 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16170 if(max < min) max = min; // enforce consistency
16171 if(def < min) def = min;
16172 if(def > max) def = max;
16177 } else if((p = strstr(opt->name, " -slider "))) {
16178 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16179 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16180 if(max < min) max = min; // enforce consistency
16181 if(def < min) def = min;
16182 if(def > max) def = max;
16186 opt->type = Spin; // Slider;
16187 } else if((p = strstr(opt->name, " -string "))) {
16188 opt->textValue = p+9;
16189 opt->type = TextBox;
16190 } else if((p = strstr(opt->name, " -file "))) {
16191 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16192 opt->textValue = p+7;
16193 opt->type = FileName; // FileName;
16194 } else if((p = strstr(opt->name, " -path "))) {
16195 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16196 opt->textValue = p+7;
16197 opt->type = PathName; // PathName;
16198 } else if(p = strstr(opt->name, " -check ")) {
16199 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16200 opt->value = (def != 0);
16201 opt->type = CheckBox;
16202 } else if(p = strstr(opt->name, " -combo ")) {
16203 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16204 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16205 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16206 opt->value = n = 0;
16207 while(q = StrStr(q, " /// ")) {
16208 n++; *q = 0; // count choices, and null-terminate each of them
16210 if(*q == '*') { // remember default, which is marked with * prefix
16214 cps->comboList[cps->comboCnt++] = q;
16216 cps->comboList[cps->comboCnt++] = NULL;
16218 opt->type = ComboBox;
16219 } else if(p = strstr(opt->name, " -button")) {
16220 opt->type = Button;
16221 } else if(p = strstr(opt->name, " -save")) {
16222 opt->type = SaveButton;
16223 } else return FALSE;
16224 *p = 0; // terminate option name
16225 // now look if the command-line options define a setting for this engine option.
16226 if(cps->optionSettings && cps->optionSettings[0])
16227 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16228 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16229 snprintf(buf, MSG_SIZ, "option %s", p);
16230 if(p = strstr(buf, ",")) *p = 0;
16231 if(q = strchr(buf, '=')) switch(opt->type) {
16233 for(n=0; n<opt->max; n++)
16234 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16237 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16241 opt->value = atoi(q+1);
16246 SendToProgram(buf, cps);
16252 FeatureDone (ChessProgramState *cps, int val)
16254 DelayedEventCallback cb = GetDelayedEvent();
16255 if ((cb == InitBackEnd3 && cps == &first) ||
16256 (cb == SettingsMenuIfReady && cps == &second) ||
16257 (cb == LoadEngine) ||
16258 (cb == TwoMachinesEventIfReady)) {
16259 CancelDelayedEvent();
16260 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16262 cps->initDone = val;
16263 if(val) cps->reload = FALSE;
16266 /* Parse feature command from engine */
16268 ParseFeatures (char *args, ChessProgramState *cps)
16276 while (*p == ' ') p++;
16277 if (*p == NULLCHAR) return;
16279 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16280 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16281 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16282 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16283 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16284 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16285 if (BoolFeature(&p, "reuse", &val, cps)) {
16286 /* Engine can disable reuse, but can't enable it if user said no */
16287 if (!val) cps->reuse = FALSE;
16290 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16291 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16292 if (gameMode == TwoMachinesPlay) {
16293 DisplayTwoMachinesTitle();
16299 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16300 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16301 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16302 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16303 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16304 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16305 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16306 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16307 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16308 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16309 if (IntFeature(&p, "done", &val, cps)) {
16310 FeatureDone(cps, val);
16313 /* Added by Tord: */
16314 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16315 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16316 /* End of additions by Tord */
16318 /* [HGM] added features: */
16319 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16320 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16321 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16322 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16323 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16324 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16325 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16326 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16327 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16328 FREE(cps->option[cps->nrOptions].name);
16329 cps->option[cps->nrOptions].name = q; q = NULL;
16330 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16331 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16332 SendToProgram(buf, cps);
16335 if(cps->nrOptions >= MAX_OPTIONS) {
16337 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16338 DisplayError(buf, 0);
16342 /* End of additions by HGM */
16344 /* unknown feature: complain and skip */
16346 while (*q && *q != '=') q++;
16347 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16348 SendToProgram(buf, cps);
16354 while (*p && *p != '\"') p++;
16355 if (*p == '\"') p++;
16357 while (*p && *p != ' ') p++;
16365 PeriodicUpdatesEvent (int newState)
16367 if (newState == appData.periodicUpdates)
16370 appData.periodicUpdates=newState;
16372 /* Display type changes, so update it now */
16373 // DisplayAnalysis();
16375 /* Get the ball rolling again... */
16377 AnalysisPeriodicEvent(1);
16378 StartAnalysisClock();
16383 PonderNextMoveEvent (int newState)
16385 if (newState == appData.ponderNextMove) return;
16386 if (gameMode == EditPosition) EditPositionDone(TRUE);
16388 SendToProgram("hard\n", &first);
16389 if (gameMode == TwoMachinesPlay) {
16390 SendToProgram("hard\n", &second);
16393 SendToProgram("easy\n", &first);
16394 thinkOutput[0] = NULLCHAR;
16395 if (gameMode == TwoMachinesPlay) {
16396 SendToProgram("easy\n", &second);
16399 appData.ponderNextMove = newState;
16403 NewSettingEvent (int option, int *feature, char *command, int value)
16407 if (gameMode == EditPosition) EditPositionDone(TRUE);
16408 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16409 if(feature == NULL || *feature) SendToProgram(buf, &first);
16410 if (gameMode == TwoMachinesPlay) {
16411 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16416 ShowThinkingEvent ()
16417 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16419 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16420 int newState = appData.showThinking
16421 // [HGM] thinking: other features now need thinking output as well
16422 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16424 if (oldState == newState) return;
16425 oldState = newState;
16426 if (gameMode == EditPosition) EditPositionDone(TRUE);
16428 SendToProgram("post\n", &first);
16429 if (gameMode == TwoMachinesPlay) {
16430 SendToProgram("post\n", &second);
16433 SendToProgram("nopost\n", &first);
16434 thinkOutput[0] = NULLCHAR;
16435 if (gameMode == TwoMachinesPlay) {
16436 SendToProgram("nopost\n", &second);
16439 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16443 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16445 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16446 if (pr == NoProc) return;
16447 AskQuestion(title, question, replyPrefix, pr);
16451 TypeInEvent (char firstChar)
16453 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16454 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16455 gameMode == AnalyzeMode || gameMode == EditGame ||
16456 gameMode == EditPosition || gameMode == IcsExamining ||
16457 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16458 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16459 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16460 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16461 gameMode == Training) PopUpMoveDialog(firstChar);
16465 TypeInDoneEvent (char *move)
16468 int n, fromX, fromY, toX, toY;
16470 ChessMove moveType;
16473 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16474 EditPositionPasteFEN(move);
16477 // [HGM] movenum: allow move number to be typed in any mode
16478 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16482 // undocumented kludge: allow command-line option to be typed in!
16483 // (potentially fatal, and does not implement the effect of the option.)
16484 // should only be used for options that are values on which future decisions will be made,
16485 // and definitely not on options that would be used during initialization.
16486 if(strstr(move, "!!! -") == move) {
16487 ParseArgsFromString(move+4);
16491 if (gameMode != EditGame && currentMove != forwardMostMove &&
16492 gameMode != Training) {
16493 DisplayMoveError(_("Displayed move is not current"));
16495 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16496 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16497 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16498 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16499 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16500 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16502 DisplayMoveError(_("Could not parse move"));
16508 DisplayMove (int moveNumber)
16510 char message[MSG_SIZ];
16512 char cpThinkOutput[MSG_SIZ];
16514 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16516 if (moveNumber == forwardMostMove - 1 ||
16517 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16519 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16521 if (strchr(cpThinkOutput, '\n')) {
16522 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16525 *cpThinkOutput = NULLCHAR;
16528 /* [AS] Hide thinking from human user */
16529 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16530 *cpThinkOutput = NULLCHAR;
16531 if( thinkOutput[0] != NULLCHAR ) {
16534 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16535 cpThinkOutput[i] = '.';
16537 cpThinkOutput[i] = NULLCHAR;
16538 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16542 if (moveNumber == forwardMostMove - 1 &&
16543 gameInfo.resultDetails != NULL) {
16544 if (gameInfo.resultDetails[0] == NULLCHAR) {
16545 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16547 snprintf(res, MSG_SIZ, " {%s} %s",
16548 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16554 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16555 DisplayMessage(res, cpThinkOutput);
16557 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16558 WhiteOnMove(moveNumber) ? " " : ".. ",
16559 parseList[moveNumber], res);
16560 DisplayMessage(message, cpThinkOutput);
16565 DisplayComment (int moveNumber, char *text)
16567 char title[MSG_SIZ];
16569 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16570 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16572 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16573 WhiteOnMove(moveNumber) ? " " : ".. ",
16574 parseList[moveNumber]);
16576 if (text != NULL && (appData.autoDisplayComment || commentUp))
16577 CommentPopUp(title, text);
16580 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16581 * might be busy thinking or pondering. It can be omitted if your
16582 * gnuchess is configured to stop thinking immediately on any user
16583 * input. However, that gnuchess feature depends on the FIONREAD
16584 * ioctl, which does not work properly on some flavors of Unix.
16587 Attention (ChessProgramState *cps)
16590 if (!cps->useSigint) return;
16591 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16592 switch (gameMode) {
16593 case MachinePlaysWhite:
16594 case MachinePlaysBlack:
16595 case TwoMachinesPlay:
16596 case IcsPlayingWhite:
16597 case IcsPlayingBlack:
16600 /* Skip if we know it isn't thinking */
16601 if (!cps->maybeThinking) return;
16602 if (appData.debugMode)
16603 fprintf(debugFP, "Interrupting %s\n", cps->which);
16604 InterruptChildProcess(cps->pr);
16605 cps->maybeThinking = FALSE;
16610 #endif /*ATTENTION*/
16616 if (whiteTimeRemaining <= 0) {
16619 if (appData.icsActive) {
16620 if (appData.autoCallFlag &&
16621 gameMode == IcsPlayingBlack && !blackFlag) {
16622 SendToICS(ics_prefix);
16623 SendToICS("flag\n");
16627 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16629 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16630 if (appData.autoCallFlag) {
16631 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16638 if (blackTimeRemaining <= 0) {
16641 if (appData.icsActive) {
16642 if (appData.autoCallFlag &&
16643 gameMode == IcsPlayingWhite && !whiteFlag) {
16644 SendToICS(ics_prefix);
16645 SendToICS("flag\n");
16649 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16651 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16652 if (appData.autoCallFlag) {
16653 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16664 CheckTimeControl ()
16666 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16667 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16670 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16672 if ( !WhiteOnMove(forwardMostMove) ) {
16673 /* White made time control */
16674 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16675 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16676 /* [HGM] time odds: correct new time quota for time odds! */
16677 / WhitePlayer()->timeOdds;
16678 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16680 lastBlack -= blackTimeRemaining;
16681 /* Black made time control */
16682 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16683 / WhitePlayer()->other->timeOdds;
16684 lastWhite = whiteTimeRemaining;
16689 DisplayBothClocks ()
16691 int wom = gameMode == EditPosition ?
16692 !blackPlaysFirst : WhiteOnMove(currentMove);
16693 DisplayWhiteClock(whiteTimeRemaining, wom);
16694 DisplayBlackClock(blackTimeRemaining, !wom);
16698 /* Timekeeping seems to be a portability nightmare. I think everyone
16699 has ftime(), but I'm really not sure, so I'm including some ifdefs
16700 to use other calls if you don't. Clocks will be less accurate if
16701 you have neither ftime nor gettimeofday.
16704 /* VS 2008 requires the #include outside of the function */
16705 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16706 #include <sys/timeb.h>
16709 /* Get the current time as a TimeMark */
16711 GetTimeMark (TimeMark *tm)
16713 #if HAVE_GETTIMEOFDAY
16715 struct timeval timeVal;
16716 struct timezone timeZone;
16718 gettimeofday(&timeVal, &timeZone);
16719 tm->sec = (long) timeVal.tv_sec;
16720 tm->ms = (int) (timeVal.tv_usec / 1000L);
16722 #else /*!HAVE_GETTIMEOFDAY*/
16725 // include <sys/timeb.h> / moved to just above start of function
16726 struct timeb timeB;
16729 tm->sec = (long) timeB.time;
16730 tm->ms = (int) timeB.millitm;
16732 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16733 tm->sec = (long) time(NULL);
16739 /* Return the difference in milliseconds between two
16740 time marks. We assume the difference will fit in a long!
16743 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16745 return 1000L*(tm2->sec - tm1->sec) +
16746 (long) (tm2->ms - tm1->ms);
16751 * Code to manage the game clocks.
16753 * In tournament play, black starts the clock and then white makes a move.
16754 * We give the human user a slight advantage if he is playing white---the
16755 * clocks don't run until he makes his first move, so it takes zero time.
16756 * Also, we don't account for network lag, so we could get out of sync
16757 * with GNU Chess's clock -- but then, referees are always right.
16760 static TimeMark tickStartTM;
16761 static long intendedTickLength;
16764 NextTickLength (long timeRemaining)
16766 long nominalTickLength, nextTickLength;
16768 if (timeRemaining > 0L && timeRemaining <= 10000L)
16769 nominalTickLength = 100L;
16771 nominalTickLength = 1000L;
16772 nextTickLength = timeRemaining % nominalTickLength;
16773 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16775 return nextTickLength;
16778 /* Adjust clock one minute up or down */
16780 AdjustClock (Boolean which, int dir)
16782 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16783 if(which) blackTimeRemaining += 60000*dir;
16784 else whiteTimeRemaining += 60000*dir;
16785 DisplayBothClocks();
16786 adjustedClock = TRUE;
16789 /* Stop clocks and reset to a fresh time control */
16793 (void) StopClockTimer();
16794 if (appData.icsActive) {
16795 whiteTimeRemaining = blackTimeRemaining = 0;
16796 } else if (searchTime) {
16797 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16798 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16799 } else { /* [HGM] correct new time quote for time odds */
16800 whiteTC = blackTC = fullTimeControlString;
16801 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16802 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16804 if (whiteFlag || blackFlag) {
16806 whiteFlag = blackFlag = FALSE;
16808 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16809 DisplayBothClocks();
16810 adjustedClock = FALSE;
16813 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16815 /* Decrement running clock by amount of time that has passed */
16819 long timeRemaining;
16820 long lastTickLength, fudge;
16823 if (!appData.clockMode) return;
16824 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16828 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16830 /* Fudge if we woke up a little too soon */
16831 fudge = intendedTickLength - lastTickLength;
16832 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16834 if (WhiteOnMove(forwardMostMove)) {
16835 if(whiteNPS >= 0) lastTickLength = 0;
16836 timeRemaining = whiteTimeRemaining -= lastTickLength;
16837 if(timeRemaining < 0 && !appData.icsActive) {
16838 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16839 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16840 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16841 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16844 DisplayWhiteClock(whiteTimeRemaining - fudge,
16845 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16847 if(blackNPS >= 0) lastTickLength = 0;
16848 timeRemaining = blackTimeRemaining -= lastTickLength;
16849 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16850 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16852 blackStartMove = forwardMostMove;
16853 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16856 DisplayBlackClock(blackTimeRemaining - fudge,
16857 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16859 if (CheckFlags()) return;
16861 if(twoBoards) { // count down secondary board's clocks as well
16862 activePartnerTime -= lastTickLength;
16864 if(activePartner == 'W')
16865 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16867 DisplayBlackClock(activePartnerTime, TRUE);
16872 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16873 StartClockTimer(intendedTickLength);
16875 /* if the time remaining has fallen below the alarm threshold, sound the
16876 * alarm. if the alarm has sounded and (due to a takeback or time control
16877 * with increment) the time remaining has increased to a level above the
16878 * threshold, reset the alarm so it can sound again.
16881 if (appData.icsActive && appData.icsAlarm) {
16883 /* make sure we are dealing with the user's clock */
16884 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16885 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16888 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16889 alarmSounded = FALSE;
16890 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16892 alarmSounded = TRUE;
16898 /* A player has just moved, so stop the previously running
16899 clock and (if in clock mode) start the other one.
16900 We redisplay both clocks in case we're in ICS mode, because
16901 ICS gives us an update to both clocks after every move.
16902 Note that this routine is called *after* forwardMostMove
16903 is updated, so the last fractional tick must be subtracted
16904 from the color that is *not* on move now.
16907 SwitchClocks (int newMoveNr)
16909 long lastTickLength;
16911 int flagged = FALSE;
16915 if (StopClockTimer() && appData.clockMode) {
16916 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16917 if (!WhiteOnMove(forwardMostMove)) {
16918 if(blackNPS >= 0) lastTickLength = 0;
16919 blackTimeRemaining -= lastTickLength;
16920 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16921 // if(pvInfoList[forwardMostMove].time == -1)
16922 pvInfoList[forwardMostMove].time = // use GUI time
16923 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16925 if(whiteNPS >= 0) lastTickLength = 0;
16926 whiteTimeRemaining -= lastTickLength;
16927 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16928 // if(pvInfoList[forwardMostMove].time == -1)
16929 pvInfoList[forwardMostMove].time =
16930 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16932 flagged = CheckFlags();
16934 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16935 CheckTimeControl();
16937 if (flagged || !appData.clockMode) return;
16939 switch (gameMode) {
16940 case MachinePlaysBlack:
16941 case MachinePlaysWhite:
16942 case BeginningOfGame:
16943 if (pausing) return;
16947 case PlayFromGameFile:
16955 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16956 if(WhiteOnMove(forwardMostMove))
16957 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16958 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16962 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16963 whiteTimeRemaining : blackTimeRemaining);
16964 StartClockTimer(intendedTickLength);
16968 /* Stop both clocks */
16972 long lastTickLength;
16975 if (!StopClockTimer()) return;
16976 if (!appData.clockMode) return;
16980 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16981 if (WhiteOnMove(forwardMostMove)) {
16982 if(whiteNPS >= 0) lastTickLength = 0;
16983 whiteTimeRemaining -= lastTickLength;
16984 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16986 if(blackNPS >= 0) lastTickLength = 0;
16987 blackTimeRemaining -= lastTickLength;
16988 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16993 /* Start clock of player on move. Time may have been reset, so
16994 if clock is already running, stop and restart it. */
16998 (void) StopClockTimer(); /* in case it was running already */
16999 DisplayBothClocks();
17000 if (CheckFlags()) return;
17002 if (!appData.clockMode) return;
17003 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17005 GetTimeMark(&tickStartTM);
17006 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17007 whiteTimeRemaining : blackTimeRemaining);
17009 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17010 whiteNPS = blackNPS = -1;
17011 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17012 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17013 whiteNPS = first.nps;
17014 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17015 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17016 blackNPS = first.nps;
17017 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17018 whiteNPS = second.nps;
17019 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17020 blackNPS = second.nps;
17021 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17023 StartClockTimer(intendedTickLength);
17027 TimeString (long ms)
17029 long second, minute, hour, day;
17031 static char buf[32];
17033 if (ms > 0 && ms <= 9900) {
17034 /* convert milliseconds to tenths, rounding up */
17035 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17037 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17041 /* convert milliseconds to seconds, rounding up */
17042 /* use floating point to avoid strangeness of integer division
17043 with negative dividends on many machines */
17044 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17051 day = second / (60 * 60 * 24);
17052 second = second % (60 * 60 * 24);
17053 hour = second / (60 * 60);
17054 second = second % (60 * 60);
17055 minute = second / 60;
17056 second = second % 60;
17059 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17060 sign, day, hour, minute, second);
17062 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17064 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17071 * This is necessary because some C libraries aren't ANSI C compliant yet.
17074 StrStr (char *string, char *match)
17078 length = strlen(match);
17080 for (i = strlen(string) - length; i >= 0; i--, string++)
17081 if (!strncmp(match, string, length))
17088 StrCaseStr (char *string, char *match)
17092 length = strlen(match);
17094 for (i = strlen(string) - length; i >= 0; i--, string++) {
17095 for (j = 0; j < length; j++) {
17096 if (ToLower(match[j]) != ToLower(string[j]))
17099 if (j == length) return string;
17107 StrCaseCmp (char *s1, char *s2)
17112 c1 = ToLower(*s1++);
17113 c2 = ToLower(*s2++);
17114 if (c1 > c2) return 1;
17115 if (c1 < c2) return -1;
17116 if (c1 == NULLCHAR) return 0;
17124 return isupper(c) ? tolower(c) : c;
17131 return islower(c) ? toupper(c) : c;
17133 #endif /* !_amigados */
17140 if ((ret = (char *) malloc(strlen(s) + 1)))
17142 safeStrCpy(ret, s, strlen(s)+1);
17148 StrSavePtr (char *s, char **savePtr)
17153 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17154 safeStrCpy(*savePtr, s, strlen(s)+1);
17166 clock = time((time_t *)NULL);
17167 tm = localtime(&clock);
17168 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17169 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17170 return StrSave(buf);
17175 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17177 int i, j, fromX, fromY, toX, toY;
17184 whiteToPlay = (gameMode == EditPosition) ?
17185 !blackPlaysFirst : (move % 2 == 0);
17188 /* Piece placement data */
17189 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17190 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17192 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17193 if (boards[move][i][j] == EmptySquare) {
17195 } else { ChessSquare piece = boards[move][i][j];
17196 if (emptycount > 0) {
17197 if(emptycount<10) /* [HGM] can be >= 10 */
17198 *p++ = '0' + emptycount;
17199 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17202 if(PieceToChar(piece) == '+') {
17203 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17205 piece = (ChessSquare)(DEMOTED piece);
17207 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17209 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17210 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17215 if (emptycount > 0) {
17216 if(emptycount<10) /* [HGM] can be >= 10 */
17217 *p++ = '0' + emptycount;
17218 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17225 /* [HGM] print Crazyhouse or Shogi holdings */
17226 if( gameInfo.holdingsWidth ) {
17227 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17229 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17230 piece = boards[move][i][BOARD_WIDTH-1];
17231 if( piece != EmptySquare )
17232 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17233 *p++ = PieceToChar(piece);
17235 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17236 piece = boards[move][BOARD_HEIGHT-i-1][0];
17237 if( piece != EmptySquare )
17238 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17239 *p++ = PieceToChar(piece);
17242 if( q == p ) *p++ = '-';
17248 *p++ = whiteToPlay ? 'w' : 'b';
17251 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17252 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17254 if(nrCastlingRights) {
17256 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17257 /* [HGM] write directly from rights */
17258 if(boards[move][CASTLING][2] != NoRights &&
17259 boards[move][CASTLING][0] != NoRights )
17260 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17261 if(boards[move][CASTLING][2] != NoRights &&
17262 boards[move][CASTLING][1] != NoRights )
17263 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17264 if(boards[move][CASTLING][5] != NoRights &&
17265 boards[move][CASTLING][3] != NoRights )
17266 *p++ = boards[move][CASTLING][3] + AAA;
17267 if(boards[move][CASTLING][5] != NoRights &&
17268 boards[move][CASTLING][4] != NoRights )
17269 *p++ = boards[move][CASTLING][4] + AAA;
17272 /* [HGM] write true castling rights */
17273 if( nrCastlingRights == 6 ) {
17275 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17276 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17277 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17278 boards[move][CASTLING][2] != NoRights );
17279 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17280 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17281 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17282 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17283 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17287 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17288 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17289 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17290 boards[move][CASTLING][5] != NoRights );
17291 if(gameInfo.variant == VariantSChess) {
17292 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17293 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17294 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17295 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17300 if (q == p) *p++ = '-'; /* No castling rights */
17304 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17305 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17306 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17307 /* En passant target square */
17308 if (move > backwardMostMove) {
17309 fromX = moveList[move - 1][0] - AAA;
17310 fromY = moveList[move - 1][1] - ONE;
17311 toX = moveList[move - 1][2] - AAA;
17312 toY = moveList[move - 1][3] - ONE;
17313 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17314 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17315 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17317 /* 2-square pawn move just happened */
17319 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17323 } else if(move == backwardMostMove) {
17324 // [HGM] perhaps we should always do it like this, and forget the above?
17325 if((signed char)boards[move][EP_STATUS] >= 0) {
17326 *p++ = boards[move][EP_STATUS] + AAA;
17327 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17339 { int i = 0, j=move;
17341 /* [HGM] find reversible plies */
17342 if (appData.debugMode) { int k;
17343 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17344 for(k=backwardMostMove; k<=forwardMostMove; k++)
17345 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17349 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17350 if( j == backwardMostMove ) i += initialRulePlies;
17351 sprintf(p, "%d ", i);
17352 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17354 /* Fullmove number */
17355 sprintf(p, "%d", (move / 2) + 1);
17356 } else *--p = NULLCHAR;
17358 return StrSave(buf);
17362 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17366 int emptycount, virgin[BOARD_FILES];
17371 /* [HGM] by default clear Crazyhouse holdings, if present */
17372 if(gameInfo.holdingsWidth) {
17373 for(i=0; i<BOARD_HEIGHT; i++) {
17374 board[i][0] = EmptySquare; /* black holdings */
17375 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17376 board[i][1] = (ChessSquare) 0; /* black counts */
17377 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17381 /* Piece placement data */
17382 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17385 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17386 if (*p == '/') p++;
17387 emptycount = gameInfo.boardWidth - j;
17388 while (emptycount--)
17389 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17391 #if(BOARD_FILES >= 10)
17392 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17393 p++; emptycount=10;
17394 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17395 while (emptycount--)
17396 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17398 } else if (*p == '*') {
17399 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17400 } else if (isdigit(*p)) {
17401 emptycount = *p++ - '0';
17402 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17403 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17404 while (emptycount--)
17405 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17406 } else if (*p == '+' || isalpha(*p)) {
17407 if (j >= gameInfo.boardWidth) return FALSE;
17409 piece = CharToPiece(*++p);
17410 if(piece == EmptySquare) return FALSE; /* unknown piece */
17411 piece = (ChessSquare) (PROMOTED piece ); p++;
17412 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17413 } else piece = CharToPiece(*p++);
17415 if(piece==EmptySquare) return FALSE; /* unknown piece */
17416 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17417 piece = (ChessSquare) (PROMOTED piece);
17418 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17421 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17427 while (*p == '/' || *p == ' ') p++;
17429 /* [HGM] look for Crazyhouse holdings here */
17430 while(*p==' ') p++;
17431 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17433 if(*p == '-' ) p++; /* empty holdings */ else {
17434 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17435 /* if we would allow FEN reading to set board size, we would */
17436 /* have to add holdings and shift the board read so far here */
17437 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17439 if((int) piece >= (int) BlackPawn ) {
17440 i = (int)piece - (int)BlackPawn;
17441 i = PieceToNumber((ChessSquare)i);
17442 if( i >= gameInfo.holdingsSize ) return FALSE;
17443 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17444 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17446 i = (int)piece - (int)WhitePawn;
17447 i = PieceToNumber((ChessSquare)i);
17448 if( i >= gameInfo.holdingsSize ) return FALSE;
17449 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17450 board[i][BOARD_WIDTH-2]++; /* black holdings */
17457 while(*p == ' ') p++;
17461 if(appData.colorNickNames) {
17462 if( c == appData.colorNickNames[0] ) c = 'w'; else
17463 if( c == appData.colorNickNames[1] ) c = 'b';
17467 *blackPlaysFirst = FALSE;
17470 *blackPlaysFirst = TRUE;
17476 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17477 /* return the extra info in global variiables */
17479 /* set defaults in case FEN is incomplete */
17480 board[EP_STATUS] = EP_UNKNOWN;
17481 for(i=0; i<nrCastlingRights; i++ ) {
17482 board[CASTLING][i] =
17483 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17484 } /* assume possible unless obviously impossible */
17485 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17486 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17487 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17488 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17489 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17490 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17491 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17492 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17495 while(*p==' ') p++;
17496 if(nrCastlingRights) {
17497 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17498 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17499 /* castling indicator present, so default becomes no castlings */
17500 for(i=0; i<nrCastlingRights; i++ ) {
17501 board[CASTLING][i] = NoRights;
17504 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17505 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17506 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17507 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17508 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17510 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17511 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17512 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17514 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17515 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17516 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17517 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17518 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17519 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17522 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17523 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17524 board[CASTLING][2] = whiteKingFile;
17525 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17526 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17529 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17530 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17531 board[CASTLING][2] = whiteKingFile;
17532 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17533 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17536 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17537 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17538 board[CASTLING][5] = blackKingFile;
17539 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17540 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17543 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17544 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17545 board[CASTLING][5] = blackKingFile;
17546 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17547 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17550 default: /* FRC castlings */
17551 if(c >= 'a') { /* black rights */
17552 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17553 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17554 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17555 if(i == BOARD_RGHT) break;
17556 board[CASTLING][5] = i;
17558 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17559 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17561 board[CASTLING][3] = c;
17563 board[CASTLING][4] = c;
17564 } else { /* white rights */
17565 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17566 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17567 if(board[0][i] == WhiteKing) break;
17568 if(i == BOARD_RGHT) break;
17569 board[CASTLING][2] = i;
17570 c -= AAA - 'a' + 'A';
17571 if(board[0][c] >= WhiteKing) break;
17573 board[CASTLING][0] = c;
17575 board[CASTLING][1] = c;
17579 for(i=0; i<nrCastlingRights; i++)
17580 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17581 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17582 if (appData.debugMode) {
17583 fprintf(debugFP, "FEN castling rights:");
17584 for(i=0; i<nrCastlingRights; i++)
17585 fprintf(debugFP, " %d", board[CASTLING][i]);
17586 fprintf(debugFP, "\n");
17589 while(*p==' ') p++;
17592 /* read e.p. field in games that know e.p. capture */
17593 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17594 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17595 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17597 p++; board[EP_STATUS] = EP_NONE;
17599 char c = *p++ - AAA;
17601 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17602 if(*p >= '0' && *p <='9') p++;
17603 board[EP_STATUS] = c;
17608 if(sscanf(p, "%d", &i) == 1) {
17609 FENrulePlies = i; /* 50-move ply counter */
17610 /* (The move number is still ignored) */
17617 EditPositionPasteFEN (char *fen)
17620 Board initial_position;
17622 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17623 DisplayError(_("Bad FEN position in clipboard"), 0);
17626 int savedBlackPlaysFirst = blackPlaysFirst;
17627 EditPositionEvent();
17628 blackPlaysFirst = savedBlackPlaysFirst;
17629 CopyBoard(boards[0], initial_position);
17630 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17631 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17632 DisplayBothClocks();
17633 DrawPosition(FALSE, boards[currentMove]);
17638 static char cseq[12] = "\\ ";
17641 set_cont_sequence (char *new_seq)
17646 // handle bad attempts to set the sequence
17648 return 0; // acceptable error - no debug
17650 len = strlen(new_seq);
17651 ret = (len > 0) && (len < sizeof(cseq));
17653 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17654 else if (appData.debugMode)
17655 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17660 reformat a source message so words don't cross the width boundary. internal
17661 newlines are not removed. returns the wrapped size (no null character unless
17662 included in source message). If dest is NULL, only calculate the size required
17663 for the dest buffer. lp argument indicats line position upon entry, and it's
17664 passed back upon exit.
17667 wrap (char *dest, char *src, int count, int width, int *lp)
17669 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17671 cseq_len = strlen(cseq);
17672 old_line = line = *lp;
17673 ansi = len = clen = 0;
17675 for (i=0; i < count; i++)
17677 if (src[i] == '\033')
17680 // if we hit the width, back up
17681 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17683 // store i & len in case the word is too long
17684 old_i = i, old_len = len;
17686 // find the end of the last word
17687 while (i && src[i] != ' ' && src[i] != '\n')
17693 // word too long? restore i & len before splitting it
17694 if ((old_i-i+clen) >= width)
17701 if (i && src[i-1] == ' ')
17704 if (src[i] != ' ' && src[i] != '\n')
17711 // now append the newline and continuation sequence
17716 strncpy(dest+len, cseq, cseq_len);
17724 dest[len] = src[i];
17728 if (src[i] == '\n')
17733 if (dest && appData.debugMode)
17735 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17736 count, width, line, len, *lp);
17737 show_bytes(debugFP, src, count);
17738 fprintf(debugFP, "\ndest: ");
17739 show_bytes(debugFP, dest, len);
17740 fprintf(debugFP, "\n");
17742 *lp = dest ? line : old_line;
17747 // [HGM] vari: routines for shelving variations
17748 Boolean modeRestore = FALSE;
17751 PushInner (int firstMove, int lastMove)
17753 int i, j, nrMoves = lastMove - firstMove;
17755 // push current tail of game on stack
17756 savedResult[storedGames] = gameInfo.result;
17757 savedDetails[storedGames] = gameInfo.resultDetails;
17758 gameInfo.resultDetails = NULL;
17759 savedFirst[storedGames] = firstMove;
17760 savedLast [storedGames] = lastMove;
17761 savedFramePtr[storedGames] = framePtr;
17762 framePtr -= nrMoves; // reserve space for the boards
17763 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17764 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17765 for(j=0; j<MOVE_LEN; j++)
17766 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17767 for(j=0; j<2*MOVE_LEN; j++)
17768 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17769 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17770 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17771 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17772 pvInfoList[firstMove+i-1].depth = 0;
17773 commentList[framePtr+i] = commentList[firstMove+i];
17774 commentList[firstMove+i] = NULL;
17778 forwardMostMove = firstMove; // truncate game so we can start variation
17782 PushTail (int firstMove, int lastMove)
17784 if(appData.icsActive) { // only in local mode
17785 forwardMostMove = currentMove; // mimic old ICS behavior
17788 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17790 PushInner(firstMove, lastMove);
17791 if(storedGames == 1) GreyRevert(FALSE);
17792 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17796 PopInner (Boolean annotate)
17799 char buf[8000], moveBuf[20];
17801 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17802 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17803 nrMoves = savedLast[storedGames] - currentMove;
17806 if(!WhiteOnMove(currentMove))
17807 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17808 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17809 for(i=currentMove; i<forwardMostMove; i++) {
17811 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17812 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17813 strcat(buf, moveBuf);
17814 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17815 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17819 for(i=1; i<=nrMoves; i++) { // copy last variation back
17820 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17821 for(j=0; j<MOVE_LEN; j++)
17822 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17823 for(j=0; j<2*MOVE_LEN; j++)
17824 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17825 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17826 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17827 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17828 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17829 commentList[currentMove+i] = commentList[framePtr+i];
17830 commentList[framePtr+i] = NULL;
17832 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17833 framePtr = savedFramePtr[storedGames];
17834 gameInfo.result = savedResult[storedGames];
17835 if(gameInfo.resultDetails != NULL) {
17836 free(gameInfo.resultDetails);
17838 gameInfo.resultDetails = savedDetails[storedGames];
17839 forwardMostMove = currentMove + nrMoves;
17843 PopTail (Boolean annotate)
17845 if(appData.icsActive) return FALSE; // only in local mode
17846 if(!storedGames) return FALSE; // sanity
17847 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17849 PopInner(annotate);
17850 if(currentMove < forwardMostMove) ForwardEvent(); else
17851 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17853 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17859 { // remove all shelved variations
17861 for(i=0; i<storedGames; i++) {
17862 if(savedDetails[i])
17863 free(savedDetails[i]);
17864 savedDetails[i] = NULL;
17866 for(i=framePtr; i<MAX_MOVES; i++) {
17867 if(commentList[i]) free(commentList[i]);
17868 commentList[i] = NULL;
17870 framePtr = MAX_MOVES-1;
17875 LoadVariation (int index, char *text)
17876 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17877 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17878 int level = 0, move;
17880 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17881 // first find outermost bracketing variation
17882 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17883 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17884 if(*p == '{') wait = '}'; else
17885 if(*p == '[') wait = ']'; else
17886 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17887 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17889 if(*p == wait) wait = NULLCHAR; // closing ]} found
17892 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17893 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17894 end[1] = NULLCHAR; // clip off comment beyond variation
17895 ToNrEvent(currentMove-1);
17896 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17897 // kludge: use ParsePV() to append variation to game
17898 move = currentMove;
17899 ParsePV(start, TRUE, TRUE);
17900 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17901 ClearPremoveHighlights();
17903 ToNrEvent(currentMove+1);
17909 char *p, *q, buf[MSG_SIZ];
17910 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17911 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17912 ParseArgsFromString(buf);
17913 ActivateTheme(TRUE); // also redo colors
17917 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17920 q = appData.themeNames;
17921 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17922 if(appData.useBitmaps) {
17923 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17924 appData.liteBackTextureFile, appData.darkBackTextureFile,
17925 appData.liteBackTextureMode,
17926 appData.darkBackTextureMode );
17928 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17929 Col2Text(2), // lightSquareColor
17930 Col2Text(3) ); // darkSquareColor
17932 if(appData.useBorder) {
17933 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17936 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17938 if(appData.useFont) {
17939 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17940 appData.renderPiecesWithFont,
17941 appData.fontToPieceTable,
17942 Col2Text(9), // appData.fontBackColorWhite
17943 Col2Text(10) ); // appData.fontForeColorBlack
17945 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17946 appData.pieceDirectory);
17947 if(!appData.pieceDirectory[0])
17948 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17949 Col2Text(0), // whitePieceColor
17950 Col2Text(1) ); // blackPieceColor
17952 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17953 Col2Text(4), // highlightSquareColor
17954 Col2Text(5) ); // premoveHighlightColor
17955 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17956 if(insert != q) insert[-1] = NULLCHAR;
17957 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17960 ActivateTheme(FALSE);