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) 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 == VariantXiangqi )
10007 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10008 if( gameInfo.variant == VariantShogi )
10009 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10010 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10011 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10012 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10013 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10014 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10015 if( gameInfo.variant == VariantCourier )
10016 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10017 if( gameInfo.variant == VariantSuper )
10018 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10019 if( gameInfo.variant == VariantGreat )
10020 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10021 if( gameInfo.variant == VariantSChess )
10022 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10023 if( gameInfo.variant == VariantGrand )
10024 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10029 InitChessProgram (ChessProgramState *cps, int setup)
10030 /* setup needed to setup FRC opening position */
10032 char buf[MSG_SIZ], b[MSG_SIZ];
10033 if (appData.noChessProgram) return;
10034 hintRequested = FALSE;
10035 bookRequested = FALSE;
10037 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10038 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10039 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10040 if(cps->memSize) { /* [HGM] memory */
10041 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10042 SendToProgram(buf, cps);
10044 SendEgtPath(cps); /* [HGM] EGT */
10045 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10046 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10047 SendToProgram(buf, cps);
10050 SendToProgram(cps->initString, cps);
10051 if (gameInfo.variant != VariantNormal &&
10052 gameInfo.variant != VariantLoadable
10053 /* [HGM] also send variant if board size non-standard */
10054 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10056 char *v = VariantName(gameInfo.variant);
10057 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10058 /* [HGM] in protocol 1 we have to assume all variants valid */
10059 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10060 DisplayFatalError(buf, 0, 1);
10064 if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10065 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10066 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10067 /* [HGM] varsize: try first if this defiant size variant is specifically known */
10068 if(StrStr(cps->variants, b) == NULL) {
10069 // specific sized variant not known, check if general sizing allowed
10070 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10071 if(StrStr(cps->variants, "boardsize") == NULL) {
10072 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10073 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10074 DisplayFatalError(buf, 0, 1);
10077 /* [HGM] here we really should compare with the maximum supported board size */
10080 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10081 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10082 SendToProgram(buf, cps);
10084 currentlyInitializedVariant = gameInfo.variant;
10086 /* [HGM] send opening position in FRC to first engine */
10088 SendToProgram("force\n", cps);
10090 /* engine is now in force mode! Set flag to wake it up after first move. */
10091 setboardSpoiledMachineBlack = 1;
10094 if (cps->sendICS) {
10095 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10096 SendToProgram(buf, cps);
10098 cps->maybeThinking = FALSE;
10099 cps->offeredDraw = 0;
10100 if (!appData.icsActive) {
10101 SendTimeControl(cps, movesPerSession, timeControl,
10102 timeIncrement, appData.searchDepth,
10105 if (appData.showThinking
10106 // [HGM] thinking: four options require thinking output to be sent
10107 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10109 SendToProgram("post\n", cps);
10111 SendToProgram("hard\n", cps);
10112 if (!appData.ponderNextMove) {
10113 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10114 it without being sure what state we are in first. "hard"
10115 is not a toggle, so that one is OK.
10117 SendToProgram("easy\n", cps);
10119 if (cps->usePing) {
10120 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10121 SendToProgram(buf, cps);
10123 cps->initDone = TRUE;
10124 ClearEngineOutputPane(cps == &second);
10129 ResendOptions (ChessProgramState *cps)
10130 { // send the stored value of the options
10133 Option *opt = cps->option;
10134 for(i=0; i<cps->nrOptions; i++, opt++) {
10135 switch(opt->type) {
10139 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10142 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10145 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10151 SendToProgram(buf, cps);
10156 StartChessProgram (ChessProgramState *cps)
10161 if (appData.noChessProgram) return;
10162 cps->initDone = FALSE;
10164 if (strcmp(cps->host, "localhost") == 0) {
10165 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10166 } else if (*appData.remoteShell == NULLCHAR) {
10167 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10169 if (*appData.remoteUser == NULLCHAR) {
10170 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10173 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10174 cps->host, appData.remoteUser, cps->program);
10176 err = StartChildProcess(buf, "", &cps->pr);
10180 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10181 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10182 if(cps != &first) return;
10183 appData.noChessProgram = TRUE;
10186 // DisplayFatalError(buf, err, 1);
10187 // cps->pr = NoProc;
10188 // cps->isr = NULL;
10192 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10193 if (cps->protocolVersion > 1) {
10194 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10195 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10196 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10197 cps->comboCnt = 0; // and values of combo boxes
10199 SendToProgram(buf, cps);
10200 if(cps->reload) ResendOptions(cps);
10202 SendToProgram("xboard\n", cps);
10207 TwoMachinesEventIfReady P((void))
10209 static int curMess = 0;
10210 if (first.lastPing != first.lastPong) {
10211 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10212 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10215 if (second.lastPing != second.lastPong) {
10216 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10217 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10220 DisplayMessage("", ""); curMess = 0;
10221 TwoMachinesEvent();
10225 MakeName (char *template)
10229 static char buf[MSG_SIZ];
10233 clock = time((time_t *)NULL);
10234 tm = localtime(&clock);
10236 while(*p++ = *template++) if(p[-1] == '%') {
10237 switch(*template++) {
10238 case 0: *p = 0; return buf;
10239 case 'Y': i = tm->tm_year+1900; break;
10240 case 'y': i = tm->tm_year-100; break;
10241 case 'M': i = tm->tm_mon+1; break;
10242 case 'd': i = tm->tm_mday; break;
10243 case 'h': i = tm->tm_hour; break;
10244 case 'm': i = tm->tm_min; break;
10245 case 's': i = tm->tm_sec; break;
10248 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10254 CountPlayers (char *p)
10257 while(p = strchr(p, '\n')) p++, n++; // count participants
10262 WriteTourneyFile (char *results, FILE *f)
10263 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10264 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10265 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10266 // create a file with tournament description
10267 fprintf(f, "-participants {%s}\n", appData.participants);
10268 fprintf(f, "-seedBase %d\n", appData.seedBase);
10269 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10270 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10271 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10272 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10273 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10274 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10275 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10276 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10277 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10278 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10279 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10280 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10281 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10282 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10283 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10284 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10285 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10286 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10287 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10288 fprintf(f, "-smpCores %d\n", appData.smpCores);
10290 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10292 fprintf(f, "-mps %d\n", appData.movesPerSession);
10293 fprintf(f, "-tc %s\n", appData.timeControl);
10294 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10296 fprintf(f, "-results \"%s\"\n", results);
10301 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10304 Substitute (char *participants, int expunge)
10306 int i, changed, changes=0, nPlayers=0;
10307 char *p, *q, *r, buf[MSG_SIZ];
10308 if(participants == NULL) return;
10309 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10310 r = p = participants; q = appData.participants;
10311 while(*p && *p == *q) {
10312 if(*p == '\n') r = p+1, nPlayers++;
10315 if(*p) { // difference
10316 while(*p && *p++ != '\n');
10317 while(*q && *q++ != '\n');
10318 changed = nPlayers;
10319 changes = 1 + (strcmp(p, q) != 0);
10321 if(changes == 1) { // a single engine mnemonic was changed
10322 q = r; while(*q) nPlayers += (*q++ == '\n');
10323 p = buf; while(*r && (*p = *r++) != '\n') p++;
10325 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10326 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10327 if(mnemonic[i]) { // The substitute is valid
10329 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10330 flock(fileno(f), LOCK_EX);
10331 ParseArgsFromFile(f);
10332 fseek(f, 0, SEEK_SET);
10333 FREE(appData.participants); appData.participants = participants;
10334 if(expunge) { // erase results of replaced engine
10335 int len = strlen(appData.results), w, b, dummy;
10336 for(i=0; i<len; i++) {
10337 Pairing(i, nPlayers, &w, &b, &dummy);
10338 if((w == changed || b == changed) && appData.results[i] == '*') {
10339 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10344 for(i=0; i<len; i++) {
10345 Pairing(i, nPlayers, &w, &b, &dummy);
10346 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10349 WriteTourneyFile(appData.results, f);
10350 fclose(f); // release lock
10353 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10355 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10356 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10357 free(participants);
10362 CheckPlayers (char *participants)
10365 char buf[MSG_SIZ], *p;
10366 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10367 while(p = strchr(participants, '\n')) {
10369 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10371 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10373 DisplayError(buf, 0);
10377 participants = p + 1;
10383 CreateTourney (char *name)
10386 if(matchMode && strcmp(name, appData.tourneyFile)) {
10387 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10389 if(name[0] == NULLCHAR) {
10390 if(appData.participants[0])
10391 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10394 f = fopen(name, "r");
10395 if(f) { // file exists
10396 ASSIGN(appData.tourneyFile, name);
10397 ParseArgsFromFile(f); // parse it
10399 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10400 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10401 DisplayError(_("Not enough participants"), 0);
10404 if(CheckPlayers(appData.participants)) return 0;
10405 ASSIGN(appData.tourneyFile, name);
10406 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10407 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10410 appData.noChessProgram = FALSE;
10411 appData.clockMode = TRUE;
10417 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10419 char buf[MSG_SIZ], *p, *q;
10420 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10421 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10422 skip = !all && group[0]; // if group requested, we start in skip mode
10423 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10424 p = names; q = buf; header = 0;
10425 while(*p && *p != '\n') *q++ = *p++;
10427 if(*p == '\n') p++;
10428 if(buf[0] == '#') {
10429 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10430 depth++; // we must be entering a new group
10431 if(all) continue; // suppress printing group headers when complete list requested
10433 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10435 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10436 if(engineList[i]) free(engineList[i]);
10437 engineList[i] = strdup(buf);
10438 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10439 if(engineMnemonic[i]) free(engineMnemonic[i]);
10440 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10442 sscanf(q + 8, "%s", buf + strlen(buf));
10445 engineMnemonic[i] = strdup(buf);
10448 engineList[i] = engineMnemonic[i] = NULL;
10452 // following implemented as macro to avoid type limitations
10453 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10456 SwapEngines (int n)
10457 { // swap settings for first engine and other engine (so far only some selected options)
10462 SWAP(chessProgram, p)
10464 SWAP(hasOwnBookUCI, h)
10465 SWAP(protocolVersion, h)
10467 SWAP(scoreIsAbsolute, h)
10472 SWAP(engOptions, p)
10473 SWAP(engInitString, p)
10474 SWAP(computerString, p)
10476 SWAP(fenOverride, p)
10478 SWAP(accumulateTC, h)
10483 GetEngineLine (char *s, int n)
10487 extern char *icsNames;
10488 if(!s || !*s) return 0;
10489 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10490 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10491 if(!mnemonic[i]) return 0;
10492 if(n == 11) return 1; // just testing if there was a match
10493 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10494 if(n == 1) SwapEngines(n);
10495 ParseArgsFromString(buf);
10496 if(n == 1) SwapEngines(n);
10497 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10498 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10499 ParseArgsFromString(buf);
10505 SetPlayer (int player, char *p)
10506 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10508 char buf[MSG_SIZ], *engineName;
10509 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10510 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10511 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10513 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10514 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10515 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10516 ParseArgsFromString(buf);
10517 } else { // no engine with this nickname is installed!
10518 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10519 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10520 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10522 DisplayError(buf, 0);
10529 char *recentEngines;
10532 RecentEngineEvent (int nr)
10535 // SwapEngines(1); // bump first to second
10536 // ReplaceEngine(&second, 1); // and load it there
10537 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10538 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10539 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10540 ReplaceEngine(&first, 0);
10541 FloatToFront(&appData.recentEngineList, command[n]);
10546 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10547 { // determine players from game number
10548 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10550 if(appData.tourneyType == 0) {
10551 roundsPerCycle = (nPlayers - 1) | 1;
10552 pairingsPerRound = nPlayers / 2;
10553 } else if(appData.tourneyType > 0) {
10554 roundsPerCycle = nPlayers - appData.tourneyType;
10555 pairingsPerRound = appData.tourneyType;
10557 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10558 gamesPerCycle = gamesPerRound * roundsPerCycle;
10559 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10560 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10561 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10562 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10563 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10564 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10566 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10567 if(appData.roundSync) *syncInterval = gamesPerRound;
10569 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10571 if(appData.tourneyType == 0) {
10572 if(curPairing == (nPlayers-1)/2 ) {
10573 *whitePlayer = curRound;
10574 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10576 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10577 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10578 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10579 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10581 } else if(appData.tourneyType > 1) {
10582 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10583 *whitePlayer = curRound + appData.tourneyType;
10584 } else if(appData.tourneyType > 0) {
10585 *whitePlayer = curPairing;
10586 *blackPlayer = curRound + appData.tourneyType;
10589 // take care of white/black alternation per round.
10590 // For cycles and games this is already taken care of by default, derived from matchGame!
10591 return curRound & 1;
10595 NextTourneyGame (int nr, int *swapColors)
10596 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10598 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10600 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10601 tf = fopen(appData.tourneyFile, "r");
10602 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10603 ParseArgsFromFile(tf); fclose(tf);
10604 InitTimeControls(); // TC might be altered from tourney file
10606 nPlayers = CountPlayers(appData.participants); // count participants
10607 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10608 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10611 p = q = appData.results;
10612 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10613 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10614 DisplayMessage(_("Waiting for other game(s)"),"");
10615 waitingForGame = TRUE;
10616 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10619 waitingForGame = FALSE;
10622 if(appData.tourneyType < 0) {
10623 if(nr>=0 && !pairingReceived) {
10625 if(pairing.pr == NoProc) {
10626 if(!appData.pairingEngine[0]) {
10627 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10630 StartChessProgram(&pairing); // starts the pairing engine
10632 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10633 SendToProgram(buf, &pairing);
10634 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10635 SendToProgram(buf, &pairing);
10636 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10638 pairingReceived = 0; // ... so we continue here
10640 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10641 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10642 matchGame = 1; roundNr = nr / syncInterval + 1;
10645 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10647 // redefine engines, engine dir, etc.
10648 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10649 if(first.pr == NoProc) {
10650 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10651 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10653 if(second.pr == NoProc) {
10655 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10656 SwapEngines(1); // and make that valid for second engine by swapping
10657 InitEngine(&second, 1);
10659 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10660 UpdateLogos(FALSE); // leave display to ModeHiglight()
10666 { // performs game initialization that does not invoke engines, and then tries to start the game
10667 int res, firstWhite, swapColors = 0;
10668 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10669 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
10671 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10672 if(strcmp(buf, currentDebugFile)) { // name has changed
10673 FILE *f = fopen(buf, "w");
10674 if(f) { // if opening the new file failed, just keep using the old one
10675 ASSIGN(currentDebugFile, buf);
10679 if(appData.serverFileName) {
10680 if(serverFP) fclose(serverFP);
10681 serverFP = fopen(appData.serverFileName, "w");
10682 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10683 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10687 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10688 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10689 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10690 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10691 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10692 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10693 Reset(FALSE, first.pr != NoProc);
10694 res = LoadGameOrPosition(matchGame); // setup game
10695 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10696 if(!res) return; // abort when bad game/pos file
10697 TwoMachinesEvent();
10701 UserAdjudicationEvent (int result)
10703 ChessMove gameResult = GameIsDrawn;
10706 gameResult = WhiteWins;
10708 else if( result < 0 ) {
10709 gameResult = BlackWins;
10712 if( gameMode == TwoMachinesPlay ) {
10713 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10718 // [HGM] save: calculate checksum of game to make games easily identifiable
10720 StringCheckSum (char *s)
10723 if(s==NULL) return 0;
10724 while(*s) i = i*259 + *s++;
10732 for(i=backwardMostMove; i<forwardMostMove; i++) {
10733 sum += pvInfoList[i].depth;
10734 sum += StringCheckSum(parseList[i]);
10735 sum += StringCheckSum(commentList[i]);
10738 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10739 return sum + StringCheckSum(commentList[i]);
10740 } // end of save patch
10743 GameEnds (ChessMove result, char *resultDetails, int whosays)
10745 GameMode nextGameMode;
10747 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10749 if(endingGame) return; /* [HGM] crash: forbid recursion */
10751 if(twoBoards) { // [HGM] dual: switch back to one board
10752 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10753 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10755 if (appData.debugMode) {
10756 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10757 result, resultDetails ? resultDetails : "(null)", whosays);
10760 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10762 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10764 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10765 /* If we are playing on ICS, the server decides when the
10766 game is over, but the engine can offer to draw, claim
10770 if (appData.zippyPlay && first.initDone) {
10771 if (result == GameIsDrawn) {
10772 /* In case draw still needs to be claimed */
10773 SendToICS(ics_prefix);
10774 SendToICS("draw\n");
10775 } else if (StrCaseStr(resultDetails, "resign")) {
10776 SendToICS(ics_prefix);
10777 SendToICS("resign\n");
10781 endingGame = 0; /* [HGM] crash */
10785 /* If we're loading the game from a file, stop */
10786 if (whosays == GE_FILE) {
10787 (void) StopLoadGameTimer();
10791 /* Cancel draw offers */
10792 first.offeredDraw = second.offeredDraw = 0;
10794 /* If this is an ICS game, only ICS can really say it's done;
10795 if not, anyone can. */
10796 isIcsGame = (gameMode == IcsPlayingWhite ||
10797 gameMode == IcsPlayingBlack ||
10798 gameMode == IcsObserving ||
10799 gameMode == IcsExamining);
10801 if (!isIcsGame || whosays == GE_ICS) {
10802 /* OK -- not an ICS game, or ICS said it was done */
10804 if (!isIcsGame && !appData.noChessProgram)
10805 SetUserThinkingEnables();
10807 /* [HGM] if a machine claims the game end we verify this claim */
10808 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10809 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10811 ChessMove trueResult = (ChessMove) -1;
10813 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10814 first.twoMachinesColor[0] :
10815 second.twoMachinesColor[0] ;
10817 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10818 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10819 /* [HGM] verify: engine mate claims accepted if they were flagged */
10820 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10822 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10823 /* [HGM] verify: engine mate claims accepted if they were flagged */
10824 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10826 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10827 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10830 // now verify win claims, but not in drop games, as we don't understand those yet
10831 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10832 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10833 (result == WhiteWins && claimer == 'w' ||
10834 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10835 if (appData.debugMode) {
10836 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10837 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10839 if(result != trueResult) {
10840 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10841 result = claimer == 'w' ? BlackWins : WhiteWins;
10842 resultDetails = buf;
10845 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10846 && (forwardMostMove <= backwardMostMove ||
10847 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10848 (claimer=='b')==(forwardMostMove&1))
10850 /* [HGM] verify: draws that were not flagged are false claims */
10851 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10852 result = claimer == 'w' ? BlackWins : WhiteWins;
10853 resultDetails = buf;
10855 /* (Claiming a loss is accepted no questions asked!) */
10856 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10857 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10858 result = GameUnfinished;
10859 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10861 /* [HGM] bare: don't allow bare King to win */
10862 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10863 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10864 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10865 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10866 && result != GameIsDrawn)
10867 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10868 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10869 int p = (signed char)boards[forwardMostMove][i][j] - color;
10870 if(p >= 0 && p <= (int)WhiteKing) k++;
10872 if (appData.debugMode) {
10873 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10874 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10877 result = GameIsDrawn;
10878 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10879 resultDetails = buf;
10885 if(serverMoves != NULL && !loadFlag) { char c = '=';
10886 if(result==WhiteWins) c = '+';
10887 if(result==BlackWins) c = '-';
10888 if(resultDetails != NULL)
10889 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10891 if (resultDetails != NULL) {
10892 gameInfo.result = result;
10893 gameInfo.resultDetails = StrSave(resultDetails);
10895 /* display last move only if game was not loaded from file */
10896 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10897 DisplayMove(currentMove - 1);
10899 if (forwardMostMove != 0) {
10900 if (gameMode != PlayFromGameFile && gameMode != EditGame
10901 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10903 if (*appData.saveGameFile != NULLCHAR) {
10904 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10905 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10907 SaveGameToFile(appData.saveGameFile, TRUE);
10908 } else if (appData.autoSaveGames) {
10909 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10911 if (*appData.savePositionFile != NULLCHAR) {
10912 SavePositionToFile(appData.savePositionFile);
10914 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10918 /* Tell program how game ended in case it is learning */
10919 /* [HGM] Moved this to after saving the PGN, just in case */
10920 /* engine died and we got here through time loss. In that */
10921 /* case we will get a fatal error writing the pipe, which */
10922 /* would otherwise lose us the PGN. */
10923 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10924 /* output during GameEnds should never be fatal anymore */
10925 if (gameMode == MachinePlaysWhite ||
10926 gameMode == MachinePlaysBlack ||
10927 gameMode == TwoMachinesPlay ||
10928 gameMode == IcsPlayingWhite ||
10929 gameMode == IcsPlayingBlack ||
10930 gameMode == BeginningOfGame) {
10932 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10934 if (first.pr != NoProc) {
10935 SendToProgram(buf, &first);
10937 if (second.pr != NoProc &&
10938 gameMode == TwoMachinesPlay) {
10939 SendToProgram(buf, &second);
10944 if (appData.icsActive) {
10945 if (appData.quietPlay &&
10946 (gameMode == IcsPlayingWhite ||
10947 gameMode == IcsPlayingBlack)) {
10948 SendToICS(ics_prefix);
10949 SendToICS("set shout 1\n");
10951 nextGameMode = IcsIdle;
10952 ics_user_moved = FALSE;
10953 /* clean up premove. It's ugly when the game has ended and the
10954 * premove highlights are still on the board.
10957 gotPremove = FALSE;
10958 ClearPremoveHighlights();
10959 DrawPosition(FALSE, boards[currentMove]);
10961 if (whosays == GE_ICS) {
10964 if (gameMode == IcsPlayingWhite)
10966 else if(gameMode == IcsPlayingBlack)
10967 PlayIcsLossSound();
10970 if (gameMode == IcsPlayingBlack)
10972 else if(gameMode == IcsPlayingWhite)
10973 PlayIcsLossSound();
10976 PlayIcsDrawSound();
10979 PlayIcsUnfinishedSound();
10982 if(appData.quitNext) { ExitEvent(0); return; }
10983 } else if (gameMode == EditGame ||
10984 gameMode == PlayFromGameFile ||
10985 gameMode == AnalyzeMode ||
10986 gameMode == AnalyzeFile) {
10987 nextGameMode = gameMode;
10989 nextGameMode = EndOfGame;
10994 nextGameMode = gameMode;
10997 if (appData.noChessProgram) {
10998 gameMode = nextGameMode;
11000 endingGame = 0; /* [HGM] crash */
11005 /* Put first chess program into idle state */
11006 if (first.pr != NoProc &&
11007 (gameMode == MachinePlaysWhite ||
11008 gameMode == MachinePlaysBlack ||
11009 gameMode == TwoMachinesPlay ||
11010 gameMode == IcsPlayingWhite ||
11011 gameMode == IcsPlayingBlack ||
11012 gameMode == BeginningOfGame)) {
11013 SendToProgram("force\n", &first);
11014 if (first.usePing) {
11016 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11017 SendToProgram(buf, &first);
11020 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11021 /* Kill off first chess program */
11022 if (first.isr != NULL)
11023 RemoveInputSource(first.isr);
11026 if (first.pr != NoProc) {
11028 DoSleep( appData.delayBeforeQuit );
11029 SendToProgram("quit\n", &first);
11030 DoSleep( appData.delayAfterQuit );
11031 DestroyChildProcess(first.pr, first.useSigterm);
11032 first.reload = TRUE;
11036 if (second.reuse) {
11037 /* Put second chess program into idle state */
11038 if (second.pr != NoProc &&
11039 gameMode == TwoMachinesPlay) {
11040 SendToProgram("force\n", &second);
11041 if (second.usePing) {
11043 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11044 SendToProgram(buf, &second);
11047 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11048 /* Kill off second chess program */
11049 if (second.isr != NULL)
11050 RemoveInputSource(second.isr);
11053 if (second.pr != NoProc) {
11054 DoSleep( appData.delayBeforeQuit );
11055 SendToProgram("quit\n", &second);
11056 DoSleep( appData.delayAfterQuit );
11057 DestroyChildProcess(second.pr, second.useSigterm);
11058 second.reload = TRUE;
11060 second.pr = NoProc;
11063 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11064 char resChar = '=';
11068 if (first.twoMachinesColor[0] == 'w') {
11071 second.matchWins++;
11076 if (first.twoMachinesColor[0] == 'b') {
11079 second.matchWins++;
11082 case GameUnfinished:
11088 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11089 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11090 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11091 ReserveGame(nextGame, resChar); // sets nextGame
11092 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11093 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11094 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11096 if (nextGame <= appData.matchGames && !abortMatch) {
11097 gameMode = nextGameMode;
11098 matchGame = nextGame; // this will be overruled in tourney mode!
11099 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11100 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11101 endingGame = 0; /* [HGM] crash */
11104 gameMode = nextGameMode;
11105 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11106 first.tidy, second.tidy,
11107 first.matchWins, second.matchWins,
11108 appData.matchGames - (first.matchWins + second.matchWins));
11109 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11110 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11111 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11112 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11113 first.twoMachinesColor = "black\n";
11114 second.twoMachinesColor = "white\n";
11116 first.twoMachinesColor = "white\n";
11117 second.twoMachinesColor = "black\n";
11121 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11122 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11124 gameMode = nextGameMode;
11126 endingGame = 0; /* [HGM] crash */
11127 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11128 if(matchMode == TRUE) { // match through command line: exit with or without popup
11130 ToNrEvent(forwardMostMove);
11131 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11133 } else DisplayFatalError(buf, 0, 0);
11134 } else { // match through menu; just stop, with or without popup
11135 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11138 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11139 } else DisplayNote(buf);
11141 if(ranking) free(ranking);
11145 /* Assumes program was just initialized (initString sent).
11146 Leaves program in force mode. */
11148 FeedMovesToProgram (ChessProgramState *cps, int upto)
11152 if (appData.debugMode)
11153 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11154 startedFromSetupPosition ? "position and " : "",
11155 backwardMostMove, upto, cps->which);
11156 if(currentlyInitializedVariant != gameInfo.variant) {
11158 // [HGM] variantswitch: make engine aware of new variant
11159 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11160 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11161 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11162 SendToProgram(buf, cps);
11163 currentlyInitializedVariant = gameInfo.variant;
11165 SendToProgram("force\n", cps);
11166 if (startedFromSetupPosition) {
11167 SendBoard(cps, backwardMostMove);
11168 if (appData.debugMode) {
11169 fprintf(debugFP, "feedMoves\n");
11172 for (i = backwardMostMove; i < upto; i++) {
11173 SendMoveToProgram(i, cps);
11179 ResurrectChessProgram ()
11181 /* The chess program may have exited.
11182 If so, restart it and feed it all the moves made so far. */
11183 static int doInit = 0;
11185 if (appData.noChessProgram) return 1;
11187 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11188 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11189 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11190 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11192 if (first.pr != NoProc) return 1;
11193 StartChessProgram(&first);
11195 InitChessProgram(&first, FALSE);
11196 FeedMovesToProgram(&first, currentMove);
11198 if (!first.sendTime) {
11199 /* can't tell gnuchess what its clock should read,
11200 so we bow to its notion. */
11202 timeRemaining[0][currentMove] = whiteTimeRemaining;
11203 timeRemaining[1][currentMove] = blackTimeRemaining;
11206 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11207 appData.icsEngineAnalyze) && first.analysisSupport) {
11208 SendToProgram("analyze\n", &first);
11209 first.analyzing = TRUE;
11215 * Button procedures
11218 Reset (int redraw, int init)
11222 if (appData.debugMode) {
11223 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11224 redraw, init, gameMode);
11226 CleanupTail(); // [HGM] vari: delete any stored variations
11227 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11228 pausing = pauseExamInvalid = FALSE;
11229 startedFromSetupPosition = blackPlaysFirst = FALSE;
11231 whiteFlag = blackFlag = FALSE;
11232 userOfferedDraw = FALSE;
11233 hintRequested = bookRequested = FALSE;
11234 first.maybeThinking = FALSE;
11235 second.maybeThinking = FALSE;
11236 first.bookSuspend = FALSE; // [HGM] book
11237 second.bookSuspend = FALSE;
11238 thinkOutput[0] = NULLCHAR;
11239 lastHint[0] = NULLCHAR;
11240 ClearGameInfo(&gameInfo);
11241 gameInfo.variant = StringToVariant(appData.variant);
11242 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11243 ics_user_moved = ics_clock_paused = FALSE;
11244 ics_getting_history = H_FALSE;
11246 white_holding[0] = black_holding[0] = NULLCHAR;
11247 ClearProgramStats();
11248 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11252 flipView = appData.flipView;
11253 ClearPremoveHighlights();
11254 gotPremove = FALSE;
11255 alarmSounded = FALSE;
11257 GameEnds(EndOfFile, NULL, GE_PLAYER);
11258 if(appData.serverMovesName != NULL) {
11259 /* [HGM] prepare to make moves file for broadcasting */
11260 clock_t t = clock();
11261 if(serverMoves != NULL) fclose(serverMoves);
11262 serverMoves = fopen(appData.serverMovesName, "r");
11263 if(serverMoves != NULL) {
11264 fclose(serverMoves);
11265 /* delay 15 sec before overwriting, so all clients can see end */
11266 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11268 serverMoves = fopen(appData.serverMovesName, "w");
11272 gameMode = BeginningOfGame;
11274 if(appData.icsActive) gameInfo.variant = VariantNormal;
11275 currentMove = forwardMostMove = backwardMostMove = 0;
11276 MarkTargetSquares(1);
11277 InitPosition(redraw);
11278 for (i = 0; i < MAX_MOVES; i++) {
11279 if (commentList[i] != NULL) {
11280 free(commentList[i]);
11281 commentList[i] = NULL;
11285 timeRemaining[0][0] = whiteTimeRemaining;
11286 timeRemaining[1][0] = blackTimeRemaining;
11288 if (first.pr == NoProc) {
11289 StartChessProgram(&first);
11292 InitChessProgram(&first, startedFromSetupPosition);
11295 DisplayMessage("", "");
11296 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11297 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11298 ClearMap(); // [HGM] exclude: invalidate map
11302 AutoPlayGameLoop ()
11305 if (!AutoPlayOneMove())
11307 if (matchMode || appData.timeDelay == 0)
11309 if (appData.timeDelay < 0)
11311 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11319 ReloadGame(1); // next game
11325 int fromX, fromY, toX, toY;
11327 if (appData.debugMode) {
11328 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11331 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11334 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11335 pvInfoList[currentMove].depth = programStats.depth;
11336 pvInfoList[currentMove].score = programStats.score;
11337 pvInfoList[currentMove].time = 0;
11338 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11339 else { // append analysis of final position as comment
11341 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11342 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11344 programStats.depth = 0;
11347 if (currentMove >= forwardMostMove) {
11348 if(gameMode == AnalyzeFile) {
11349 if(appData.loadGameIndex == -1) {
11350 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11351 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11353 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11356 // gameMode = EndOfGame;
11357 // ModeHighlight();
11359 /* [AS] Clear current move marker at the end of a game */
11360 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11365 toX = moveList[currentMove][2] - AAA;
11366 toY = moveList[currentMove][3] - ONE;
11368 if (moveList[currentMove][1] == '@') {
11369 if (appData.highlightLastMove) {
11370 SetHighlights(-1, -1, toX, toY);
11373 fromX = moveList[currentMove][0] - AAA;
11374 fromY = moveList[currentMove][1] - ONE;
11376 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11378 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11380 if (appData.highlightLastMove) {
11381 SetHighlights(fromX, fromY, toX, toY);
11384 DisplayMove(currentMove);
11385 SendMoveToProgram(currentMove++, &first);
11386 DisplayBothClocks();
11387 DrawPosition(FALSE, boards[currentMove]);
11388 // [HGM] PV info: always display, routine tests if empty
11389 DisplayComment(currentMove - 1, commentList[currentMove]);
11395 LoadGameOneMove (ChessMove readAhead)
11397 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11398 char promoChar = NULLCHAR;
11399 ChessMove moveType;
11400 char move[MSG_SIZ];
11403 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11404 gameMode != AnalyzeMode && gameMode != Training) {
11409 yyboardindex = forwardMostMove;
11410 if (readAhead != EndOfFile) {
11411 moveType = readAhead;
11413 if (gameFileFP == NULL)
11415 moveType = (ChessMove) Myylex();
11419 switch (moveType) {
11421 if (appData.debugMode)
11422 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11425 /* append the comment but don't display it */
11426 AppendComment(currentMove, p, FALSE);
11429 case WhiteCapturesEnPassant:
11430 case BlackCapturesEnPassant:
11431 case WhitePromotion:
11432 case BlackPromotion:
11433 case WhiteNonPromotion:
11434 case BlackNonPromotion:
11436 case WhiteKingSideCastle:
11437 case WhiteQueenSideCastle:
11438 case BlackKingSideCastle:
11439 case BlackQueenSideCastle:
11440 case WhiteKingSideCastleWild:
11441 case WhiteQueenSideCastleWild:
11442 case BlackKingSideCastleWild:
11443 case BlackQueenSideCastleWild:
11445 case WhiteHSideCastleFR:
11446 case WhiteASideCastleFR:
11447 case BlackHSideCastleFR:
11448 case BlackASideCastleFR:
11450 if (appData.debugMode)
11451 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11452 fromX = currentMoveString[0] - AAA;
11453 fromY = currentMoveString[1] - ONE;
11454 toX = currentMoveString[2] - AAA;
11455 toY = currentMoveString[3] - ONE;
11456 promoChar = currentMoveString[4];
11461 if (appData.debugMode)
11462 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11463 fromX = moveType == WhiteDrop ?
11464 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11465 (int) CharToPiece(ToLower(currentMoveString[0]));
11467 toX = currentMoveString[2] - AAA;
11468 toY = currentMoveString[3] - ONE;
11474 case GameUnfinished:
11475 if (appData.debugMode)
11476 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11477 p = strchr(yy_text, '{');
11478 if (p == NULL) p = strchr(yy_text, '(');
11481 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11483 q = strchr(p, *p == '{' ? '}' : ')');
11484 if (q != NULL) *q = NULLCHAR;
11487 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11488 GameEnds(moveType, p, GE_FILE);
11490 if (cmailMsgLoaded) {
11492 flipView = WhiteOnMove(currentMove);
11493 if (moveType == GameUnfinished) flipView = !flipView;
11494 if (appData.debugMode)
11495 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11500 if (appData.debugMode)
11501 fprintf(debugFP, "Parser hit end of file\n");
11502 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11508 if (WhiteOnMove(currentMove)) {
11509 GameEnds(BlackWins, "Black mates", GE_FILE);
11511 GameEnds(WhiteWins, "White mates", GE_FILE);
11515 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11521 case MoveNumberOne:
11522 if (lastLoadGameStart == GNUChessGame) {
11523 /* GNUChessGames have numbers, but they aren't move numbers */
11524 if (appData.debugMode)
11525 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11526 yy_text, (int) moveType);
11527 return LoadGameOneMove(EndOfFile); /* tail recursion */
11529 /* else fall thru */
11534 /* Reached start of next game in file */
11535 if (appData.debugMode)
11536 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11537 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11543 if (WhiteOnMove(currentMove)) {
11544 GameEnds(BlackWins, "Black mates", GE_FILE);
11546 GameEnds(WhiteWins, "White mates", GE_FILE);
11550 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11556 case PositionDiagram: /* should not happen; ignore */
11557 case ElapsedTime: /* ignore */
11558 case NAG: /* ignore */
11559 if (appData.debugMode)
11560 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11561 yy_text, (int) moveType);
11562 return LoadGameOneMove(EndOfFile); /* tail recursion */
11565 if (appData.testLegality) {
11566 if (appData.debugMode)
11567 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11568 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11569 (forwardMostMove / 2) + 1,
11570 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11571 DisplayError(move, 0);
11574 if (appData.debugMode)
11575 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11576 yy_text, currentMoveString);
11577 fromX = currentMoveString[0] - AAA;
11578 fromY = currentMoveString[1] - ONE;
11579 toX = currentMoveString[2] - AAA;
11580 toY = currentMoveString[3] - ONE;
11581 promoChar = currentMoveString[4];
11585 case AmbiguousMove:
11586 if (appData.debugMode)
11587 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11588 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11589 (forwardMostMove / 2) + 1,
11590 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11591 DisplayError(move, 0);
11596 case ImpossibleMove:
11597 if (appData.debugMode)
11598 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11599 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11600 (forwardMostMove / 2) + 1,
11601 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11602 DisplayError(move, 0);
11608 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11609 DrawPosition(FALSE, boards[currentMove]);
11610 DisplayBothClocks();
11611 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11612 DisplayComment(currentMove - 1, commentList[currentMove]);
11614 (void) StopLoadGameTimer();
11616 cmailOldMove = forwardMostMove;
11619 /* currentMoveString is set as a side-effect of yylex */
11621 thinkOutput[0] = NULLCHAR;
11622 MakeMove(fromX, fromY, toX, toY, promoChar);
11623 currentMove = forwardMostMove;
11628 /* Load the nth game from the given file */
11630 LoadGameFromFile (char *filename, int n, char *title, int useList)
11635 if (strcmp(filename, "-") == 0) {
11639 f = fopen(filename, "rb");
11641 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11642 DisplayError(buf, errno);
11646 if (fseek(f, 0, 0) == -1) {
11647 /* f is not seekable; probably a pipe */
11650 if (useList && n == 0) {
11651 int error = GameListBuild(f);
11653 DisplayError(_("Cannot build game list"), error);
11654 } else if (!ListEmpty(&gameList) &&
11655 ((ListGame *) gameList.tailPred)->number > 1) {
11656 GameListPopUp(f, title);
11663 return LoadGame(f, n, title, FALSE);
11668 MakeRegisteredMove ()
11670 int fromX, fromY, toX, toY;
11672 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11673 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11676 if (appData.debugMode)
11677 fprintf(debugFP, "Restoring %s for game %d\n",
11678 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11680 thinkOutput[0] = NULLCHAR;
11681 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11682 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11683 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11684 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11685 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11686 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11687 MakeMove(fromX, fromY, toX, toY, promoChar);
11688 ShowMove(fromX, fromY, toX, toY);
11690 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11697 if (WhiteOnMove(currentMove)) {
11698 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11700 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11705 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11712 if (WhiteOnMove(currentMove)) {
11713 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11715 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11720 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11731 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11733 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11737 if (gameNumber > nCmailGames) {
11738 DisplayError(_("No more games in this message"), 0);
11741 if (f == lastLoadGameFP) {
11742 int offset = gameNumber - lastLoadGameNumber;
11744 cmailMsg[0] = NULLCHAR;
11745 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11746 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11747 nCmailMovesRegistered--;
11749 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11750 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11751 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11754 if (! RegisterMove()) return FALSE;
11758 retVal = LoadGame(f, gameNumber, title, useList);
11760 /* Make move registered during previous look at this game, if any */
11761 MakeRegisteredMove();
11763 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11764 commentList[currentMove]
11765 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11766 DisplayComment(currentMove - 1, commentList[currentMove]);
11772 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11774 ReloadGame (int offset)
11776 int gameNumber = lastLoadGameNumber + offset;
11777 if (lastLoadGameFP == NULL) {
11778 DisplayError(_("No game has been loaded yet"), 0);
11781 if (gameNumber <= 0) {
11782 DisplayError(_("Can't back up any further"), 0);
11785 if (cmailMsgLoaded) {
11786 return CmailLoadGame(lastLoadGameFP, gameNumber,
11787 lastLoadGameTitle, lastLoadGameUseList);
11789 return LoadGame(lastLoadGameFP, gameNumber,
11790 lastLoadGameTitle, lastLoadGameUseList);
11794 int keys[EmptySquare+1];
11797 PositionMatches (Board b1, Board b2)
11800 switch(appData.searchMode) {
11801 case 1: return CompareWithRights(b1, b2);
11803 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11804 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11808 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11809 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11810 sum += keys[b1[r][f]] - keys[b2[r][f]];
11814 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11815 sum += keys[b1[r][f]] - keys[b2[r][f]];
11827 int pieceList[256], quickBoard[256];
11828 ChessSquare pieceType[256] = { EmptySquare };
11829 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11830 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11831 int soughtTotal, turn;
11832 Boolean epOK, flipSearch;
11835 unsigned char piece, to;
11838 #define DSIZE (250000)
11840 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11841 Move *moveDatabase = initialSpace;
11842 unsigned int movePtr, dataSize = DSIZE;
11845 MakePieceList (Board board, int *counts)
11847 int r, f, n=Q_PROMO, total=0;
11848 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11849 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11850 int sq = f + (r<<4);
11851 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11852 quickBoard[sq] = ++n;
11854 pieceType[n] = board[r][f];
11855 counts[board[r][f]]++;
11856 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11857 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11861 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11866 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11868 int sq = fromX + (fromY<<4);
11869 int piece = quickBoard[sq];
11870 quickBoard[sq] = 0;
11871 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11872 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11873 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11874 moveDatabase[movePtr++].piece = Q_WCASTL;
11875 quickBoard[sq] = piece;
11876 piece = quickBoard[from]; quickBoard[from] = 0;
11877 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11879 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11880 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11881 moveDatabase[movePtr++].piece = Q_BCASTL;
11882 quickBoard[sq] = piece;
11883 piece = quickBoard[from]; quickBoard[from] = 0;
11884 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11886 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11887 quickBoard[(fromY<<4)+toX] = 0;
11888 moveDatabase[movePtr].piece = Q_EP;
11889 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11890 moveDatabase[movePtr].to = sq;
11892 if(promoPiece != pieceType[piece]) {
11893 moveDatabase[movePtr++].piece = Q_PROMO;
11894 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11896 moveDatabase[movePtr].piece = piece;
11897 quickBoard[sq] = piece;
11902 PackGame (Board board)
11904 Move *newSpace = NULL;
11905 moveDatabase[movePtr].piece = 0; // terminate previous game
11906 if(movePtr > dataSize) {
11907 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11908 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11909 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11912 Move *p = moveDatabase, *q = newSpace;
11913 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11914 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11915 moveDatabase = newSpace;
11916 } else { // calloc failed, we must be out of memory. Too bad...
11917 dataSize = 0; // prevent calloc events for all subsequent games
11918 return 0; // and signal this one isn't cached
11922 MakePieceList(board, counts);
11927 QuickCompare (Board board, int *minCounts, int *maxCounts)
11928 { // compare according to search mode
11930 switch(appData.searchMode)
11932 case 1: // exact position match
11933 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11934 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11935 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11938 case 2: // can have extra material on empty squares
11939 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11940 if(board[r][f] == EmptySquare) continue;
11941 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11944 case 3: // material with exact Pawn structure
11945 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11946 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11947 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11948 } // fall through to material comparison
11949 case 4: // exact material
11950 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11952 case 6: // material range with given imbalance
11953 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11954 // fall through to range comparison
11955 case 5: // material range
11956 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11962 QuickScan (Board board, Move *move)
11963 { // reconstruct game,and compare all positions in it
11964 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11966 int piece = move->piece;
11967 int to = move->to, from = pieceList[piece];
11968 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11969 if(!piece) return -1;
11970 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11971 piece = (++move)->piece;
11972 from = pieceList[piece];
11973 counts[pieceType[piece]]--;
11974 pieceType[piece] = (ChessSquare) move->to;
11975 counts[move->to]++;
11976 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11977 counts[pieceType[quickBoard[to]]]--;
11978 quickBoard[to] = 0; total--;
11981 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11982 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11983 from = pieceList[piece]; // so this must be King
11984 quickBoard[from] = 0;
11985 pieceList[piece] = to;
11986 from = pieceList[(++move)->piece]; // for FRC this has to be done here
11987 quickBoard[from] = 0; // rook
11988 quickBoard[to] = piece;
11989 to = move->to; piece = move->piece;
11993 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11994 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11995 quickBoard[from] = 0;
11997 quickBoard[to] = piece;
11998 pieceList[piece] = to;
12000 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12001 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12002 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12003 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12005 static int lastCounts[EmptySquare+1];
12007 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12008 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12009 } else stretch = 0;
12010 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12019 flipSearch = FALSE;
12020 CopyBoard(soughtBoard, boards[currentMove]);
12021 soughtTotal = MakePieceList(soughtBoard, maxSought);
12022 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12023 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12024 CopyBoard(reverseBoard, boards[currentMove]);
12025 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12026 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12027 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12028 reverseBoard[r][f] = piece;
12030 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12031 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12032 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12033 || (boards[currentMove][CASTLING][2] == NoRights ||
12034 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12035 && (boards[currentMove][CASTLING][5] == NoRights ||
12036 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12039 CopyBoard(flipBoard, soughtBoard);
12040 CopyBoard(rotateBoard, reverseBoard);
12041 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12042 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12043 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12046 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12047 if(appData.searchMode >= 5) {
12048 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12049 MakePieceList(soughtBoard, minSought);
12050 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12052 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12053 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12056 GameInfo dummyInfo;
12057 static int creatingBook;
12060 GameContainsPosition (FILE *f, ListGame *lg)
12062 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12063 int fromX, fromY, toX, toY;
12065 static int initDone=FALSE;
12067 // weed out games based on numerical tag comparison
12068 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12069 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12070 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12071 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12073 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12076 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
12077 else CopyBoard(boards[scratch], initialPosition); // default start position
12080 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12081 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12084 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12085 fseek(f, lg->offset, 0);
12088 yyboardindex = scratch;
12089 quickFlag = plyNr+1;
12094 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12100 if(plyNr) return -1; // after we have seen moves, this is for new game
12103 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12104 case ImpossibleMove:
12105 case WhiteWins: // game ends here with these four
12108 case GameUnfinished:
12112 if(appData.testLegality) return -1;
12113 case WhiteCapturesEnPassant:
12114 case BlackCapturesEnPassant:
12115 case WhitePromotion:
12116 case BlackPromotion:
12117 case WhiteNonPromotion:
12118 case BlackNonPromotion:
12120 case WhiteKingSideCastle:
12121 case WhiteQueenSideCastle:
12122 case BlackKingSideCastle:
12123 case BlackQueenSideCastle:
12124 case WhiteKingSideCastleWild:
12125 case WhiteQueenSideCastleWild:
12126 case BlackKingSideCastleWild:
12127 case BlackQueenSideCastleWild:
12128 case WhiteHSideCastleFR:
12129 case WhiteASideCastleFR:
12130 case BlackHSideCastleFR:
12131 case BlackASideCastleFR:
12132 fromX = currentMoveString[0] - AAA;
12133 fromY = currentMoveString[1] - ONE;
12134 toX = currentMoveString[2] - AAA;
12135 toY = currentMoveString[3] - ONE;
12136 promoChar = currentMoveString[4];
12140 fromX = next == WhiteDrop ?
12141 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12142 (int) CharToPiece(ToLower(currentMoveString[0]));
12144 toX = currentMoveString[2] - AAA;
12145 toY = currentMoveString[3] - ONE;
12149 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12151 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12152 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12153 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12154 if(appData.findMirror) {
12155 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12156 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12161 /* Load the nth game from open file f */
12163 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12167 int gn = gameNumber;
12168 ListGame *lg = NULL;
12169 int numPGNTags = 0;
12171 GameMode oldGameMode;
12172 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12174 if (appData.debugMode)
12175 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12177 if (gameMode == Training )
12178 SetTrainingModeOff();
12180 oldGameMode = gameMode;
12181 if (gameMode != BeginningOfGame) {
12182 Reset(FALSE, TRUE);
12186 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12187 fclose(lastLoadGameFP);
12191 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12194 fseek(f, lg->offset, 0);
12195 GameListHighlight(gameNumber);
12196 pos = lg->position;
12200 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12201 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12203 DisplayError(_("Game number out of range"), 0);
12208 if (fseek(f, 0, 0) == -1) {
12209 if (f == lastLoadGameFP ?
12210 gameNumber == lastLoadGameNumber + 1 :
12214 DisplayError(_("Can't seek on game file"), 0);
12219 lastLoadGameFP = f;
12220 lastLoadGameNumber = gameNumber;
12221 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12222 lastLoadGameUseList = useList;
12226 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12227 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12228 lg->gameInfo.black);
12230 } else if (*title != NULLCHAR) {
12231 if (gameNumber > 1) {
12232 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12235 DisplayTitle(title);
12239 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12240 gameMode = PlayFromGameFile;
12244 currentMove = forwardMostMove = backwardMostMove = 0;
12245 CopyBoard(boards[0], initialPosition);
12249 * Skip the first gn-1 games in the file.
12250 * Also skip over anything that precedes an identifiable
12251 * start of game marker, to avoid being confused by
12252 * garbage at the start of the file. Currently
12253 * recognized start of game markers are the move number "1",
12254 * the pattern "gnuchess .* game", the pattern
12255 * "^[#;%] [^ ]* game file", and a PGN tag block.
12256 * A game that starts with one of the latter two patterns
12257 * will also have a move number 1, possibly
12258 * following a position diagram.
12259 * 5-4-02: Let's try being more lenient and allowing a game to
12260 * start with an unnumbered move. Does that break anything?
12262 cm = lastLoadGameStart = EndOfFile;
12264 yyboardindex = forwardMostMove;
12265 cm = (ChessMove) Myylex();
12268 if (cmailMsgLoaded) {
12269 nCmailGames = CMAIL_MAX_GAMES - gn;
12272 DisplayError(_("Game not found in file"), 0);
12279 lastLoadGameStart = cm;
12282 case MoveNumberOne:
12283 switch (lastLoadGameStart) {
12288 case MoveNumberOne:
12290 gn--; /* count this game */
12291 lastLoadGameStart = cm;
12300 switch (lastLoadGameStart) {
12303 case MoveNumberOne:
12305 gn--; /* count this game */
12306 lastLoadGameStart = cm;
12309 lastLoadGameStart = cm; /* game counted already */
12317 yyboardindex = forwardMostMove;
12318 cm = (ChessMove) Myylex();
12319 } while (cm == PGNTag || cm == Comment);
12326 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12327 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12328 != CMAIL_OLD_RESULT) {
12330 cmailResult[ CMAIL_MAX_GAMES
12331 - gn - 1] = CMAIL_OLD_RESULT;
12337 /* Only a NormalMove can be at the start of a game
12338 * without a position diagram. */
12339 if (lastLoadGameStart == EndOfFile ) {
12341 lastLoadGameStart = MoveNumberOne;
12350 if (appData.debugMode)
12351 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12353 if (cm == XBoardGame) {
12354 /* Skip any header junk before position diagram and/or move 1 */
12356 yyboardindex = forwardMostMove;
12357 cm = (ChessMove) Myylex();
12359 if (cm == EndOfFile ||
12360 cm == GNUChessGame || cm == XBoardGame) {
12361 /* Empty game; pretend end-of-file and handle later */
12366 if (cm == MoveNumberOne || cm == PositionDiagram ||
12367 cm == PGNTag || cm == Comment)
12370 } else if (cm == GNUChessGame) {
12371 if (gameInfo.event != NULL) {
12372 free(gameInfo.event);
12374 gameInfo.event = StrSave(yy_text);
12377 startedFromSetupPosition = FALSE;
12378 while (cm == PGNTag) {
12379 if (appData.debugMode)
12380 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12381 err = ParsePGNTag(yy_text, &gameInfo);
12382 if (!err) numPGNTags++;
12384 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12385 if(gameInfo.variant != oldVariant) {
12386 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12387 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12388 InitPosition(TRUE);
12389 oldVariant = gameInfo.variant;
12390 if (appData.debugMode)
12391 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12395 if (gameInfo.fen != NULL) {
12396 Board initial_position;
12397 startedFromSetupPosition = TRUE;
12398 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12400 DisplayError(_("Bad FEN position in file"), 0);
12403 CopyBoard(boards[0], initial_position);
12404 if (blackPlaysFirst) {
12405 currentMove = forwardMostMove = backwardMostMove = 1;
12406 CopyBoard(boards[1], initial_position);
12407 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12408 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12409 timeRemaining[0][1] = whiteTimeRemaining;
12410 timeRemaining[1][1] = blackTimeRemaining;
12411 if (commentList[0] != NULL) {
12412 commentList[1] = commentList[0];
12413 commentList[0] = NULL;
12416 currentMove = forwardMostMove = backwardMostMove = 0;
12418 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12420 initialRulePlies = FENrulePlies;
12421 for( i=0; i< nrCastlingRights; i++ )
12422 initialRights[i] = initial_position[CASTLING][i];
12424 yyboardindex = forwardMostMove;
12425 free(gameInfo.fen);
12426 gameInfo.fen = NULL;
12429 yyboardindex = forwardMostMove;
12430 cm = (ChessMove) Myylex();
12432 /* Handle comments interspersed among the tags */
12433 while (cm == Comment) {
12435 if (appData.debugMode)
12436 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12438 AppendComment(currentMove, p, FALSE);
12439 yyboardindex = forwardMostMove;
12440 cm = (ChessMove) Myylex();
12444 /* don't rely on existence of Event tag since if game was
12445 * pasted from clipboard the Event tag may not exist
12447 if (numPGNTags > 0){
12449 if (gameInfo.variant == VariantNormal) {
12450 VariantClass v = StringToVariant(gameInfo.event);
12451 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12452 if(v < VariantShogi) gameInfo.variant = v;
12455 if( appData.autoDisplayTags ) {
12456 tags = PGNTags(&gameInfo);
12457 TagsPopUp(tags, CmailMsg());
12462 /* Make something up, but don't display it now */
12467 if (cm == PositionDiagram) {
12470 Board initial_position;
12472 if (appData.debugMode)
12473 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12475 if (!startedFromSetupPosition) {
12477 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12478 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12489 initial_position[i][j++] = CharToPiece(*p);
12492 while (*p == ' ' || *p == '\t' ||
12493 *p == '\n' || *p == '\r') p++;
12495 if (strncmp(p, "black", strlen("black"))==0)
12496 blackPlaysFirst = TRUE;
12498 blackPlaysFirst = FALSE;
12499 startedFromSetupPosition = TRUE;
12501 CopyBoard(boards[0], initial_position);
12502 if (blackPlaysFirst) {
12503 currentMove = forwardMostMove = backwardMostMove = 1;
12504 CopyBoard(boards[1], initial_position);
12505 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12506 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12507 timeRemaining[0][1] = whiteTimeRemaining;
12508 timeRemaining[1][1] = blackTimeRemaining;
12509 if (commentList[0] != NULL) {
12510 commentList[1] = commentList[0];
12511 commentList[0] = NULL;
12514 currentMove = forwardMostMove = backwardMostMove = 0;
12517 yyboardindex = forwardMostMove;
12518 cm = (ChessMove) Myylex();
12521 if(!creatingBook) {
12522 if (first.pr == NoProc) {
12523 StartChessProgram(&first);
12525 InitChessProgram(&first, FALSE);
12526 SendToProgram("force\n", &first);
12527 if (startedFromSetupPosition) {
12528 SendBoard(&first, forwardMostMove);
12529 if (appData.debugMode) {
12530 fprintf(debugFP, "Load Game\n");
12532 DisplayBothClocks();
12536 /* [HGM] server: flag to write setup moves in broadcast file as one */
12537 loadFlag = appData.suppressLoadMoves;
12539 while (cm == Comment) {
12541 if (appData.debugMode)
12542 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12544 AppendComment(currentMove, p, FALSE);
12545 yyboardindex = forwardMostMove;
12546 cm = (ChessMove) Myylex();
12549 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12550 cm == WhiteWins || cm == BlackWins ||
12551 cm == GameIsDrawn || cm == GameUnfinished) {
12552 DisplayMessage("", _("No moves in game"));
12553 if (cmailMsgLoaded) {
12554 if (appData.debugMode)
12555 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12559 DrawPosition(FALSE, boards[currentMove]);
12560 DisplayBothClocks();
12561 gameMode = EditGame;
12568 // [HGM] PV info: routine tests if comment empty
12569 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12570 DisplayComment(currentMove - 1, commentList[currentMove]);
12572 if (!matchMode && appData.timeDelay != 0)
12573 DrawPosition(FALSE, boards[currentMove]);
12575 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12576 programStats.ok_to_send = 1;
12579 /* if the first token after the PGN tags is a move
12580 * and not move number 1, retrieve it from the parser
12582 if (cm != MoveNumberOne)
12583 LoadGameOneMove(cm);
12585 /* load the remaining moves from the file */
12586 while (LoadGameOneMove(EndOfFile)) {
12587 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12588 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12591 /* rewind to the start of the game */
12592 currentMove = backwardMostMove;
12594 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12596 if (oldGameMode == AnalyzeFile) {
12597 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12598 AnalyzeFileEvent();
12600 if (oldGameMode == AnalyzeMode) {
12601 AnalyzeFileEvent();
12604 if(creatingBook) return TRUE;
12605 if (!matchMode && pos > 0) {
12606 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12608 if (matchMode || appData.timeDelay == 0) {
12610 } else if (appData.timeDelay > 0) {
12611 AutoPlayGameLoop();
12614 if (appData.debugMode)
12615 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12617 loadFlag = 0; /* [HGM] true game starts */
12621 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12623 ReloadPosition (int offset)
12625 int positionNumber = lastLoadPositionNumber + offset;
12626 if (lastLoadPositionFP == NULL) {
12627 DisplayError(_("No position has been loaded yet"), 0);
12630 if (positionNumber <= 0) {
12631 DisplayError(_("Can't back up any further"), 0);
12634 return LoadPosition(lastLoadPositionFP, positionNumber,
12635 lastLoadPositionTitle);
12638 /* Load the nth position from the given file */
12640 LoadPositionFromFile (char *filename, int n, char *title)
12645 if (strcmp(filename, "-") == 0) {
12646 return LoadPosition(stdin, n, "stdin");
12648 f = fopen(filename, "rb");
12650 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12651 DisplayError(buf, errno);
12654 return LoadPosition(f, n, title);
12659 /* Load the nth position from the given open file, and close it */
12661 LoadPosition (FILE *f, int positionNumber, char *title)
12663 char *p, line[MSG_SIZ];
12664 Board initial_position;
12665 int i, j, fenMode, pn;
12667 if (gameMode == Training )
12668 SetTrainingModeOff();
12670 if (gameMode != BeginningOfGame) {
12671 Reset(FALSE, TRUE);
12673 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12674 fclose(lastLoadPositionFP);
12676 if (positionNumber == 0) positionNumber = 1;
12677 lastLoadPositionFP = f;
12678 lastLoadPositionNumber = positionNumber;
12679 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12680 if (first.pr == NoProc && !appData.noChessProgram) {
12681 StartChessProgram(&first);
12682 InitChessProgram(&first, FALSE);
12684 pn = positionNumber;
12685 if (positionNumber < 0) {
12686 /* Negative position number means to seek to that byte offset */
12687 if (fseek(f, -positionNumber, 0) == -1) {
12688 DisplayError(_("Can't seek on position file"), 0);
12693 if (fseek(f, 0, 0) == -1) {
12694 if (f == lastLoadPositionFP ?
12695 positionNumber == lastLoadPositionNumber + 1 :
12696 positionNumber == 1) {
12699 DisplayError(_("Can't seek on position file"), 0);
12704 /* See if this file is FEN or old-style xboard */
12705 if (fgets(line, MSG_SIZ, f) == NULL) {
12706 DisplayError(_("Position not found in file"), 0);
12709 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12710 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12713 if (fenMode || line[0] == '#') pn--;
12715 /* skip positions before number pn */
12716 if (fgets(line, MSG_SIZ, f) == NULL) {
12718 DisplayError(_("Position not found in file"), 0);
12721 if (fenMode || line[0] == '#') pn--;
12726 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12727 DisplayError(_("Bad FEN position in file"), 0);
12731 (void) fgets(line, MSG_SIZ, f);
12732 (void) fgets(line, MSG_SIZ, f);
12734 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12735 (void) fgets(line, MSG_SIZ, f);
12736 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12739 initial_position[i][j++] = CharToPiece(*p);
12743 blackPlaysFirst = FALSE;
12745 (void) fgets(line, MSG_SIZ, f);
12746 if (strncmp(line, "black", strlen("black"))==0)
12747 blackPlaysFirst = TRUE;
12750 startedFromSetupPosition = TRUE;
12752 CopyBoard(boards[0], initial_position);
12753 if (blackPlaysFirst) {
12754 currentMove = forwardMostMove = backwardMostMove = 1;
12755 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12756 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12757 CopyBoard(boards[1], initial_position);
12758 DisplayMessage("", _("Black to play"));
12760 currentMove = forwardMostMove = backwardMostMove = 0;
12761 DisplayMessage("", _("White to play"));
12763 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12764 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12765 SendToProgram("force\n", &first);
12766 SendBoard(&first, forwardMostMove);
12768 if (appData.debugMode) {
12770 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12771 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12772 fprintf(debugFP, "Load Position\n");
12775 if (positionNumber > 1) {
12776 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12777 DisplayTitle(line);
12779 DisplayTitle(title);
12781 gameMode = EditGame;
12784 timeRemaining[0][1] = whiteTimeRemaining;
12785 timeRemaining[1][1] = blackTimeRemaining;
12786 DrawPosition(FALSE, boards[currentMove]);
12793 CopyPlayerNameIntoFileName (char **dest, char *src)
12795 while (*src != NULLCHAR && *src != ',') {
12800 *(*dest)++ = *src++;
12806 DefaultFileName (char *ext)
12808 static char def[MSG_SIZ];
12811 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12813 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12815 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12817 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12824 /* Save the current game to the given file */
12826 SaveGameToFile (char *filename, int append)
12830 int result, i, t,tot=0;
12832 if (strcmp(filename, "-") == 0) {
12833 return SaveGame(stdout, 0, NULL);
12835 for(i=0; i<10; i++) { // upto 10 tries
12836 f = fopen(filename, append ? "a" : "w");
12837 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12838 if(f || errno != 13) break;
12839 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12843 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12844 DisplayError(buf, errno);
12847 safeStrCpy(buf, lastMsg, MSG_SIZ);
12848 DisplayMessage(_("Waiting for access to save file"), "");
12849 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12850 DisplayMessage(_("Saving game"), "");
12851 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12852 result = SaveGame(f, 0, NULL);
12853 DisplayMessage(buf, "");
12860 SavePart (char *str)
12862 static char buf[MSG_SIZ];
12865 p = strchr(str, ' ');
12866 if (p == NULL) return str;
12867 strncpy(buf, str, p - str);
12868 buf[p - str] = NULLCHAR;
12872 #define PGN_MAX_LINE 75
12874 #define PGN_SIDE_WHITE 0
12875 #define PGN_SIDE_BLACK 1
12878 FindFirstMoveOutOfBook (int side)
12882 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12883 int index = backwardMostMove;
12884 int has_book_hit = 0;
12886 if( (index % 2) != side ) {
12890 while( index < forwardMostMove ) {
12891 /* Check to see if engine is in book */
12892 int depth = pvInfoList[index].depth;
12893 int score = pvInfoList[index].score;
12899 else if( score == 0 && depth == 63 ) {
12900 in_book = 1; /* Zappa */
12902 else if( score == 2 && depth == 99 ) {
12903 in_book = 1; /* Abrok */
12906 has_book_hit += in_book;
12922 GetOutOfBookInfo (char * buf)
12926 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12928 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12929 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12933 if( oob[0] >= 0 || oob[1] >= 0 ) {
12934 for( i=0; i<2; i++ ) {
12938 if( i > 0 && oob[0] >= 0 ) {
12939 strcat( buf, " " );
12942 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12943 sprintf( buf+strlen(buf), "%s%.2f",
12944 pvInfoList[idx].score >= 0 ? "+" : "",
12945 pvInfoList[idx].score / 100.0 );
12951 /* Save game in PGN style and close the file */
12953 SaveGamePGN (FILE *f)
12955 int i, offset, linelen, newblock;
12958 int movelen, numlen, blank;
12959 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12961 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12963 PrintPGNTags(f, &gameInfo);
12965 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12967 if (backwardMostMove > 0 || startedFromSetupPosition) {
12968 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12969 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12970 fprintf(f, "\n{--------------\n");
12971 PrintPosition(f, backwardMostMove);
12972 fprintf(f, "--------------}\n");
12976 /* [AS] Out of book annotation */
12977 if( appData.saveOutOfBookInfo ) {
12980 GetOutOfBookInfo( buf );
12982 if( buf[0] != '\0' ) {
12983 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12990 i = backwardMostMove;
12994 while (i < forwardMostMove) {
12995 /* Print comments preceding this move */
12996 if (commentList[i] != NULL) {
12997 if (linelen > 0) fprintf(f, "\n");
12998 fprintf(f, "%s", commentList[i]);
13003 /* Format move number */
13005 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13008 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13010 numtext[0] = NULLCHAR;
13012 numlen = strlen(numtext);
13015 /* Print move number */
13016 blank = linelen > 0 && numlen > 0;
13017 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13026 fprintf(f, "%s", numtext);
13030 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13031 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13034 blank = linelen > 0 && movelen > 0;
13035 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13044 fprintf(f, "%s", move_buffer);
13045 linelen += movelen;
13047 /* [AS] Add PV info if present */
13048 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13049 /* [HGM] add time */
13050 char buf[MSG_SIZ]; int seconds;
13052 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13058 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13061 seconds = (seconds + 4)/10; // round to full seconds
13063 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13065 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13068 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13069 pvInfoList[i].score >= 0 ? "+" : "",
13070 pvInfoList[i].score / 100.0,
13071 pvInfoList[i].depth,
13074 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13076 /* Print score/depth */
13077 blank = linelen > 0 && movelen > 0;
13078 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13087 fprintf(f, "%s", move_buffer);
13088 linelen += movelen;
13094 /* Start a new line */
13095 if (linelen > 0) fprintf(f, "\n");
13097 /* Print comments after last move */
13098 if (commentList[i] != NULL) {
13099 fprintf(f, "%s\n", commentList[i]);
13103 if (gameInfo.resultDetails != NULL &&
13104 gameInfo.resultDetails[0] != NULLCHAR) {
13105 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
13106 PGNResult(gameInfo.result));
13108 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13112 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13116 /* Save game in old style and close the file */
13118 SaveGameOldStyle (FILE *f)
13123 tm = time((time_t *) NULL);
13125 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13128 if (backwardMostMove > 0 || startedFromSetupPosition) {
13129 fprintf(f, "\n[--------------\n");
13130 PrintPosition(f, backwardMostMove);
13131 fprintf(f, "--------------]\n");
13136 i = backwardMostMove;
13137 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13139 while (i < forwardMostMove) {
13140 if (commentList[i] != NULL) {
13141 fprintf(f, "[%s]\n", commentList[i]);
13144 if ((i % 2) == 1) {
13145 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13148 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13150 if (commentList[i] != NULL) {
13154 if (i >= forwardMostMove) {
13158 fprintf(f, "%s\n", parseList[i]);
13163 if (commentList[i] != NULL) {
13164 fprintf(f, "[%s]\n", commentList[i]);
13167 /* This isn't really the old style, but it's close enough */
13168 if (gameInfo.resultDetails != NULL &&
13169 gameInfo.resultDetails[0] != NULLCHAR) {
13170 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13171 gameInfo.resultDetails);
13173 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13180 /* Save the current game to open file f and close the file */
13182 SaveGame (FILE *f, int dummy, char *dummy2)
13184 if (gameMode == EditPosition) EditPositionDone(TRUE);
13185 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13186 if (appData.oldSaveStyle)
13187 return SaveGameOldStyle(f);
13189 return SaveGamePGN(f);
13192 /* Save the current position to the given file */
13194 SavePositionToFile (char *filename)
13199 if (strcmp(filename, "-") == 0) {
13200 return SavePosition(stdout, 0, NULL);
13202 f = fopen(filename, "a");
13204 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13205 DisplayError(buf, errno);
13208 safeStrCpy(buf, lastMsg, MSG_SIZ);
13209 DisplayMessage(_("Waiting for access to save file"), "");
13210 flock(fileno(f), LOCK_EX); // [HGM] lock
13211 DisplayMessage(_("Saving position"), "");
13212 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13213 SavePosition(f, 0, NULL);
13214 DisplayMessage(buf, "");
13220 /* Save the current position to the given open file and close the file */
13222 SavePosition (FILE *f, int dummy, char *dummy2)
13227 if (gameMode == EditPosition) EditPositionDone(TRUE);
13228 if (appData.oldSaveStyle) {
13229 tm = time((time_t *) NULL);
13231 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13233 fprintf(f, "[--------------\n");
13234 PrintPosition(f, currentMove);
13235 fprintf(f, "--------------]\n");
13237 fen = PositionToFEN(currentMove, NULL, 1);
13238 fprintf(f, "%s\n", fen);
13246 ReloadCmailMsgEvent (int unregister)
13249 static char *inFilename = NULL;
13250 static char *outFilename;
13252 struct stat inbuf, outbuf;
13255 /* Any registered moves are unregistered if unregister is set, */
13256 /* i.e. invoked by the signal handler */
13258 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13259 cmailMoveRegistered[i] = FALSE;
13260 if (cmailCommentList[i] != NULL) {
13261 free(cmailCommentList[i]);
13262 cmailCommentList[i] = NULL;
13265 nCmailMovesRegistered = 0;
13268 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13269 cmailResult[i] = CMAIL_NOT_RESULT;
13273 if (inFilename == NULL) {
13274 /* Because the filenames are static they only get malloced once */
13275 /* and they never get freed */
13276 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13277 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13279 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13280 sprintf(outFilename, "%s.out", appData.cmailGameName);
13283 status = stat(outFilename, &outbuf);
13285 cmailMailedMove = FALSE;
13287 status = stat(inFilename, &inbuf);
13288 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13291 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13292 counts the games, notes how each one terminated, etc.
13294 It would be nice to remove this kludge and instead gather all
13295 the information while building the game list. (And to keep it
13296 in the game list nodes instead of having a bunch of fixed-size
13297 parallel arrays.) Note this will require getting each game's
13298 termination from the PGN tags, as the game list builder does
13299 not process the game moves. --mann
13301 cmailMsgLoaded = TRUE;
13302 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13304 /* Load first game in the file or popup game menu */
13305 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13307 #endif /* !WIN32 */
13315 char string[MSG_SIZ];
13317 if ( cmailMailedMove
13318 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13319 return TRUE; /* Allow free viewing */
13322 /* Unregister move to ensure that we don't leave RegisterMove */
13323 /* with the move registered when the conditions for registering no */
13325 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13326 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13327 nCmailMovesRegistered --;
13329 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13331 free(cmailCommentList[lastLoadGameNumber - 1]);
13332 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13336 if (cmailOldMove == -1) {
13337 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13341 if (currentMove > cmailOldMove + 1) {
13342 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13346 if (currentMove < cmailOldMove) {
13347 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13351 if (forwardMostMove > currentMove) {
13352 /* Silently truncate extra moves */
13356 if ( (currentMove == cmailOldMove + 1)
13357 || ( (currentMove == cmailOldMove)
13358 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13359 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13360 if (gameInfo.result != GameUnfinished) {
13361 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13364 if (commentList[currentMove] != NULL) {
13365 cmailCommentList[lastLoadGameNumber - 1]
13366 = StrSave(commentList[currentMove]);
13368 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13370 if (appData.debugMode)
13371 fprintf(debugFP, "Saving %s for game %d\n",
13372 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13374 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13376 f = fopen(string, "w");
13377 if (appData.oldSaveStyle) {
13378 SaveGameOldStyle(f); /* also closes the file */
13380 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13381 f = fopen(string, "w");
13382 SavePosition(f, 0, NULL); /* also closes the file */
13384 fprintf(f, "{--------------\n");
13385 PrintPosition(f, currentMove);
13386 fprintf(f, "--------------}\n\n");
13388 SaveGame(f, 0, NULL); /* also closes the file*/
13391 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13392 nCmailMovesRegistered ++;
13393 } else if (nCmailGames == 1) {
13394 DisplayError(_("You have not made a move yet"), 0);
13405 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13406 FILE *commandOutput;
13407 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13408 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13414 if (! cmailMsgLoaded) {
13415 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13419 if (nCmailGames == nCmailResults) {
13420 DisplayError(_("No unfinished games"), 0);
13424 #if CMAIL_PROHIBIT_REMAIL
13425 if (cmailMailedMove) {
13426 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);
13427 DisplayError(msg, 0);
13432 if (! (cmailMailedMove || RegisterMove())) return;
13434 if ( cmailMailedMove
13435 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13436 snprintf(string, MSG_SIZ, partCommandString,
13437 appData.debugMode ? " -v" : "", appData.cmailGameName);
13438 commandOutput = popen(string, "r");
13440 if (commandOutput == NULL) {
13441 DisplayError(_("Failed to invoke cmail"), 0);
13443 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13444 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13446 if (nBuffers > 1) {
13447 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13448 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13449 nBytes = MSG_SIZ - 1;
13451 (void) memcpy(msg, buffer, nBytes);
13453 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13455 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13456 cmailMailedMove = TRUE; /* Prevent >1 moves */
13459 for (i = 0; i < nCmailGames; i ++) {
13460 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13465 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13467 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13469 appData.cmailGameName,
13471 LoadGameFromFile(buffer, 1, buffer, FALSE);
13472 cmailMsgLoaded = FALSE;
13476 DisplayInformation(msg);
13477 pclose(commandOutput);
13480 if ((*cmailMsg) != '\0') {
13481 DisplayInformation(cmailMsg);
13486 #endif /* !WIN32 */
13495 int prependComma = 0;
13497 char string[MSG_SIZ]; /* Space for game-list */
13500 if (!cmailMsgLoaded) return "";
13502 if (cmailMailedMove) {
13503 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13505 /* Create a list of games left */
13506 snprintf(string, MSG_SIZ, "[");
13507 for (i = 0; i < nCmailGames; i ++) {
13508 if (! ( cmailMoveRegistered[i]
13509 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13510 if (prependComma) {
13511 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13513 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13517 strcat(string, number);
13520 strcat(string, "]");
13522 if (nCmailMovesRegistered + nCmailResults == 0) {
13523 switch (nCmailGames) {
13525 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13529 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13533 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13538 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13540 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13545 if (nCmailResults == nCmailGames) {
13546 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13548 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13553 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13565 if (gameMode == Training)
13566 SetTrainingModeOff();
13569 cmailMsgLoaded = FALSE;
13570 if (appData.icsActive) {
13571 SendToICS(ics_prefix);
13572 SendToICS("refresh\n");
13577 ExitEvent (int status)
13581 /* Give up on clean exit */
13585 /* Keep trying for clean exit */
13589 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13591 if (telnetISR != NULL) {
13592 RemoveInputSource(telnetISR);
13594 if (icsPR != NoProc) {
13595 DestroyChildProcess(icsPR, TRUE);
13598 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13599 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13601 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13602 /* make sure this other one finishes before killing it! */
13603 if(endingGame) { int count = 0;
13604 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13605 while(endingGame && count++ < 10) DoSleep(1);
13606 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13609 /* Kill off chess programs */
13610 if (first.pr != NoProc) {
13613 DoSleep( appData.delayBeforeQuit );
13614 SendToProgram("quit\n", &first);
13615 DoSleep( appData.delayAfterQuit );
13616 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13618 if (second.pr != NoProc) {
13619 DoSleep( appData.delayBeforeQuit );
13620 SendToProgram("quit\n", &second);
13621 DoSleep( appData.delayAfterQuit );
13622 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13624 if (first.isr != NULL) {
13625 RemoveInputSource(first.isr);
13627 if (second.isr != NULL) {
13628 RemoveInputSource(second.isr);
13631 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13632 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13634 ShutDownFrontEnd();
13639 PauseEngine (ChessProgramState *cps)
13641 SendToProgram("pause\n", cps);
13646 UnPauseEngine (ChessProgramState *cps)
13648 SendToProgram("resume\n", cps);
13655 if (appData.debugMode)
13656 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13660 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13662 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13663 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13664 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13666 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13667 HandleMachineMove(stashedInputMove, stalledEngine);
13668 stalledEngine = NULL;
13671 if (gameMode == MachinePlaysWhite ||
13672 gameMode == TwoMachinesPlay ||
13673 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13674 if(first.pause) UnPauseEngine(&first);
13675 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13676 if(second.pause) UnPauseEngine(&second);
13677 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13680 DisplayBothClocks();
13682 if (gameMode == PlayFromGameFile) {
13683 if (appData.timeDelay >= 0)
13684 AutoPlayGameLoop();
13685 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13686 Reset(FALSE, TRUE);
13687 SendToICS(ics_prefix);
13688 SendToICS("refresh\n");
13689 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13690 ForwardInner(forwardMostMove);
13692 pauseExamInvalid = FALSE;
13694 switch (gameMode) {
13698 pauseExamForwardMostMove = forwardMostMove;
13699 pauseExamInvalid = FALSE;
13702 case IcsPlayingWhite:
13703 case IcsPlayingBlack:
13707 case PlayFromGameFile:
13708 (void) StopLoadGameTimer();
13712 case BeginningOfGame:
13713 if (appData.icsActive) return;
13714 /* else fall through */
13715 case MachinePlaysWhite:
13716 case MachinePlaysBlack:
13717 case TwoMachinesPlay:
13718 if (forwardMostMove == 0)
13719 return; /* don't pause if no one has moved */
13720 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13721 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13722 if(onMove->pause) { // thinking engine can be paused
13723 PauseEngine(onMove); // do it
13724 if(onMove->other->pause) // pondering opponent can always be paused immediately
13725 PauseEngine(onMove->other);
13727 SendToProgram("easy\n", onMove->other);
13729 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13730 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13732 PauseEngine(&first);
13734 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13735 } else { // human on move, pause pondering by either method
13737 PauseEngine(&first);
13738 else if(appData.ponderNextMove)
13739 SendToProgram("easy\n", &first);
13742 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13752 EditCommentEvent ()
13754 char title[MSG_SIZ];
13756 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13757 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13759 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13760 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13761 parseList[currentMove - 1]);
13764 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13771 char *tags = PGNTags(&gameInfo);
13773 EditTagsPopUp(tags, NULL);
13780 if(second.analyzing) {
13781 SendToProgram("exit\n", &second);
13782 second.analyzing = FALSE;
13784 if (second.pr == NoProc) StartChessProgram(&second);
13785 InitChessProgram(&second, FALSE);
13786 FeedMovesToProgram(&second, currentMove);
13788 SendToProgram("analyze\n", &second);
13789 second.analyzing = TRUE;
13793 /* Toggle ShowThinking */
13795 ToggleShowThinking()
13797 appData.showThinking = !appData.showThinking;
13798 ShowThinkingEvent();
13802 AnalyzeModeEvent ()
13806 if (!first.analysisSupport) {
13807 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13808 DisplayError(buf, 0);
13811 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13812 if (appData.icsActive) {
13813 if (gameMode != IcsObserving) {
13814 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13815 DisplayError(buf, 0);
13817 if (appData.icsEngineAnalyze) {
13818 if (appData.debugMode)
13819 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13825 /* if enable, user wants to disable icsEngineAnalyze */
13826 if (appData.icsEngineAnalyze) {
13831 appData.icsEngineAnalyze = TRUE;
13832 if (appData.debugMode)
13833 fprintf(debugFP, "ICS engine analyze starting... \n");
13836 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13837 if (appData.noChessProgram || gameMode == AnalyzeMode)
13840 if (gameMode != AnalyzeFile) {
13841 if (!appData.icsEngineAnalyze) {
13843 if (gameMode != EditGame) return 0;
13845 if (!appData.showThinking) ToggleShowThinking();
13846 ResurrectChessProgram();
13847 SendToProgram("analyze\n", &first);
13848 first.analyzing = TRUE;
13849 /*first.maybeThinking = TRUE;*/
13850 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13851 EngineOutputPopUp();
13853 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13858 StartAnalysisClock();
13859 GetTimeMark(&lastNodeCountTime);
13865 AnalyzeFileEvent ()
13867 if (appData.noChessProgram || gameMode == AnalyzeFile)
13870 if (!first.analysisSupport) {
13872 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13873 DisplayError(buf, 0);
13877 if (gameMode != AnalyzeMode) {
13878 keepInfo = 1; // mere annotating should not alter PGN tags
13881 if (gameMode != EditGame) return;
13882 if (!appData.showThinking) ToggleShowThinking();
13883 ResurrectChessProgram();
13884 SendToProgram("analyze\n", &first);
13885 first.analyzing = TRUE;
13886 /*first.maybeThinking = TRUE;*/
13887 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13888 EngineOutputPopUp();
13890 gameMode = AnalyzeFile;
13894 StartAnalysisClock();
13895 GetTimeMark(&lastNodeCountTime);
13897 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13898 AnalysisPeriodicEvent(1);
13902 MachineWhiteEvent ()
13905 char *bookHit = NULL;
13907 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13911 if (gameMode == PlayFromGameFile ||
13912 gameMode == TwoMachinesPlay ||
13913 gameMode == Training ||
13914 gameMode == AnalyzeMode ||
13915 gameMode == EndOfGame)
13918 if (gameMode == EditPosition)
13919 EditPositionDone(TRUE);
13921 if (!WhiteOnMove(currentMove)) {
13922 DisplayError(_("It is not White's turn"), 0);
13926 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13929 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13930 gameMode == AnalyzeFile)
13933 ResurrectChessProgram(); /* in case it isn't running */
13934 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13935 gameMode = MachinePlaysWhite;
13938 gameMode = MachinePlaysWhite;
13942 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13944 if (first.sendName) {
13945 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13946 SendToProgram(buf, &first);
13948 if (first.sendTime) {
13949 if (first.useColors) {
13950 SendToProgram("black\n", &first); /*gnu kludge*/
13952 SendTimeRemaining(&first, TRUE);
13954 if (first.useColors) {
13955 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13957 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13958 SetMachineThinkingEnables();
13959 first.maybeThinking = TRUE;
13963 if (appData.autoFlipView && !flipView) {
13964 flipView = !flipView;
13965 DrawPosition(FALSE, NULL);
13966 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13969 if(bookHit) { // [HGM] book: simulate book reply
13970 static char bookMove[MSG_SIZ]; // a bit generous?
13972 programStats.nodes = programStats.depth = programStats.time =
13973 programStats.score = programStats.got_only_move = 0;
13974 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13976 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13977 strcat(bookMove, bookHit);
13978 HandleMachineMove(bookMove, &first);
13983 MachineBlackEvent ()
13986 char *bookHit = NULL;
13988 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13992 if (gameMode == PlayFromGameFile ||
13993 gameMode == TwoMachinesPlay ||
13994 gameMode == Training ||
13995 gameMode == AnalyzeMode ||
13996 gameMode == EndOfGame)
13999 if (gameMode == EditPosition)
14000 EditPositionDone(TRUE);
14002 if (WhiteOnMove(currentMove)) {
14003 DisplayError(_("It is not Black's turn"), 0);
14007 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14010 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14011 gameMode == AnalyzeFile)
14014 ResurrectChessProgram(); /* in case it isn't running */
14015 gameMode = MachinePlaysBlack;
14019 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14021 if (first.sendName) {
14022 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14023 SendToProgram(buf, &first);
14025 if (first.sendTime) {
14026 if (first.useColors) {
14027 SendToProgram("white\n", &first); /*gnu kludge*/
14029 SendTimeRemaining(&first, FALSE);
14031 if (first.useColors) {
14032 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14034 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14035 SetMachineThinkingEnables();
14036 first.maybeThinking = TRUE;
14039 if (appData.autoFlipView && flipView) {
14040 flipView = !flipView;
14041 DrawPosition(FALSE, NULL);
14042 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14044 if(bookHit) { // [HGM] book: simulate book reply
14045 static char bookMove[MSG_SIZ]; // a bit generous?
14047 programStats.nodes = programStats.depth = programStats.time =
14048 programStats.score = programStats.got_only_move = 0;
14049 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14051 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14052 strcat(bookMove, bookHit);
14053 HandleMachineMove(bookMove, &first);
14059 DisplayTwoMachinesTitle ()
14062 if (appData.matchGames > 0) {
14063 if(appData.tourneyFile[0]) {
14064 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14065 gameInfo.white, _("vs."), gameInfo.black,
14066 nextGame+1, appData.matchGames+1,
14067 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14069 if (first.twoMachinesColor[0] == 'w') {
14070 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14071 gameInfo.white, _("vs."), gameInfo.black,
14072 first.matchWins, second.matchWins,
14073 matchGame - 1 - (first.matchWins + second.matchWins));
14075 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14076 gameInfo.white, _("vs."), gameInfo.black,
14077 second.matchWins, first.matchWins,
14078 matchGame - 1 - (first.matchWins + second.matchWins));
14081 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14087 SettingsMenuIfReady ()
14089 if (second.lastPing != second.lastPong) {
14090 DisplayMessage("", _("Waiting for second chess program"));
14091 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14095 DisplayMessage("", "");
14096 SettingsPopUp(&second);
14100 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14103 if (cps->pr == NoProc) {
14104 StartChessProgram(cps);
14105 if (cps->protocolVersion == 1) {
14107 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14109 /* kludge: allow timeout for initial "feature" command */
14110 if(retry != TwoMachinesEventIfReady) FreezeUI();
14111 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14112 DisplayMessage("", buf);
14113 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14121 TwoMachinesEvent P((void))
14125 ChessProgramState *onmove;
14126 char *bookHit = NULL;
14127 static int stalling = 0;
14131 if (appData.noChessProgram) return;
14133 switch (gameMode) {
14134 case TwoMachinesPlay:
14136 case MachinePlaysWhite:
14137 case MachinePlaysBlack:
14138 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14139 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14143 case BeginningOfGame:
14144 case PlayFromGameFile:
14147 if (gameMode != EditGame) return;
14150 EditPositionDone(TRUE);
14161 // forwardMostMove = currentMove;
14162 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14163 startingEngine = TRUE;
14165 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14167 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14168 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14169 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14172 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14174 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14175 startingEngine = FALSE;
14176 DisplayError("second engine does not play this", 0);
14181 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14182 SendToProgram("force\n", &second);
14184 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14187 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14188 if(appData.matchPause>10000 || appData.matchPause<10)
14189 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14190 wait = SubtractTimeMarks(&now, &pauseStart);
14191 if(wait < appData.matchPause) {
14192 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14195 // we are now committed to starting the game
14197 DisplayMessage("", "");
14198 if (startedFromSetupPosition) {
14199 SendBoard(&second, backwardMostMove);
14200 if (appData.debugMode) {
14201 fprintf(debugFP, "Two Machines\n");
14204 for (i = backwardMostMove; i < forwardMostMove; i++) {
14205 SendMoveToProgram(i, &second);
14208 gameMode = TwoMachinesPlay;
14209 pausing = startingEngine = FALSE;
14210 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14212 DisplayTwoMachinesTitle();
14214 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14219 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14220 SendToProgram(first.computerString, &first);
14221 if (first.sendName) {
14222 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14223 SendToProgram(buf, &first);
14225 SendToProgram(second.computerString, &second);
14226 if (second.sendName) {
14227 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14228 SendToProgram(buf, &second);
14232 if (!first.sendTime || !second.sendTime) {
14233 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14234 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14236 if (onmove->sendTime) {
14237 if (onmove->useColors) {
14238 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14240 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14242 if (onmove->useColors) {
14243 SendToProgram(onmove->twoMachinesColor, onmove);
14245 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14246 // SendToProgram("go\n", onmove);
14247 onmove->maybeThinking = TRUE;
14248 SetMachineThinkingEnables();
14252 if(bookHit) { // [HGM] book: simulate book reply
14253 static char bookMove[MSG_SIZ]; // a bit generous?
14255 programStats.nodes = programStats.depth = programStats.time =
14256 programStats.score = programStats.got_only_move = 0;
14257 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14259 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14260 strcat(bookMove, bookHit);
14261 savedMessage = bookMove; // args for deferred call
14262 savedState = onmove;
14263 ScheduleDelayedEvent(DeferredBookMove, 1);
14270 if (gameMode == Training) {
14271 SetTrainingModeOff();
14272 gameMode = PlayFromGameFile;
14273 DisplayMessage("", _("Training mode off"));
14275 gameMode = Training;
14276 animateTraining = appData.animate;
14278 /* make sure we are not already at the end of the game */
14279 if (currentMove < forwardMostMove) {
14280 SetTrainingModeOn();
14281 DisplayMessage("", _("Training mode on"));
14283 gameMode = PlayFromGameFile;
14284 DisplayError(_("Already at end of game"), 0);
14293 if (!appData.icsActive) return;
14294 switch (gameMode) {
14295 case IcsPlayingWhite:
14296 case IcsPlayingBlack:
14299 case BeginningOfGame:
14307 EditPositionDone(TRUE);
14320 gameMode = IcsIdle;
14330 switch (gameMode) {
14332 SetTrainingModeOff();
14334 case MachinePlaysWhite:
14335 case MachinePlaysBlack:
14336 case BeginningOfGame:
14337 SendToProgram("force\n", &first);
14338 SetUserThinkingEnables();
14340 case PlayFromGameFile:
14341 (void) StopLoadGameTimer();
14342 if (gameFileFP != NULL) {
14347 EditPositionDone(TRUE);
14352 SendToProgram("force\n", &first);
14354 case TwoMachinesPlay:
14355 GameEnds(EndOfFile, NULL, GE_PLAYER);
14356 ResurrectChessProgram();
14357 SetUserThinkingEnables();
14360 ResurrectChessProgram();
14362 case IcsPlayingBlack:
14363 case IcsPlayingWhite:
14364 DisplayError(_("Warning: You are still playing a game"), 0);
14367 DisplayError(_("Warning: You are still observing a game"), 0);
14370 DisplayError(_("Warning: You are still examining a game"), 0);
14381 first.offeredDraw = second.offeredDraw = 0;
14383 if (gameMode == PlayFromGameFile) {
14384 whiteTimeRemaining = timeRemaining[0][currentMove];
14385 blackTimeRemaining = timeRemaining[1][currentMove];
14389 if (gameMode == MachinePlaysWhite ||
14390 gameMode == MachinePlaysBlack ||
14391 gameMode == TwoMachinesPlay ||
14392 gameMode == EndOfGame) {
14393 i = forwardMostMove;
14394 while (i > currentMove) {
14395 SendToProgram("undo\n", &first);
14398 if(!adjustedClock) {
14399 whiteTimeRemaining = timeRemaining[0][currentMove];
14400 blackTimeRemaining = timeRemaining[1][currentMove];
14401 DisplayBothClocks();
14403 if (whiteFlag || blackFlag) {
14404 whiteFlag = blackFlag = 0;
14409 gameMode = EditGame;
14416 EditPositionEvent ()
14418 if (gameMode == EditPosition) {
14424 if (gameMode != EditGame) return;
14426 gameMode = EditPosition;
14429 if (currentMove > 0)
14430 CopyBoard(boards[0], boards[currentMove]);
14432 blackPlaysFirst = !WhiteOnMove(currentMove);
14434 currentMove = forwardMostMove = backwardMostMove = 0;
14435 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14437 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14443 /* [DM] icsEngineAnalyze - possible call from other functions */
14444 if (appData.icsEngineAnalyze) {
14445 appData.icsEngineAnalyze = FALSE;
14447 DisplayMessage("",_("Close ICS engine analyze..."));
14449 if (first.analysisSupport && first.analyzing) {
14450 SendToBoth("exit\n");
14451 first.analyzing = second.analyzing = FALSE;
14453 thinkOutput[0] = NULLCHAR;
14457 EditPositionDone (Boolean fakeRights)
14459 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14461 startedFromSetupPosition = TRUE;
14462 InitChessProgram(&first, FALSE);
14463 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14464 boards[0][EP_STATUS] = EP_NONE;
14465 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14466 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14467 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14468 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14469 } else boards[0][CASTLING][2] = NoRights;
14470 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14471 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14472 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14473 } else boards[0][CASTLING][5] = NoRights;
14474 if(gameInfo.variant == VariantSChess) {
14476 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14477 boards[0][VIRGIN][i] = 0;
14478 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14479 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14483 SendToProgram("force\n", &first);
14484 if (blackPlaysFirst) {
14485 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14486 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14487 currentMove = forwardMostMove = backwardMostMove = 1;
14488 CopyBoard(boards[1], boards[0]);
14490 currentMove = forwardMostMove = backwardMostMove = 0;
14492 SendBoard(&first, forwardMostMove);
14493 if (appData.debugMode) {
14494 fprintf(debugFP, "EditPosDone\n");
14497 DisplayMessage("", "");
14498 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14499 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14500 gameMode = EditGame;
14502 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14503 ClearHighlights(); /* [AS] */
14506 /* Pause for `ms' milliseconds */
14507 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14509 TimeDelay (long ms)
14516 } while (SubtractTimeMarks(&m2, &m1) < ms);
14519 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14521 SendMultiLineToICS (char *buf)
14523 char temp[MSG_SIZ+1], *p;
14530 strncpy(temp, buf, len);
14535 if (*p == '\n' || *p == '\r')
14540 strcat(temp, "\n");
14542 SendToPlayer(temp, strlen(temp));
14546 SetWhiteToPlayEvent ()
14548 if (gameMode == EditPosition) {
14549 blackPlaysFirst = FALSE;
14550 DisplayBothClocks(); /* works because currentMove is 0 */
14551 } else if (gameMode == IcsExamining) {
14552 SendToICS(ics_prefix);
14553 SendToICS("tomove white\n");
14558 SetBlackToPlayEvent ()
14560 if (gameMode == EditPosition) {
14561 blackPlaysFirst = TRUE;
14562 currentMove = 1; /* kludge */
14563 DisplayBothClocks();
14565 } else if (gameMode == IcsExamining) {
14566 SendToICS(ics_prefix);
14567 SendToICS("tomove black\n");
14572 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14575 ChessSquare piece = boards[0][y][x];
14577 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14579 switch (selection) {
14581 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14582 SendToICS(ics_prefix);
14583 SendToICS("bsetup clear\n");
14584 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14585 SendToICS(ics_prefix);
14586 SendToICS("clearboard\n");
14588 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14589 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14590 for (y = 0; y < BOARD_HEIGHT; y++) {
14591 if (gameMode == IcsExamining) {
14592 if (boards[currentMove][y][x] != EmptySquare) {
14593 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14598 boards[0][y][x] = p;
14603 if (gameMode == EditPosition) {
14604 DrawPosition(FALSE, boards[0]);
14609 SetWhiteToPlayEvent();
14613 SetBlackToPlayEvent();
14617 if (gameMode == IcsExamining) {
14618 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14619 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14622 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14623 if(x == BOARD_LEFT-2) {
14624 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14625 boards[0][y][1] = 0;
14627 if(x == BOARD_RGHT+1) {
14628 if(y >= gameInfo.holdingsSize) break;
14629 boards[0][y][BOARD_WIDTH-2] = 0;
14632 boards[0][y][x] = EmptySquare;
14633 DrawPosition(FALSE, boards[0]);
14638 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14639 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14640 selection = (ChessSquare) (PROMOTED piece);
14641 } else if(piece == EmptySquare) selection = WhiteSilver;
14642 else selection = (ChessSquare)((int)piece - 1);
14646 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14647 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14648 selection = (ChessSquare) (DEMOTED piece);
14649 } else if(piece == EmptySquare) selection = BlackSilver;
14650 else selection = (ChessSquare)((int)piece + 1);
14655 if(gameInfo.variant == VariantShatranj ||
14656 gameInfo.variant == VariantXiangqi ||
14657 gameInfo.variant == VariantCourier ||
14658 gameInfo.variant == VariantASEAN ||
14659 gameInfo.variant == VariantMakruk )
14660 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14665 if(gameInfo.variant == VariantXiangqi)
14666 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14667 if(gameInfo.variant == VariantKnightmate)
14668 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14671 if (gameMode == IcsExamining) {
14672 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14673 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14674 PieceToChar(selection), AAA + x, ONE + y);
14677 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14679 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14680 n = PieceToNumber(selection - BlackPawn);
14681 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14682 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14683 boards[0][BOARD_HEIGHT-1-n][1]++;
14685 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14686 n = PieceToNumber(selection);
14687 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14688 boards[0][n][BOARD_WIDTH-1] = selection;
14689 boards[0][n][BOARD_WIDTH-2]++;
14692 boards[0][y][x] = selection;
14693 DrawPosition(TRUE, boards[0]);
14695 fromX = fromY = -1;
14703 DropMenuEvent (ChessSquare selection, int x, int y)
14705 ChessMove moveType;
14707 switch (gameMode) {
14708 case IcsPlayingWhite:
14709 case MachinePlaysBlack:
14710 if (!WhiteOnMove(currentMove)) {
14711 DisplayMoveError(_("It is Black's turn"));
14714 moveType = WhiteDrop;
14716 case IcsPlayingBlack:
14717 case MachinePlaysWhite:
14718 if (WhiteOnMove(currentMove)) {
14719 DisplayMoveError(_("It is White's turn"));
14722 moveType = BlackDrop;
14725 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14731 if (moveType == BlackDrop && selection < BlackPawn) {
14732 selection = (ChessSquare) ((int) selection
14733 + (int) BlackPawn - (int) WhitePawn);
14735 if (boards[currentMove][y][x] != EmptySquare) {
14736 DisplayMoveError(_("That square is occupied"));
14740 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14746 /* Accept a pending offer of any kind from opponent */
14748 if (appData.icsActive) {
14749 SendToICS(ics_prefix);
14750 SendToICS("accept\n");
14751 } else if (cmailMsgLoaded) {
14752 if (currentMove == cmailOldMove &&
14753 commentList[cmailOldMove] != NULL &&
14754 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14755 "Black offers a draw" : "White offers a draw")) {
14757 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14758 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14760 DisplayError(_("There is no pending offer on this move"), 0);
14761 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14764 /* Not used for offers from chess program */
14771 /* Decline a pending offer of any kind from opponent */
14773 if (appData.icsActive) {
14774 SendToICS(ics_prefix);
14775 SendToICS("decline\n");
14776 } else if (cmailMsgLoaded) {
14777 if (currentMove == cmailOldMove &&
14778 commentList[cmailOldMove] != NULL &&
14779 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14780 "Black offers a draw" : "White offers a draw")) {
14782 AppendComment(cmailOldMove, "Draw declined", TRUE);
14783 DisplayComment(cmailOldMove - 1, "Draw declined");
14786 DisplayError(_("There is no pending offer on this move"), 0);
14789 /* Not used for offers from chess program */
14796 /* Issue ICS rematch command */
14797 if (appData.icsActive) {
14798 SendToICS(ics_prefix);
14799 SendToICS("rematch\n");
14806 /* Call your opponent's flag (claim a win on time) */
14807 if (appData.icsActive) {
14808 SendToICS(ics_prefix);
14809 SendToICS("flag\n");
14811 switch (gameMode) {
14814 case MachinePlaysWhite:
14817 GameEnds(GameIsDrawn, "Both players ran out of time",
14820 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14822 DisplayError(_("Your opponent is not out of time"), 0);
14825 case MachinePlaysBlack:
14828 GameEnds(GameIsDrawn, "Both players ran out of time",
14831 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14833 DisplayError(_("Your opponent is not out of time"), 0);
14841 ClockClick (int which)
14842 { // [HGM] code moved to back-end from winboard.c
14843 if(which) { // black clock
14844 if (gameMode == EditPosition || gameMode == IcsExamining) {
14845 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14846 SetBlackToPlayEvent();
14847 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14848 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14849 } else if (shiftKey) {
14850 AdjustClock(which, -1);
14851 } else if (gameMode == IcsPlayingWhite ||
14852 gameMode == MachinePlaysBlack) {
14855 } else { // white clock
14856 if (gameMode == EditPosition || gameMode == IcsExamining) {
14857 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14858 SetWhiteToPlayEvent();
14859 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14860 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14861 } else if (shiftKey) {
14862 AdjustClock(which, -1);
14863 } else if (gameMode == IcsPlayingBlack ||
14864 gameMode == MachinePlaysWhite) {
14873 /* Offer draw or accept pending draw offer from opponent */
14875 if (appData.icsActive) {
14876 /* Note: tournament rules require draw offers to be
14877 made after you make your move but before you punch
14878 your clock. Currently ICS doesn't let you do that;
14879 instead, you immediately punch your clock after making
14880 a move, but you can offer a draw at any time. */
14882 SendToICS(ics_prefix);
14883 SendToICS("draw\n");
14884 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14885 } else if (cmailMsgLoaded) {
14886 if (currentMove == cmailOldMove &&
14887 commentList[cmailOldMove] != NULL &&
14888 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14889 "Black offers a draw" : "White offers a draw")) {
14890 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14891 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14892 } else if (currentMove == cmailOldMove + 1) {
14893 char *offer = WhiteOnMove(cmailOldMove) ?
14894 "White offers a draw" : "Black offers a draw";
14895 AppendComment(currentMove, offer, TRUE);
14896 DisplayComment(currentMove - 1, offer);
14897 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14899 DisplayError(_("You must make your move before offering a draw"), 0);
14900 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14902 } else if (first.offeredDraw) {
14903 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14905 if (first.sendDrawOffers) {
14906 SendToProgram("draw\n", &first);
14907 userOfferedDraw = TRUE;
14915 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14917 if (appData.icsActive) {
14918 SendToICS(ics_prefix);
14919 SendToICS("adjourn\n");
14921 /* Currently GNU Chess doesn't offer or accept Adjourns */
14929 /* Offer Abort or accept pending Abort offer from opponent */
14931 if (appData.icsActive) {
14932 SendToICS(ics_prefix);
14933 SendToICS("abort\n");
14935 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14942 /* Resign. You can do this even if it's not your turn. */
14944 if (appData.icsActive) {
14945 SendToICS(ics_prefix);
14946 SendToICS("resign\n");
14948 switch (gameMode) {
14949 case MachinePlaysWhite:
14950 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14952 case MachinePlaysBlack:
14953 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14956 if (cmailMsgLoaded) {
14958 if (WhiteOnMove(cmailOldMove)) {
14959 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14961 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14963 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14974 StopObservingEvent ()
14976 /* Stop observing current games */
14977 SendToICS(ics_prefix);
14978 SendToICS("unobserve\n");
14982 StopExaminingEvent ()
14984 /* Stop observing current game */
14985 SendToICS(ics_prefix);
14986 SendToICS("unexamine\n");
14990 ForwardInner (int target)
14992 int limit; int oldSeekGraphUp = seekGraphUp;
14994 if (appData.debugMode)
14995 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14996 target, currentMove, forwardMostMove);
14998 if (gameMode == EditPosition)
15001 seekGraphUp = FALSE;
15002 MarkTargetSquares(1);
15004 if (gameMode == PlayFromGameFile && !pausing)
15007 if (gameMode == IcsExamining && pausing)
15008 limit = pauseExamForwardMostMove;
15010 limit = forwardMostMove;
15012 if (target > limit) target = limit;
15014 if (target > 0 && moveList[target - 1][0]) {
15015 int fromX, fromY, toX, toY;
15016 toX = moveList[target - 1][2] - AAA;
15017 toY = moveList[target - 1][3] - ONE;
15018 if (moveList[target - 1][1] == '@') {
15019 if (appData.highlightLastMove) {
15020 SetHighlights(-1, -1, toX, toY);
15023 fromX = moveList[target - 1][0] - AAA;
15024 fromY = moveList[target - 1][1] - ONE;
15025 if (target == currentMove + 1) {
15026 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15028 if (appData.highlightLastMove) {
15029 SetHighlights(fromX, fromY, toX, toY);
15033 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15034 gameMode == Training || gameMode == PlayFromGameFile ||
15035 gameMode == AnalyzeFile) {
15036 while (currentMove < target) {
15037 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15038 SendMoveToProgram(currentMove++, &first);
15041 currentMove = target;
15044 if (gameMode == EditGame || gameMode == EndOfGame) {
15045 whiteTimeRemaining = timeRemaining[0][currentMove];
15046 blackTimeRemaining = timeRemaining[1][currentMove];
15048 DisplayBothClocks();
15049 DisplayMove(currentMove - 1);
15050 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15051 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15052 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15053 DisplayComment(currentMove - 1, commentList[currentMove]);
15055 ClearMap(); // [HGM] exclude: invalidate map
15062 if (gameMode == IcsExamining && !pausing) {
15063 SendToICS(ics_prefix);
15064 SendToICS("forward\n");
15066 ForwardInner(currentMove + 1);
15073 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15074 /* to optimze, we temporarily turn off analysis mode while we feed
15075 * the remaining moves to the engine. Otherwise we get analysis output
15078 if (first.analysisSupport) {
15079 SendToProgram("exit\nforce\n", &first);
15080 first.analyzing = FALSE;
15084 if (gameMode == IcsExamining && !pausing) {
15085 SendToICS(ics_prefix);
15086 SendToICS("forward 999999\n");
15088 ForwardInner(forwardMostMove);
15091 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15092 /* we have fed all the moves, so reactivate analysis mode */
15093 SendToProgram("analyze\n", &first);
15094 first.analyzing = TRUE;
15095 /*first.maybeThinking = TRUE;*/
15096 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15101 BackwardInner (int target)
15103 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15105 if (appData.debugMode)
15106 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15107 target, currentMove, forwardMostMove);
15109 if (gameMode == EditPosition) return;
15110 seekGraphUp = FALSE;
15111 MarkTargetSquares(1);
15112 if (currentMove <= backwardMostMove) {
15114 DrawPosition(full_redraw, boards[currentMove]);
15117 if (gameMode == PlayFromGameFile && !pausing)
15120 if (moveList[target][0]) {
15121 int fromX, fromY, toX, toY;
15122 toX = moveList[target][2] - AAA;
15123 toY = moveList[target][3] - ONE;
15124 if (moveList[target][1] == '@') {
15125 if (appData.highlightLastMove) {
15126 SetHighlights(-1, -1, toX, toY);
15129 fromX = moveList[target][0] - AAA;
15130 fromY = moveList[target][1] - ONE;
15131 if (target == currentMove - 1) {
15132 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15134 if (appData.highlightLastMove) {
15135 SetHighlights(fromX, fromY, toX, toY);
15139 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15140 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15141 while (currentMove > target) {
15142 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15143 // null move cannot be undone. Reload program with move history before it.
15145 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15146 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15148 SendBoard(&first, i);
15149 if(second.analyzing) SendBoard(&second, i);
15150 for(currentMove=i; currentMove<target; currentMove++) {
15151 SendMoveToProgram(currentMove, &first);
15152 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15156 SendToBoth("undo\n");
15160 currentMove = target;
15163 if (gameMode == EditGame || gameMode == EndOfGame) {
15164 whiteTimeRemaining = timeRemaining[0][currentMove];
15165 blackTimeRemaining = timeRemaining[1][currentMove];
15167 DisplayBothClocks();
15168 DisplayMove(currentMove - 1);
15169 DrawPosition(full_redraw, boards[currentMove]);
15170 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15171 // [HGM] PV info: routine tests if comment empty
15172 DisplayComment(currentMove - 1, commentList[currentMove]);
15173 ClearMap(); // [HGM] exclude: invalidate map
15179 if (gameMode == IcsExamining && !pausing) {
15180 SendToICS(ics_prefix);
15181 SendToICS("backward\n");
15183 BackwardInner(currentMove - 1);
15190 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15191 /* to optimize, we temporarily turn off analysis mode while we undo
15192 * all the moves. Otherwise we get analysis output after each undo.
15194 if (first.analysisSupport) {
15195 SendToProgram("exit\nforce\n", &first);
15196 first.analyzing = FALSE;
15200 if (gameMode == IcsExamining && !pausing) {
15201 SendToICS(ics_prefix);
15202 SendToICS("backward 999999\n");
15204 BackwardInner(backwardMostMove);
15207 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15208 /* we have fed all the moves, so reactivate analysis mode */
15209 SendToProgram("analyze\n", &first);
15210 first.analyzing = TRUE;
15211 /*first.maybeThinking = TRUE;*/
15212 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15219 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15220 if (to >= forwardMostMove) to = forwardMostMove;
15221 if (to <= backwardMostMove) to = backwardMostMove;
15222 if (to < currentMove) {
15230 RevertEvent (Boolean annotate)
15232 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15235 if (gameMode != IcsExamining) {
15236 DisplayError(_("You are not examining a game"), 0);
15240 DisplayError(_("You can't revert while pausing"), 0);
15243 SendToICS(ics_prefix);
15244 SendToICS("revert\n");
15248 RetractMoveEvent ()
15250 switch (gameMode) {
15251 case MachinePlaysWhite:
15252 case MachinePlaysBlack:
15253 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15254 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15257 if (forwardMostMove < 2) return;
15258 currentMove = forwardMostMove = forwardMostMove - 2;
15259 whiteTimeRemaining = timeRemaining[0][currentMove];
15260 blackTimeRemaining = timeRemaining[1][currentMove];
15261 DisplayBothClocks();
15262 DisplayMove(currentMove - 1);
15263 ClearHighlights();/*!! could figure this out*/
15264 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15265 SendToProgram("remove\n", &first);
15266 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15269 case BeginningOfGame:
15273 case IcsPlayingWhite:
15274 case IcsPlayingBlack:
15275 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15276 SendToICS(ics_prefix);
15277 SendToICS("takeback 2\n");
15279 SendToICS(ics_prefix);
15280 SendToICS("takeback 1\n");
15289 ChessProgramState *cps;
15291 switch (gameMode) {
15292 case MachinePlaysWhite:
15293 if (!WhiteOnMove(forwardMostMove)) {
15294 DisplayError(_("It is your turn"), 0);
15299 case MachinePlaysBlack:
15300 if (WhiteOnMove(forwardMostMove)) {
15301 DisplayError(_("It is your turn"), 0);
15306 case TwoMachinesPlay:
15307 if (WhiteOnMove(forwardMostMove) ==
15308 (first.twoMachinesColor[0] == 'w')) {
15314 case BeginningOfGame:
15318 SendToProgram("?\n", cps);
15322 TruncateGameEvent ()
15325 if (gameMode != EditGame) return;
15332 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15333 if (forwardMostMove > currentMove) {
15334 if (gameInfo.resultDetails != NULL) {
15335 free(gameInfo.resultDetails);
15336 gameInfo.resultDetails = NULL;
15337 gameInfo.result = GameUnfinished;
15339 forwardMostMove = currentMove;
15340 HistorySet(parseList, backwardMostMove, forwardMostMove,
15348 if (appData.noChessProgram) return;
15349 switch (gameMode) {
15350 case MachinePlaysWhite:
15351 if (WhiteOnMove(forwardMostMove)) {
15352 DisplayError(_("Wait until your turn"), 0);
15356 case BeginningOfGame:
15357 case MachinePlaysBlack:
15358 if (!WhiteOnMove(forwardMostMove)) {
15359 DisplayError(_("Wait until your turn"), 0);
15364 DisplayError(_("No hint available"), 0);
15367 SendToProgram("hint\n", &first);
15368 hintRequested = TRUE;
15374 ListGame * lg = (ListGame *) gameList.head;
15377 static int secondTime = FALSE;
15379 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15380 DisplayError(_("Game list not loaded or empty"), 0);
15384 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15387 DisplayNote(_("Book file exists! Try again for overwrite."));
15391 creatingBook = TRUE;
15392 secondTime = FALSE;
15394 /* Get list size */
15395 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15396 LoadGame(f, nItem, "", TRUE);
15397 AddGameToBook(TRUE);
15398 lg = (ListGame *) lg->node.succ;
15401 creatingBook = FALSE;
15408 if (appData.noChessProgram) return;
15409 switch (gameMode) {
15410 case MachinePlaysWhite:
15411 if (WhiteOnMove(forwardMostMove)) {
15412 DisplayError(_("Wait until your turn"), 0);
15416 case BeginningOfGame:
15417 case MachinePlaysBlack:
15418 if (!WhiteOnMove(forwardMostMove)) {
15419 DisplayError(_("Wait until your turn"), 0);
15424 EditPositionDone(TRUE);
15426 case TwoMachinesPlay:
15431 SendToProgram("bk\n", &first);
15432 bookOutput[0] = NULLCHAR;
15433 bookRequested = TRUE;
15439 char *tags = PGNTags(&gameInfo);
15440 TagsPopUp(tags, CmailMsg());
15444 /* end button procedures */
15447 PrintPosition (FILE *fp, int move)
15451 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15452 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15453 char c = PieceToChar(boards[move][i][j]);
15454 fputc(c == 'x' ? '.' : c, fp);
15455 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15458 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15459 fprintf(fp, "white to play\n");
15461 fprintf(fp, "black to play\n");
15465 PrintOpponents (FILE *fp)
15467 if (gameInfo.white != NULL) {
15468 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15474 /* Find last component of program's own name, using some heuristics */
15476 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15479 int local = (strcmp(host, "localhost") == 0);
15480 while (!local && (p = strchr(prog, ';')) != NULL) {
15482 while (*p == ' ') p++;
15485 if (*prog == '"' || *prog == '\'') {
15486 q = strchr(prog + 1, *prog);
15488 q = strchr(prog, ' ');
15490 if (q == NULL) q = prog + strlen(prog);
15492 while (p >= prog && *p != '/' && *p != '\\') p--;
15494 if(p == prog && *p == '"') p++;
15496 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15497 memcpy(buf, p, q - p);
15498 buf[q - p] = NULLCHAR;
15506 TimeControlTagValue ()
15509 if (!appData.clockMode) {
15510 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15511 } else if (movesPerSession > 0) {
15512 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15513 } else if (timeIncrement == 0) {
15514 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15516 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15518 return StrSave(buf);
15524 /* This routine is used only for certain modes */
15525 VariantClass v = gameInfo.variant;
15526 ChessMove r = GameUnfinished;
15529 if(keepInfo) return;
15531 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15532 r = gameInfo.result;
15533 p = gameInfo.resultDetails;
15534 gameInfo.resultDetails = NULL;
15536 ClearGameInfo(&gameInfo);
15537 gameInfo.variant = v;
15539 switch (gameMode) {
15540 case MachinePlaysWhite:
15541 gameInfo.event = StrSave( appData.pgnEventHeader );
15542 gameInfo.site = StrSave(HostName());
15543 gameInfo.date = PGNDate();
15544 gameInfo.round = StrSave("-");
15545 gameInfo.white = StrSave(first.tidy);
15546 gameInfo.black = StrSave(UserName());
15547 gameInfo.timeControl = TimeControlTagValue();
15550 case MachinePlaysBlack:
15551 gameInfo.event = StrSave( appData.pgnEventHeader );
15552 gameInfo.site = StrSave(HostName());
15553 gameInfo.date = PGNDate();
15554 gameInfo.round = StrSave("-");
15555 gameInfo.white = StrSave(UserName());
15556 gameInfo.black = StrSave(first.tidy);
15557 gameInfo.timeControl = TimeControlTagValue();
15560 case TwoMachinesPlay:
15561 gameInfo.event = StrSave( appData.pgnEventHeader );
15562 gameInfo.site = StrSave(HostName());
15563 gameInfo.date = PGNDate();
15566 snprintf(buf, MSG_SIZ, "%d", roundNr);
15567 gameInfo.round = StrSave(buf);
15569 gameInfo.round = StrSave("-");
15571 if (first.twoMachinesColor[0] == 'w') {
15572 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15573 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15575 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15576 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15578 gameInfo.timeControl = TimeControlTagValue();
15582 gameInfo.event = StrSave("Edited game");
15583 gameInfo.site = StrSave(HostName());
15584 gameInfo.date = PGNDate();
15585 gameInfo.round = StrSave("-");
15586 gameInfo.white = StrSave("-");
15587 gameInfo.black = StrSave("-");
15588 gameInfo.result = r;
15589 gameInfo.resultDetails = p;
15593 gameInfo.event = StrSave("Edited position");
15594 gameInfo.site = StrSave(HostName());
15595 gameInfo.date = PGNDate();
15596 gameInfo.round = StrSave("-");
15597 gameInfo.white = StrSave("-");
15598 gameInfo.black = StrSave("-");
15601 case IcsPlayingWhite:
15602 case IcsPlayingBlack:
15607 case PlayFromGameFile:
15608 gameInfo.event = StrSave("Game from non-PGN file");
15609 gameInfo.site = StrSave(HostName());
15610 gameInfo.date = PGNDate();
15611 gameInfo.round = StrSave("-");
15612 gameInfo.white = StrSave("?");
15613 gameInfo.black = StrSave("?");
15622 ReplaceComment (int index, char *text)
15628 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15629 pvInfoList[index-1].depth == len &&
15630 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15631 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15632 while (*text == '\n') text++;
15633 len = strlen(text);
15634 while (len > 0 && text[len - 1] == '\n') len--;
15636 if (commentList[index] != NULL)
15637 free(commentList[index]);
15640 commentList[index] = NULL;
15643 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15644 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15645 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15646 commentList[index] = (char *) malloc(len + 2);
15647 strncpy(commentList[index], text, len);
15648 commentList[index][len] = '\n';
15649 commentList[index][len + 1] = NULLCHAR;
15651 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15653 commentList[index] = (char *) malloc(len + 7);
15654 safeStrCpy(commentList[index], "{\n", 3);
15655 safeStrCpy(commentList[index]+2, text, len+1);
15656 commentList[index][len+2] = NULLCHAR;
15657 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15658 strcat(commentList[index], "\n}\n");
15663 CrushCRs (char *text)
15671 if (ch == '\r') continue;
15673 } while (ch != '\0');
15677 AppendComment (int index, char *text, Boolean addBraces)
15678 /* addBraces tells if we should add {} */
15683 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15684 if(addBraces == 3) addBraces = 0; else // force appending literally
15685 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15688 while (*text == '\n') text++;
15689 len = strlen(text);
15690 while (len > 0 && text[len - 1] == '\n') len--;
15691 text[len] = NULLCHAR;
15693 if (len == 0) return;
15695 if (commentList[index] != NULL) {
15696 Boolean addClosingBrace = addBraces;
15697 old = commentList[index];
15698 oldlen = strlen(old);
15699 while(commentList[index][oldlen-1] == '\n')
15700 commentList[index][--oldlen] = NULLCHAR;
15701 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15702 safeStrCpy(commentList[index], old, oldlen + len + 6);
15704 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15705 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15706 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15707 while (*text == '\n') { text++; len--; }
15708 commentList[index][--oldlen] = NULLCHAR;
15710 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15711 else strcat(commentList[index], "\n");
15712 strcat(commentList[index], text);
15713 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15714 else strcat(commentList[index], "\n");
15716 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15718 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15719 else commentList[index][0] = NULLCHAR;
15720 strcat(commentList[index], text);
15721 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15722 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15727 FindStr (char * text, char * sub_text)
15729 char * result = strstr( text, sub_text );
15731 if( result != NULL ) {
15732 result += strlen( sub_text );
15738 /* [AS] Try to extract PV info from PGN comment */
15739 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15741 GetInfoFromComment (int index, char * text)
15743 char * sep = text, *p;
15745 if( text != NULL && index > 0 ) {
15748 int time = -1, sec = 0, deci;
15749 char * s_eval = FindStr( text, "[%eval " );
15750 char * s_emt = FindStr( text, "[%emt " );
15752 if( s_eval != NULL || s_emt != NULL ) {
15754 if(0) { // [HGM] this code is not finished, and could actually be detrimental
15759 if( s_eval != NULL ) {
15760 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15764 if( delim != ']' ) {
15769 if( s_emt != NULL ) {
15774 /* We expect something like: [+|-]nnn.nn/dd */
15777 if(*text != '{') return text; // [HGM] braces: must be normal comment
15779 sep = strchr( text, '/' );
15780 if( sep == NULL || sep < (text+4) ) {
15785 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15786 if(p[1] == '(') { // comment starts with PV
15787 p = strchr(p, ')'); // locate end of PV
15788 if(p == NULL || sep < p+5) return text;
15789 // at this point we have something like "{(.*) +0.23/6 ..."
15790 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15791 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15792 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15794 time = -1; sec = -1; deci = -1;
15795 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15796 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15797 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15798 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15802 if( score_lo < 0 || score_lo >= 100 ) {
15806 if(sec >= 0) time = 600*time + 10*sec; else
15807 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15809 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15811 /* [HGM] PV time: now locate end of PV info */
15812 while( *++sep >= '0' && *sep <= '9'); // strip depth
15814 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15816 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15818 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15819 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15830 pvInfoList[index-1].depth = depth;
15831 pvInfoList[index-1].score = score;
15832 pvInfoList[index-1].time = 10*time; // centi-sec
15833 if(*sep == '}') *sep = 0; else *--sep = '{';
15834 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15840 SendToProgram (char *message, ChessProgramState *cps)
15842 int count, outCount, error;
15845 if (cps->pr == NoProc) return;
15848 if (appData.debugMode) {
15851 fprintf(debugFP, "%ld >%-6s: %s",
15852 SubtractTimeMarks(&now, &programStartTime),
15853 cps->which, message);
15855 fprintf(serverFP, "%ld >%-6s: %s",
15856 SubtractTimeMarks(&now, &programStartTime),
15857 cps->which, message), fflush(serverFP);
15860 count = strlen(message);
15861 outCount = OutputToProcess(cps->pr, message, count, &error);
15862 if (outCount < count && !exiting
15863 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15864 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15865 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15866 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15867 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15868 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15869 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15870 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15872 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15873 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15874 gameInfo.result = res;
15876 gameInfo.resultDetails = StrSave(buf);
15878 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15879 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15884 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15888 ChessProgramState *cps = (ChessProgramState *)closure;
15890 if (isr != cps->isr) return; /* Killed intentionally */
15893 RemoveInputSource(cps->isr);
15894 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15895 _(cps->which), cps->program);
15896 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15897 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15898 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15899 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15900 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15901 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15903 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15904 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15905 gameInfo.result = res;
15907 gameInfo.resultDetails = StrSave(buf);
15909 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15910 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15912 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15913 _(cps->which), cps->program);
15914 RemoveInputSource(cps->isr);
15916 /* [AS] Program is misbehaving badly... kill it */
15917 if( count == -2 ) {
15918 DestroyChildProcess( cps->pr, 9 );
15922 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15927 if ((end_str = strchr(message, '\r')) != NULL)
15928 *end_str = NULLCHAR;
15929 if ((end_str = strchr(message, '\n')) != NULL)
15930 *end_str = NULLCHAR;
15932 if (appData.debugMode) {
15933 TimeMark now; int print = 1;
15934 char *quote = ""; char c; int i;
15936 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15937 char start = message[0];
15938 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15939 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15940 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15941 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15942 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15943 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15944 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15945 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15946 sscanf(message, "hint: %c", &c)!=1 &&
15947 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15948 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15949 print = (appData.engineComments >= 2);
15951 message[0] = start; // restore original message
15955 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15956 SubtractTimeMarks(&now, &programStartTime), cps->which,
15960 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15961 SubtractTimeMarks(&now, &programStartTime), cps->which,
15963 message), fflush(serverFP);
15967 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15968 if (appData.icsEngineAnalyze) {
15969 if (strstr(message, "whisper") != NULL ||
15970 strstr(message, "kibitz") != NULL ||
15971 strstr(message, "tellics") != NULL) return;
15974 HandleMachineMove(message, cps);
15979 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15984 if( timeControl_2 > 0 ) {
15985 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15986 tc = timeControl_2;
15989 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15990 inc /= cps->timeOdds;
15991 st /= cps->timeOdds;
15993 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15996 /* Set exact time per move, normally using st command */
15997 if (cps->stKludge) {
15998 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16000 if (seconds == 0) {
16001 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16003 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16006 snprintf(buf, MSG_SIZ, "st %d\n", st);
16009 /* Set conventional or incremental time control, using level command */
16010 if (seconds == 0) {
16011 /* Note old gnuchess bug -- minutes:seconds used to not work.
16012 Fixed in later versions, but still avoid :seconds
16013 when seconds is 0. */
16014 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16016 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16017 seconds, inc/1000.);
16020 SendToProgram(buf, cps);
16022 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16023 /* Orthogonally, limit search to given depth */
16025 if (cps->sdKludge) {
16026 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16028 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16030 SendToProgram(buf, cps);
16033 if(cps->nps >= 0) { /* [HGM] nps */
16034 if(cps->supportsNPS == FALSE)
16035 cps->nps = -1; // don't use if engine explicitly says not supported!
16037 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16038 SendToProgram(buf, cps);
16043 ChessProgramState *
16045 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16047 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16048 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16054 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16056 char message[MSG_SIZ];
16059 /* Note: this routine must be called when the clocks are stopped
16060 or when they have *just* been set or switched; otherwise
16061 it will be off by the time since the current tick started.
16063 if (machineWhite) {
16064 time = whiteTimeRemaining / 10;
16065 otime = blackTimeRemaining / 10;
16067 time = blackTimeRemaining / 10;
16068 otime = whiteTimeRemaining / 10;
16070 /* [HGM] translate opponent's time by time-odds factor */
16071 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16073 if (time <= 0) time = 1;
16074 if (otime <= 0) otime = 1;
16076 snprintf(message, MSG_SIZ, "time %ld\n", time);
16077 SendToProgram(message, cps);
16079 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16080 SendToProgram(message, cps);
16084 EngineDefinedVariant (ChessProgramState *cps, int n)
16085 { // return name of n-th unknown variant that engine supports
16086 static char buf[MSG_SIZ];
16087 char *p, *s = cps->variants;
16088 if(!s) return NULL;
16089 do { // parse string from variants feature
16091 p = strchr(s, ',');
16092 if(p) *p = NULLCHAR;
16093 v = StringToVariant(s);
16094 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16095 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16096 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16099 if(n < 0) return buf;
16105 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16108 int len = strlen(name);
16111 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16113 sscanf(*p, "%d", &val);
16115 while (**p && **p != ' ')
16117 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16118 SendToProgram(buf, cps);
16125 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16128 int len = strlen(name);
16129 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16131 sscanf(*p, "%d", loc);
16132 while (**p && **p != ' ') (*p)++;
16133 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16134 SendToProgram(buf, cps);
16141 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16144 int len = strlen(name);
16145 if (strncmp((*p), name, len) == 0
16146 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16148 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16149 sscanf(*p, "%[^\"]", *loc);
16150 while (**p && **p != '\"') (*p)++;
16151 if (**p == '\"') (*p)++;
16152 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16153 SendToProgram(buf, cps);
16160 ParseOption (Option *opt, ChessProgramState *cps)
16161 // [HGM] options: process the string that defines an engine option, and determine
16162 // name, type, default value, and allowed value range
16164 char *p, *q, buf[MSG_SIZ];
16165 int n, min = (-1)<<31, max = 1<<31, def;
16167 if(p = strstr(opt->name, " -spin ")) {
16168 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16169 if(max < min) max = min; // enforce consistency
16170 if(def < min) def = min;
16171 if(def > max) def = max;
16176 } else if((p = strstr(opt->name, " -slider "))) {
16177 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16178 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16179 if(max < min) max = min; // enforce consistency
16180 if(def < min) def = min;
16181 if(def > max) def = max;
16185 opt->type = Spin; // Slider;
16186 } else if((p = strstr(opt->name, " -string "))) {
16187 opt->textValue = p+9;
16188 opt->type = TextBox;
16189 } else if((p = strstr(opt->name, " -file "))) {
16190 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16191 opt->textValue = p+7;
16192 opt->type = FileName; // FileName;
16193 } else if((p = strstr(opt->name, " -path "))) {
16194 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16195 opt->textValue = p+7;
16196 opt->type = PathName; // PathName;
16197 } else if(p = strstr(opt->name, " -check ")) {
16198 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16199 opt->value = (def != 0);
16200 opt->type = CheckBox;
16201 } else if(p = strstr(opt->name, " -combo ")) {
16202 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16203 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16204 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16205 opt->value = n = 0;
16206 while(q = StrStr(q, " /// ")) {
16207 n++; *q = 0; // count choices, and null-terminate each of them
16209 if(*q == '*') { // remember default, which is marked with * prefix
16213 cps->comboList[cps->comboCnt++] = q;
16215 cps->comboList[cps->comboCnt++] = NULL;
16217 opt->type = ComboBox;
16218 } else if(p = strstr(opt->name, " -button")) {
16219 opt->type = Button;
16220 } else if(p = strstr(opt->name, " -save")) {
16221 opt->type = SaveButton;
16222 } else return FALSE;
16223 *p = 0; // terminate option name
16224 // now look if the command-line options define a setting for this engine option.
16225 if(cps->optionSettings && cps->optionSettings[0])
16226 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16227 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16228 snprintf(buf, MSG_SIZ, "option %s", p);
16229 if(p = strstr(buf, ",")) *p = 0;
16230 if(q = strchr(buf, '=')) switch(opt->type) {
16232 for(n=0; n<opt->max; n++)
16233 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16236 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16240 opt->value = atoi(q+1);
16245 SendToProgram(buf, cps);
16251 FeatureDone (ChessProgramState *cps, int val)
16253 DelayedEventCallback cb = GetDelayedEvent();
16254 if ((cb == InitBackEnd3 && cps == &first) ||
16255 (cb == SettingsMenuIfReady && cps == &second) ||
16256 (cb == LoadEngine) ||
16257 (cb == TwoMachinesEventIfReady)) {
16258 CancelDelayedEvent();
16259 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16261 cps->initDone = val;
16262 if(val) cps->reload = FALSE;
16265 /* Parse feature command from engine */
16267 ParseFeatures (char *args, ChessProgramState *cps)
16275 while (*p == ' ') p++;
16276 if (*p == NULLCHAR) return;
16278 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16279 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16280 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16281 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16282 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16283 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16284 if (BoolFeature(&p, "reuse", &val, cps)) {
16285 /* Engine can disable reuse, but can't enable it if user said no */
16286 if (!val) cps->reuse = FALSE;
16289 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16290 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16291 if (gameMode == TwoMachinesPlay) {
16292 DisplayTwoMachinesTitle();
16298 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16299 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16300 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16301 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16302 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16303 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16304 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16305 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16306 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16307 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16308 if (IntFeature(&p, "done", &val, cps)) {
16309 FeatureDone(cps, val);
16312 /* Added by Tord: */
16313 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16314 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16315 /* End of additions by Tord */
16317 /* [HGM] added features: */
16318 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16319 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16320 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16321 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16322 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16323 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16324 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16325 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16326 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16327 FREE(cps->option[cps->nrOptions].name);
16328 cps->option[cps->nrOptions].name = q; q = NULL;
16329 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16330 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16331 SendToProgram(buf, cps);
16334 if(cps->nrOptions >= MAX_OPTIONS) {
16336 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16337 DisplayError(buf, 0);
16341 /* End of additions by HGM */
16343 /* unknown feature: complain and skip */
16345 while (*q && *q != '=') q++;
16346 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16347 SendToProgram(buf, cps);
16353 while (*p && *p != '\"') p++;
16354 if (*p == '\"') p++;
16356 while (*p && *p != ' ') p++;
16364 PeriodicUpdatesEvent (int newState)
16366 if (newState == appData.periodicUpdates)
16369 appData.periodicUpdates=newState;
16371 /* Display type changes, so update it now */
16372 // DisplayAnalysis();
16374 /* Get the ball rolling again... */
16376 AnalysisPeriodicEvent(1);
16377 StartAnalysisClock();
16382 PonderNextMoveEvent (int newState)
16384 if (newState == appData.ponderNextMove) return;
16385 if (gameMode == EditPosition) EditPositionDone(TRUE);
16387 SendToProgram("hard\n", &first);
16388 if (gameMode == TwoMachinesPlay) {
16389 SendToProgram("hard\n", &second);
16392 SendToProgram("easy\n", &first);
16393 thinkOutput[0] = NULLCHAR;
16394 if (gameMode == TwoMachinesPlay) {
16395 SendToProgram("easy\n", &second);
16398 appData.ponderNextMove = newState;
16402 NewSettingEvent (int option, int *feature, char *command, int value)
16406 if (gameMode == EditPosition) EditPositionDone(TRUE);
16407 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16408 if(feature == NULL || *feature) SendToProgram(buf, &first);
16409 if (gameMode == TwoMachinesPlay) {
16410 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16415 ShowThinkingEvent ()
16416 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16418 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16419 int newState = appData.showThinking
16420 // [HGM] thinking: other features now need thinking output as well
16421 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16423 if (oldState == newState) return;
16424 oldState = newState;
16425 if (gameMode == EditPosition) EditPositionDone(TRUE);
16427 SendToProgram("post\n", &first);
16428 if (gameMode == TwoMachinesPlay) {
16429 SendToProgram("post\n", &second);
16432 SendToProgram("nopost\n", &first);
16433 thinkOutput[0] = NULLCHAR;
16434 if (gameMode == TwoMachinesPlay) {
16435 SendToProgram("nopost\n", &second);
16438 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16442 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16444 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16445 if (pr == NoProc) return;
16446 AskQuestion(title, question, replyPrefix, pr);
16450 TypeInEvent (char firstChar)
16452 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16453 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16454 gameMode == AnalyzeMode || gameMode == EditGame ||
16455 gameMode == EditPosition || gameMode == IcsExamining ||
16456 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16457 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16458 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16459 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16460 gameMode == Training) PopUpMoveDialog(firstChar);
16464 TypeInDoneEvent (char *move)
16467 int n, fromX, fromY, toX, toY;
16469 ChessMove moveType;
16472 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16473 EditPositionPasteFEN(move);
16476 // [HGM] movenum: allow move number to be typed in any mode
16477 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16481 // undocumented kludge: allow command-line option to be typed in!
16482 // (potentially fatal, and does not implement the effect of the option.)
16483 // should only be used for options that are values on which future decisions will be made,
16484 // and definitely not on options that would be used during initialization.
16485 if(strstr(move, "!!! -") == move) {
16486 ParseArgsFromString(move+4);
16490 if (gameMode != EditGame && currentMove != forwardMostMove &&
16491 gameMode != Training) {
16492 DisplayMoveError(_("Displayed move is not current"));
16494 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16495 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16496 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16497 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16498 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16499 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16501 DisplayMoveError(_("Could not parse move"));
16507 DisplayMove (int moveNumber)
16509 char message[MSG_SIZ];
16511 char cpThinkOutput[MSG_SIZ];
16513 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16515 if (moveNumber == forwardMostMove - 1 ||
16516 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16518 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16520 if (strchr(cpThinkOutput, '\n')) {
16521 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16524 *cpThinkOutput = NULLCHAR;
16527 /* [AS] Hide thinking from human user */
16528 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16529 *cpThinkOutput = NULLCHAR;
16530 if( thinkOutput[0] != NULLCHAR ) {
16533 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16534 cpThinkOutput[i] = '.';
16536 cpThinkOutput[i] = NULLCHAR;
16537 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16541 if (moveNumber == forwardMostMove - 1 &&
16542 gameInfo.resultDetails != NULL) {
16543 if (gameInfo.resultDetails[0] == NULLCHAR) {
16544 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16546 snprintf(res, MSG_SIZ, " {%s} %s",
16547 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16553 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16554 DisplayMessage(res, cpThinkOutput);
16556 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16557 WhiteOnMove(moveNumber) ? " " : ".. ",
16558 parseList[moveNumber], res);
16559 DisplayMessage(message, cpThinkOutput);
16564 DisplayComment (int moveNumber, char *text)
16566 char title[MSG_SIZ];
16568 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16569 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16571 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16572 WhiteOnMove(moveNumber) ? " " : ".. ",
16573 parseList[moveNumber]);
16575 if (text != NULL && (appData.autoDisplayComment || commentUp))
16576 CommentPopUp(title, text);
16579 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16580 * might be busy thinking or pondering. It can be omitted if your
16581 * gnuchess is configured to stop thinking immediately on any user
16582 * input. However, that gnuchess feature depends on the FIONREAD
16583 * ioctl, which does not work properly on some flavors of Unix.
16586 Attention (ChessProgramState *cps)
16589 if (!cps->useSigint) return;
16590 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16591 switch (gameMode) {
16592 case MachinePlaysWhite:
16593 case MachinePlaysBlack:
16594 case TwoMachinesPlay:
16595 case IcsPlayingWhite:
16596 case IcsPlayingBlack:
16599 /* Skip if we know it isn't thinking */
16600 if (!cps->maybeThinking) return;
16601 if (appData.debugMode)
16602 fprintf(debugFP, "Interrupting %s\n", cps->which);
16603 InterruptChildProcess(cps->pr);
16604 cps->maybeThinking = FALSE;
16609 #endif /*ATTENTION*/
16615 if (whiteTimeRemaining <= 0) {
16618 if (appData.icsActive) {
16619 if (appData.autoCallFlag &&
16620 gameMode == IcsPlayingBlack && !blackFlag) {
16621 SendToICS(ics_prefix);
16622 SendToICS("flag\n");
16626 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16628 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16629 if (appData.autoCallFlag) {
16630 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16637 if (blackTimeRemaining <= 0) {
16640 if (appData.icsActive) {
16641 if (appData.autoCallFlag &&
16642 gameMode == IcsPlayingWhite && !whiteFlag) {
16643 SendToICS(ics_prefix);
16644 SendToICS("flag\n");
16648 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16650 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16651 if (appData.autoCallFlag) {
16652 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16663 CheckTimeControl ()
16665 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16666 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16669 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16671 if ( !WhiteOnMove(forwardMostMove) ) {
16672 /* White made time control */
16673 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16674 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16675 /* [HGM] time odds: correct new time quota for time odds! */
16676 / WhitePlayer()->timeOdds;
16677 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16679 lastBlack -= blackTimeRemaining;
16680 /* Black made time control */
16681 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16682 / WhitePlayer()->other->timeOdds;
16683 lastWhite = whiteTimeRemaining;
16688 DisplayBothClocks ()
16690 int wom = gameMode == EditPosition ?
16691 !blackPlaysFirst : WhiteOnMove(currentMove);
16692 DisplayWhiteClock(whiteTimeRemaining, wom);
16693 DisplayBlackClock(blackTimeRemaining, !wom);
16697 /* Timekeeping seems to be a portability nightmare. I think everyone
16698 has ftime(), but I'm really not sure, so I'm including some ifdefs
16699 to use other calls if you don't. Clocks will be less accurate if
16700 you have neither ftime nor gettimeofday.
16703 /* VS 2008 requires the #include outside of the function */
16704 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16705 #include <sys/timeb.h>
16708 /* Get the current time as a TimeMark */
16710 GetTimeMark (TimeMark *tm)
16712 #if HAVE_GETTIMEOFDAY
16714 struct timeval timeVal;
16715 struct timezone timeZone;
16717 gettimeofday(&timeVal, &timeZone);
16718 tm->sec = (long) timeVal.tv_sec;
16719 tm->ms = (int) (timeVal.tv_usec / 1000L);
16721 #else /*!HAVE_GETTIMEOFDAY*/
16724 // include <sys/timeb.h> / moved to just above start of function
16725 struct timeb timeB;
16728 tm->sec = (long) timeB.time;
16729 tm->ms = (int) timeB.millitm;
16731 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16732 tm->sec = (long) time(NULL);
16738 /* Return the difference in milliseconds between two
16739 time marks. We assume the difference will fit in a long!
16742 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16744 return 1000L*(tm2->sec - tm1->sec) +
16745 (long) (tm2->ms - tm1->ms);
16750 * Code to manage the game clocks.
16752 * In tournament play, black starts the clock and then white makes a move.
16753 * We give the human user a slight advantage if he is playing white---the
16754 * clocks don't run until he makes his first move, so it takes zero time.
16755 * Also, we don't account for network lag, so we could get out of sync
16756 * with GNU Chess's clock -- but then, referees are always right.
16759 static TimeMark tickStartTM;
16760 static long intendedTickLength;
16763 NextTickLength (long timeRemaining)
16765 long nominalTickLength, nextTickLength;
16767 if (timeRemaining > 0L && timeRemaining <= 10000L)
16768 nominalTickLength = 100L;
16770 nominalTickLength = 1000L;
16771 nextTickLength = timeRemaining % nominalTickLength;
16772 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16774 return nextTickLength;
16777 /* Adjust clock one minute up or down */
16779 AdjustClock (Boolean which, int dir)
16781 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16782 if(which) blackTimeRemaining += 60000*dir;
16783 else whiteTimeRemaining += 60000*dir;
16784 DisplayBothClocks();
16785 adjustedClock = TRUE;
16788 /* Stop clocks and reset to a fresh time control */
16792 (void) StopClockTimer();
16793 if (appData.icsActive) {
16794 whiteTimeRemaining = blackTimeRemaining = 0;
16795 } else if (searchTime) {
16796 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16797 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16798 } else { /* [HGM] correct new time quote for time odds */
16799 whiteTC = blackTC = fullTimeControlString;
16800 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16801 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16803 if (whiteFlag || blackFlag) {
16805 whiteFlag = blackFlag = FALSE;
16807 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16808 DisplayBothClocks();
16809 adjustedClock = FALSE;
16812 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16814 /* Decrement running clock by amount of time that has passed */
16818 long timeRemaining;
16819 long lastTickLength, fudge;
16822 if (!appData.clockMode) return;
16823 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16827 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16829 /* Fudge if we woke up a little too soon */
16830 fudge = intendedTickLength - lastTickLength;
16831 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16833 if (WhiteOnMove(forwardMostMove)) {
16834 if(whiteNPS >= 0) lastTickLength = 0;
16835 timeRemaining = whiteTimeRemaining -= lastTickLength;
16836 if(timeRemaining < 0 && !appData.icsActive) {
16837 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16838 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16839 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16840 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16843 DisplayWhiteClock(whiteTimeRemaining - fudge,
16844 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16846 if(blackNPS >= 0) lastTickLength = 0;
16847 timeRemaining = blackTimeRemaining -= lastTickLength;
16848 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16849 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16851 blackStartMove = forwardMostMove;
16852 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16855 DisplayBlackClock(blackTimeRemaining - fudge,
16856 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16858 if (CheckFlags()) return;
16860 if(twoBoards) { // count down secondary board's clocks as well
16861 activePartnerTime -= lastTickLength;
16863 if(activePartner == 'W')
16864 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16866 DisplayBlackClock(activePartnerTime, TRUE);
16871 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16872 StartClockTimer(intendedTickLength);
16874 /* if the time remaining has fallen below the alarm threshold, sound the
16875 * alarm. if the alarm has sounded and (due to a takeback or time control
16876 * with increment) the time remaining has increased to a level above the
16877 * threshold, reset the alarm so it can sound again.
16880 if (appData.icsActive && appData.icsAlarm) {
16882 /* make sure we are dealing with the user's clock */
16883 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16884 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16887 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16888 alarmSounded = FALSE;
16889 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16891 alarmSounded = TRUE;
16897 /* A player has just moved, so stop the previously running
16898 clock and (if in clock mode) start the other one.
16899 We redisplay both clocks in case we're in ICS mode, because
16900 ICS gives us an update to both clocks after every move.
16901 Note that this routine is called *after* forwardMostMove
16902 is updated, so the last fractional tick must be subtracted
16903 from the color that is *not* on move now.
16906 SwitchClocks (int newMoveNr)
16908 long lastTickLength;
16910 int flagged = FALSE;
16914 if (StopClockTimer() && appData.clockMode) {
16915 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16916 if (!WhiteOnMove(forwardMostMove)) {
16917 if(blackNPS >= 0) lastTickLength = 0;
16918 blackTimeRemaining -= lastTickLength;
16919 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16920 // if(pvInfoList[forwardMostMove].time == -1)
16921 pvInfoList[forwardMostMove].time = // use GUI time
16922 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16924 if(whiteNPS >= 0) lastTickLength = 0;
16925 whiteTimeRemaining -= lastTickLength;
16926 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16927 // if(pvInfoList[forwardMostMove].time == -1)
16928 pvInfoList[forwardMostMove].time =
16929 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16931 flagged = CheckFlags();
16933 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16934 CheckTimeControl();
16936 if (flagged || !appData.clockMode) return;
16938 switch (gameMode) {
16939 case MachinePlaysBlack:
16940 case MachinePlaysWhite:
16941 case BeginningOfGame:
16942 if (pausing) return;
16946 case PlayFromGameFile:
16954 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16955 if(WhiteOnMove(forwardMostMove))
16956 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16957 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16961 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16962 whiteTimeRemaining : blackTimeRemaining);
16963 StartClockTimer(intendedTickLength);
16967 /* Stop both clocks */
16971 long lastTickLength;
16974 if (!StopClockTimer()) return;
16975 if (!appData.clockMode) return;
16979 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16980 if (WhiteOnMove(forwardMostMove)) {
16981 if(whiteNPS >= 0) lastTickLength = 0;
16982 whiteTimeRemaining -= lastTickLength;
16983 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16985 if(blackNPS >= 0) lastTickLength = 0;
16986 blackTimeRemaining -= lastTickLength;
16987 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16992 /* Start clock of player on move. Time may have been reset, so
16993 if clock is already running, stop and restart it. */
16997 (void) StopClockTimer(); /* in case it was running already */
16998 DisplayBothClocks();
16999 if (CheckFlags()) return;
17001 if (!appData.clockMode) return;
17002 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17004 GetTimeMark(&tickStartTM);
17005 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17006 whiteTimeRemaining : blackTimeRemaining);
17008 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17009 whiteNPS = blackNPS = -1;
17010 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17011 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17012 whiteNPS = first.nps;
17013 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17014 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17015 blackNPS = first.nps;
17016 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17017 whiteNPS = second.nps;
17018 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17019 blackNPS = second.nps;
17020 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17022 StartClockTimer(intendedTickLength);
17026 TimeString (long ms)
17028 long second, minute, hour, day;
17030 static char buf[32];
17032 if (ms > 0 && ms <= 9900) {
17033 /* convert milliseconds to tenths, rounding up */
17034 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17036 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17040 /* convert milliseconds to seconds, rounding up */
17041 /* use floating point to avoid strangeness of integer division
17042 with negative dividends on many machines */
17043 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17050 day = second / (60 * 60 * 24);
17051 second = second % (60 * 60 * 24);
17052 hour = second / (60 * 60);
17053 second = second % (60 * 60);
17054 minute = second / 60;
17055 second = second % 60;
17058 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17059 sign, day, hour, minute, second);
17061 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17063 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17070 * This is necessary because some C libraries aren't ANSI C compliant yet.
17073 StrStr (char *string, char *match)
17077 length = strlen(match);
17079 for (i = strlen(string) - length; i >= 0; i--, string++)
17080 if (!strncmp(match, string, length))
17087 StrCaseStr (char *string, char *match)
17091 length = strlen(match);
17093 for (i = strlen(string) - length; i >= 0; i--, string++) {
17094 for (j = 0; j < length; j++) {
17095 if (ToLower(match[j]) != ToLower(string[j]))
17098 if (j == length) return string;
17106 StrCaseCmp (char *s1, char *s2)
17111 c1 = ToLower(*s1++);
17112 c2 = ToLower(*s2++);
17113 if (c1 > c2) return 1;
17114 if (c1 < c2) return -1;
17115 if (c1 == NULLCHAR) return 0;
17123 return isupper(c) ? tolower(c) : c;
17130 return islower(c) ? toupper(c) : c;
17132 #endif /* !_amigados */
17139 if ((ret = (char *) malloc(strlen(s) + 1)))
17141 safeStrCpy(ret, s, strlen(s)+1);
17147 StrSavePtr (char *s, char **savePtr)
17152 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17153 safeStrCpy(*savePtr, s, strlen(s)+1);
17165 clock = time((time_t *)NULL);
17166 tm = localtime(&clock);
17167 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17168 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17169 return StrSave(buf);
17174 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17176 int i, j, fromX, fromY, toX, toY;
17183 whiteToPlay = (gameMode == EditPosition) ?
17184 !blackPlaysFirst : (move % 2 == 0);
17187 /* Piece placement data */
17188 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17189 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17191 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17192 if (boards[move][i][j] == EmptySquare) {
17194 } else { ChessSquare piece = boards[move][i][j];
17195 if (emptycount > 0) {
17196 if(emptycount<10) /* [HGM] can be >= 10 */
17197 *p++ = '0' + emptycount;
17198 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17201 if(PieceToChar(piece) == '+') {
17202 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17204 piece = (ChessSquare)(DEMOTED piece);
17206 *p++ = PieceToChar(piece);
17208 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17209 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17214 if (emptycount > 0) {
17215 if(emptycount<10) /* [HGM] can be >= 10 */
17216 *p++ = '0' + emptycount;
17217 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17224 /* [HGM] print Crazyhouse or Shogi holdings */
17225 if( gameInfo.holdingsWidth ) {
17226 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17228 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17229 piece = boards[move][i][BOARD_WIDTH-1];
17230 if( piece != EmptySquare )
17231 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17232 *p++ = PieceToChar(piece);
17234 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17235 piece = boards[move][BOARD_HEIGHT-i-1][0];
17236 if( piece != EmptySquare )
17237 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17238 *p++ = PieceToChar(piece);
17241 if( q == p ) *p++ = '-';
17247 *p++ = whiteToPlay ? 'w' : 'b';
17250 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17251 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17253 if(nrCastlingRights) {
17255 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17256 /* [HGM] write directly from rights */
17257 if(boards[move][CASTLING][2] != NoRights &&
17258 boards[move][CASTLING][0] != NoRights )
17259 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17260 if(boards[move][CASTLING][2] != NoRights &&
17261 boards[move][CASTLING][1] != NoRights )
17262 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17263 if(boards[move][CASTLING][5] != NoRights &&
17264 boards[move][CASTLING][3] != NoRights )
17265 *p++ = boards[move][CASTLING][3] + AAA;
17266 if(boards[move][CASTLING][5] != NoRights &&
17267 boards[move][CASTLING][4] != NoRights )
17268 *p++ = boards[move][CASTLING][4] + AAA;
17271 /* [HGM] write true castling rights */
17272 if( nrCastlingRights == 6 ) {
17274 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17275 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17276 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17277 boards[move][CASTLING][2] != NoRights );
17278 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17279 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17280 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17281 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17282 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17286 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17287 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17288 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17289 boards[move][CASTLING][5] != NoRights );
17290 if(gameInfo.variant == VariantSChess) {
17291 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17292 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17293 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17294 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17299 if (q == p) *p++ = '-'; /* No castling rights */
17303 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17304 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17305 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17306 /* En passant target square */
17307 if (move > backwardMostMove) {
17308 fromX = moveList[move - 1][0] - AAA;
17309 fromY = moveList[move - 1][1] - ONE;
17310 toX = moveList[move - 1][2] - AAA;
17311 toY = moveList[move - 1][3] - ONE;
17312 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17313 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17314 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17316 /* 2-square pawn move just happened */
17318 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17322 } else if(move == backwardMostMove) {
17323 // [HGM] perhaps we should always do it like this, and forget the above?
17324 if((signed char)boards[move][EP_STATUS] >= 0) {
17325 *p++ = boards[move][EP_STATUS] + AAA;
17326 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17338 { int i = 0, j=move;
17340 /* [HGM] find reversible plies */
17341 if (appData.debugMode) { int k;
17342 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17343 for(k=backwardMostMove; k<=forwardMostMove; k++)
17344 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17348 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17349 if( j == backwardMostMove ) i += initialRulePlies;
17350 sprintf(p, "%d ", i);
17351 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17353 /* Fullmove number */
17354 sprintf(p, "%d", (move / 2) + 1);
17355 } else *--p = NULLCHAR;
17357 return StrSave(buf);
17361 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17365 int emptycount, virgin[BOARD_FILES];
17370 /* [HGM] by default clear Crazyhouse holdings, if present */
17371 if(gameInfo.holdingsWidth) {
17372 for(i=0; i<BOARD_HEIGHT; i++) {
17373 board[i][0] = EmptySquare; /* black holdings */
17374 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17375 board[i][1] = (ChessSquare) 0; /* black counts */
17376 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17380 /* Piece placement data */
17381 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17384 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17385 if (*p == '/') p++;
17386 emptycount = gameInfo.boardWidth - j;
17387 while (emptycount--)
17388 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17390 #if(BOARD_FILES >= 10)
17391 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17392 p++; emptycount=10;
17393 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17394 while (emptycount--)
17395 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17397 } else if (isdigit(*p)) {
17398 emptycount = *p++ - '0';
17399 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17400 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17401 while (emptycount--)
17402 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17403 } else if (*p == '+' || isalpha(*p)) {
17404 if (j >= gameInfo.boardWidth) return FALSE;
17406 piece = CharToPiece(*++p);
17407 if(piece == EmptySquare) return FALSE; /* unknown piece */
17408 piece = (ChessSquare) (PROMOTED piece ); p++;
17409 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17410 } else piece = CharToPiece(*p++);
17412 if(piece==EmptySquare) return FALSE; /* unknown piece */
17413 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17414 piece = (ChessSquare) (PROMOTED piece);
17415 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17418 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17424 while (*p == '/' || *p == ' ') p++;
17426 /* [HGM] look for Crazyhouse holdings here */
17427 while(*p==' ') p++;
17428 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17430 if(*p == '-' ) p++; /* empty holdings */ else {
17431 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17432 /* if we would allow FEN reading to set board size, we would */
17433 /* have to add holdings and shift the board read so far here */
17434 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17436 if((int) piece >= (int) BlackPawn ) {
17437 i = (int)piece - (int)BlackPawn;
17438 i = PieceToNumber((ChessSquare)i);
17439 if( i >= gameInfo.holdingsSize ) return FALSE;
17440 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17441 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17443 i = (int)piece - (int)WhitePawn;
17444 i = PieceToNumber((ChessSquare)i);
17445 if( i >= gameInfo.holdingsSize ) return FALSE;
17446 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17447 board[i][BOARD_WIDTH-2]++; /* black holdings */
17454 while(*p == ' ') p++;
17458 if(appData.colorNickNames) {
17459 if( c == appData.colorNickNames[0] ) c = 'w'; else
17460 if( c == appData.colorNickNames[1] ) c = 'b';
17464 *blackPlaysFirst = FALSE;
17467 *blackPlaysFirst = TRUE;
17473 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17474 /* return the extra info in global variiables */
17476 /* set defaults in case FEN is incomplete */
17477 board[EP_STATUS] = EP_UNKNOWN;
17478 for(i=0; i<nrCastlingRights; i++ ) {
17479 board[CASTLING][i] =
17480 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17481 } /* assume possible unless obviously impossible */
17482 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17483 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17484 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17485 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17486 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17487 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17488 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17489 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17492 while(*p==' ') p++;
17493 if(nrCastlingRights) {
17494 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17495 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17496 /* castling indicator present, so default becomes no castlings */
17497 for(i=0; i<nrCastlingRights; i++ ) {
17498 board[CASTLING][i] = NoRights;
17501 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17502 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17503 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17504 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17505 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17507 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17508 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17509 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17511 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17512 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17513 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17514 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17515 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17516 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17519 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17520 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17521 board[CASTLING][2] = whiteKingFile;
17522 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17523 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17526 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17527 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17528 board[CASTLING][2] = whiteKingFile;
17529 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17530 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17533 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17534 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17535 board[CASTLING][5] = blackKingFile;
17536 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17537 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17540 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17541 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17542 board[CASTLING][5] = blackKingFile;
17543 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17544 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17547 default: /* FRC castlings */
17548 if(c >= 'a') { /* black rights */
17549 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17550 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17551 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17552 if(i == BOARD_RGHT) break;
17553 board[CASTLING][5] = i;
17555 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17556 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17558 board[CASTLING][3] = c;
17560 board[CASTLING][4] = c;
17561 } else { /* white rights */
17562 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17563 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17564 if(board[0][i] == WhiteKing) break;
17565 if(i == BOARD_RGHT) break;
17566 board[CASTLING][2] = i;
17567 c -= AAA - 'a' + 'A';
17568 if(board[0][c] >= WhiteKing) break;
17570 board[CASTLING][0] = c;
17572 board[CASTLING][1] = c;
17576 for(i=0; i<nrCastlingRights; i++)
17577 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17578 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17579 if (appData.debugMode) {
17580 fprintf(debugFP, "FEN castling rights:");
17581 for(i=0; i<nrCastlingRights; i++)
17582 fprintf(debugFP, " %d", board[CASTLING][i]);
17583 fprintf(debugFP, "\n");
17586 while(*p==' ') p++;
17589 /* read e.p. field in games that know e.p. capture */
17590 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17591 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17592 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17594 p++; board[EP_STATUS] = EP_NONE;
17596 char c = *p++ - AAA;
17598 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17599 if(*p >= '0' && *p <='9') p++;
17600 board[EP_STATUS] = c;
17605 if(sscanf(p, "%d", &i) == 1) {
17606 FENrulePlies = i; /* 50-move ply counter */
17607 /* (The move number is still ignored) */
17614 EditPositionPasteFEN (char *fen)
17617 Board initial_position;
17619 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17620 DisplayError(_("Bad FEN position in clipboard"), 0);
17623 int savedBlackPlaysFirst = blackPlaysFirst;
17624 EditPositionEvent();
17625 blackPlaysFirst = savedBlackPlaysFirst;
17626 CopyBoard(boards[0], initial_position);
17627 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17628 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17629 DisplayBothClocks();
17630 DrawPosition(FALSE, boards[currentMove]);
17635 static char cseq[12] = "\\ ";
17638 set_cont_sequence (char *new_seq)
17643 // handle bad attempts to set the sequence
17645 return 0; // acceptable error - no debug
17647 len = strlen(new_seq);
17648 ret = (len > 0) && (len < sizeof(cseq));
17650 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17651 else if (appData.debugMode)
17652 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17657 reformat a source message so words don't cross the width boundary. internal
17658 newlines are not removed. returns the wrapped size (no null character unless
17659 included in source message). If dest is NULL, only calculate the size required
17660 for the dest buffer. lp argument indicats line position upon entry, and it's
17661 passed back upon exit.
17664 wrap (char *dest, char *src, int count, int width, int *lp)
17666 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17668 cseq_len = strlen(cseq);
17669 old_line = line = *lp;
17670 ansi = len = clen = 0;
17672 for (i=0; i < count; i++)
17674 if (src[i] == '\033')
17677 // if we hit the width, back up
17678 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17680 // store i & len in case the word is too long
17681 old_i = i, old_len = len;
17683 // find the end of the last word
17684 while (i && src[i] != ' ' && src[i] != '\n')
17690 // word too long? restore i & len before splitting it
17691 if ((old_i-i+clen) >= width)
17698 if (i && src[i-1] == ' ')
17701 if (src[i] != ' ' && src[i] != '\n')
17708 // now append the newline and continuation sequence
17713 strncpy(dest+len, cseq, cseq_len);
17721 dest[len] = src[i];
17725 if (src[i] == '\n')
17730 if (dest && appData.debugMode)
17732 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17733 count, width, line, len, *lp);
17734 show_bytes(debugFP, src, count);
17735 fprintf(debugFP, "\ndest: ");
17736 show_bytes(debugFP, dest, len);
17737 fprintf(debugFP, "\n");
17739 *lp = dest ? line : old_line;
17744 // [HGM] vari: routines for shelving variations
17745 Boolean modeRestore = FALSE;
17748 PushInner (int firstMove, int lastMove)
17750 int i, j, nrMoves = lastMove - firstMove;
17752 // push current tail of game on stack
17753 savedResult[storedGames] = gameInfo.result;
17754 savedDetails[storedGames] = gameInfo.resultDetails;
17755 gameInfo.resultDetails = NULL;
17756 savedFirst[storedGames] = firstMove;
17757 savedLast [storedGames] = lastMove;
17758 savedFramePtr[storedGames] = framePtr;
17759 framePtr -= nrMoves; // reserve space for the boards
17760 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17761 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17762 for(j=0; j<MOVE_LEN; j++)
17763 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17764 for(j=0; j<2*MOVE_LEN; j++)
17765 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17766 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17767 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17768 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17769 pvInfoList[firstMove+i-1].depth = 0;
17770 commentList[framePtr+i] = commentList[firstMove+i];
17771 commentList[firstMove+i] = NULL;
17775 forwardMostMove = firstMove; // truncate game so we can start variation
17779 PushTail (int firstMove, int lastMove)
17781 if(appData.icsActive) { // only in local mode
17782 forwardMostMove = currentMove; // mimic old ICS behavior
17785 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17787 PushInner(firstMove, lastMove);
17788 if(storedGames == 1) GreyRevert(FALSE);
17789 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17793 PopInner (Boolean annotate)
17796 char buf[8000], moveBuf[20];
17798 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17799 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17800 nrMoves = savedLast[storedGames] - currentMove;
17803 if(!WhiteOnMove(currentMove))
17804 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17805 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17806 for(i=currentMove; i<forwardMostMove; i++) {
17808 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17809 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17810 strcat(buf, moveBuf);
17811 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17812 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17816 for(i=1; i<=nrMoves; i++) { // copy last variation back
17817 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17818 for(j=0; j<MOVE_LEN; j++)
17819 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17820 for(j=0; j<2*MOVE_LEN; j++)
17821 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17822 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17823 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17824 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17825 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17826 commentList[currentMove+i] = commentList[framePtr+i];
17827 commentList[framePtr+i] = NULL;
17829 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17830 framePtr = savedFramePtr[storedGames];
17831 gameInfo.result = savedResult[storedGames];
17832 if(gameInfo.resultDetails != NULL) {
17833 free(gameInfo.resultDetails);
17835 gameInfo.resultDetails = savedDetails[storedGames];
17836 forwardMostMove = currentMove + nrMoves;
17840 PopTail (Boolean annotate)
17842 if(appData.icsActive) return FALSE; // only in local mode
17843 if(!storedGames) return FALSE; // sanity
17844 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17846 PopInner(annotate);
17847 if(currentMove < forwardMostMove) ForwardEvent(); else
17848 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17850 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17856 { // remove all shelved variations
17858 for(i=0; i<storedGames; i++) {
17859 if(savedDetails[i])
17860 free(savedDetails[i]);
17861 savedDetails[i] = NULL;
17863 for(i=framePtr; i<MAX_MOVES; i++) {
17864 if(commentList[i]) free(commentList[i]);
17865 commentList[i] = NULL;
17867 framePtr = MAX_MOVES-1;
17872 LoadVariation (int index, char *text)
17873 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17874 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17875 int level = 0, move;
17877 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17878 // first find outermost bracketing variation
17879 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17880 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17881 if(*p == '{') wait = '}'; else
17882 if(*p == '[') wait = ']'; else
17883 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17884 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17886 if(*p == wait) wait = NULLCHAR; // closing ]} found
17889 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17890 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17891 end[1] = NULLCHAR; // clip off comment beyond variation
17892 ToNrEvent(currentMove-1);
17893 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17894 // kludge: use ParsePV() to append variation to game
17895 move = currentMove;
17896 ParsePV(start, TRUE, TRUE);
17897 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17898 ClearPremoveHighlights();
17900 ToNrEvent(currentMove+1);
17906 char *p, *q, buf[MSG_SIZ];
17907 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17908 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17909 ParseArgsFromString(buf);
17910 ActivateTheme(TRUE); // also redo colors
17914 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17917 q = appData.themeNames;
17918 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17919 if(appData.useBitmaps) {
17920 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17921 appData.liteBackTextureFile, appData.darkBackTextureFile,
17922 appData.liteBackTextureMode,
17923 appData.darkBackTextureMode );
17925 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17926 Col2Text(2), // lightSquareColor
17927 Col2Text(3) ); // darkSquareColor
17929 if(appData.useBorder) {
17930 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17933 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17935 if(appData.useFont) {
17936 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17937 appData.renderPiecesWithFont,
17938 appData.fontToPieceTable,
17939 Col2Text(9), // appData.fontBackColorWhite
17940 Col2Text(10) ); // appData.fontForeColorBlack
17942 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17943 appData.pieceDirectory);
17944 if(!appData.pieceDirectory[0])
17945 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17946 Col2Text(0), // whitePieceColor
17947 Col2Text(1) ); // blackPieceColor
17949 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17950 Col2Text(4), // highlightSquareColor
17951 Col2Text(5) ); // premoveHighlightColor
17952 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17953 if(insert != q) insert[-1] = NULLCHAR;
17954 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17957 ActivateTheme(FALSE);