2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 int flock(int f, int code);
75 #include <sys/types.h>
84 #else /* not STDC_HEADERS */
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
105 # include <sys/time.h>
111 #if defined(_amigados) && !defined(__GNUC__)
116 extern int gettimeofday(struct timeval *, struct timezone *);
124 #include "frontend.h"
131 #include "backendz.h"
132 #include "evalgraph.h"
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153 char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180 char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182 int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((void));
231 extern void ConsoleCreate();
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
254 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
279 /* States for ics_getting_history */
281 #define H_REQUESTED 1
282 #define H_GOT_REQ_HEADER 2
283 #define H_GOT_UNREQ_HEADER 3
284 #define H_GETTING_MOVES 4
285 #define H_GOT_UNWANTED_HEADER 5
287 /* whosays values for GameEnds */
296 /* Maximum number of games in a cmail message */
297 #define CMAIL_MAX_GAMES 20
299 /* Different types of move when calling RegisterMove */
301 #define CMAIL_RESIGN 1
303 #define CMAIL_ACCEPT 3
305 /* Different types of result to remember for each game */
306 #define CMAIL_NOT_RESULT 0
307 #define CMAIL_OLD_RESULT 1
308 #define CMAIL_NEW_RESULT 2
310 /* Telnet protocol constants */
321 safeStrCpy (char *dst, const char *src, size_t count)
324 assert( dst != NULL );
325 assert( src != NULL );
328 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
329 if( i == count && dst[count-1] != NULLCHAR)
331 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
332 if(appData.debugMode)
333 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
339 /* Some compiler can't cast u64 to double
340 * This function do the job for us:
342 * We use the highest bit for cast, this only
343 * works if the highest bit is not
344 * in use (This should not happen)
346 * We used this for all compiler
349 u64ToDouble (u64 value)
352 u64 tmp = value & u64Const(0x7fffffffffffffff);
353 r = (double)(s64)tmp;
354 if (value & u64Const(0x8000000000000000))
355 r += 9.2233720368547758080e18; /* 2^63 */
359 /* Fake up flags for now, as we aren't keeping track of castling
360 availability yet. [HGM] Change of logic: the flag now only
361 indicates the type of castlings allowed by the rule of the game.
362 The actual rights themselves are maintained in the array
363 castlingRights, as part of the game history, and are not probed
369 int flags = F_ALL_CASTLE_OK;
370 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
371 switch (gameInfo.variant) {
373 flags &= ~F_ALL_CASTLE_OK;
374 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
375 flags |= F_IGNORE_CHECK;
377 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
380 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
382 case VariantKriegspiel:
383 flags |= F_KRIEGSPIEL_CAPTURE;
385 case VariantCapaRandom:
386 case VariantFischeRandom:
387 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
388 case VariantNoCastle:
389 case VariantShatranj:
394 flags &= ~F_ALL_CASTLE_OK;
402 FILE *gameFileFP, *debugFP, *serverFP;
403 char *currentDebugFile; // [HGM] debug split: to remember name
406 [AS] Note: sometimes, the sscanf() function is used to parse the input
407 into a fixed-size buffer. Because of this, we must be prepared to
408 receive strings as long as the size of the input buffer, which is currently
409 set to 4K for Windows and 8K for the rest.
410 So, we must either allocate sufficiently large buffers here, or
411 reduce the size of the input buffer in the input reading part.
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
418 ChessProgramState first, second, pairing;
420 /* premove variables */
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
430 char *ics_prefix = "$";
431 enum ICS_TYPE ics_type = ICS_GENERIC;
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey, controlKey; // [HGM] set by mouse handler
461 int have_sent_ICS_logon = 0;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE, startingEngine = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
475 /* animateTraining preserves the state of appData.animate
476 * when Training mode is activated. This allows the
477 * response to be animated when appData.animate == TRUE and
478 * appData.animateDragging == TRUE.
480 Boolean animateTraining;
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char initialRights[BOARD_FILES];
490 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int initialRulePlies, FENrulePlies;
492 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
513 ChessSquare FIDEArray[2][BOARD_FILES] = {
514 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517 BlackKing, BlackBishop, BlackKnight, BlackRook }
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524 BlackKing, BlackKing, BlackKnight, BlackRook }
527 ChessSquare KnightmateArray[2][BOARD_FILES] = {
528 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530 { BlackRook, BlackMan, BlackBishop, BlackQueen,
531 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558 { BlackRook, BlackKnight, BlackMan, BlackFerz,
559 BlackKing, BlackMan, BlackKnight, BlackRook }
562 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
563 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
564 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
565 { BlackRook, BlackKnight, BlackMan, BlackFerz,
566 BlackKing, BlackMan, BlackKnight, BlackRook }
569 ChessSquare lionArray[2][BOARD_FILES] = {
570 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
571 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
572 { BlackRook, BlackLion, BlackBishop, BlackQueen,
573 BlackKing, BlackBishop, BlackKnight, BlackRook }
577 #if (BOARD_FILES>=10)
578 ChessSquare ShogiArray[2][BOARD_FILES] = {
579 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
580 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
581 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
582 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
585 ChessSquare XiangqiArray[2][BOARD_FILES] = {
586 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
587 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
588 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
589 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
592 ChessSquare CapablancaArray[2][BOARD_FILES] = {
593 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
594 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
595 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
596 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
599 ChessSquare GreatArray[2][BOARD_FILES] = {
600 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
601 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
602 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
603 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
606 ChessSquare JanusArray[2][BOARD_FILES] = {
607 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
608 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
609 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
610 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
613 ChessSquare GrandArray[2][BOARD_FILES] = {
614 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
615 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
616 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
617 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
621 ChessSquare GothicArray[2][BOARD_FILES] = {
622 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
623 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
624 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
625 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
628 #define GothicArray CapablancaArray
632 ChessSquare FalconArray[2][BOARD_FILES] = {
633 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
634 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
635 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
636 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
639 #define FalconArray CapablancaArray
642 #else // !(BOARD_FILES>=10)
643 #define XiangqiPosition FIDEArray
644 #define CapablancaArray FIDEArray
645 #define GothicArray FIDEArray
646 #define GreatArray FIDEArray
647 #endif // !(BOARD_FILES>=10)
649 #if (BOARD_FILES>=12)
650 ChessSquare CourierArray[2][BOARD_FILES] = {
651 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
652 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
653 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
654 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
656 ChessSquare ChuArray[6][BOARD_FILES] = {
657 { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
658 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
659 { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
660 BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
661 { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
662 WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
663 { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
664 BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
665 { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
666 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
667 { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
668 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
670 #else // !(BOARD_FILES>=12)
671 #define CourierArray CapablancaArray
672 #define ChuArray CapablancaArray
673 #endif // !(BOARD_FILES>=12)
676 Board initialPosition;
679 /* Convert str to a rating. Checks for special cases of "----",
681 "++++", etc. Also strips ()'s */
683 string_to_rating (char *str)
685 while(*str && !isdigit(*str)) ++str;
687 return 0; /* One of the special "no rating" cases */
695 /* Init programStats */
696 programStats.movelist[0] = 0;
697 programStats.depth = 0;
698 programStats.nr_moves = 0;
699 programStats.moves_left = 0;
700 programStats.nodes = 0;
701 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
702 programStats.score = 0;
703 programStats.got_only_move = 0;
704 programStats.got_fail = 0;
705 programStats.line_is_book = 0;
710 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
711 if (appData.firstPlaysBlack) {
712 first.twoMachinesColor = "black\n";
713 second.twoMachinesColor = "white\n";
715 first.twoMachinesColor = "white\n";
716 second.twoMachinesColor = "black\n";
719 first.other = &second;
720 second.other = &first;
723 if(appData.timeOddsMode) {
724 norm = appData.timeOdds[0];
725 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
727 first.timeOdds = appData.timeOdds[0]/norm;
728 second.timeOdds = appData.timeOdds[1]/norm;
731 if(programVersion) free(programVersion);
732 if (appData.noChessProgram) {
733 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
734 sprintf(programVersion, "%s", PACKAGE_STRING);
736 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
737 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
738 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
743 UnloadEngine (ChessProgramState *cps)
745 /* Kill off first chess program */
746 if (cps->isr != NULL)
747 RemoveInputSource(cps->isr);
750 if (cps->pr != NoProc) {
752 DoSleep( appData.delayBeforeQuit );
753 SendToProgram("quit\n", cps);
754 DoSleep( appData.delayAfterQuit );
755 DestroyChildProcess(cps->pr, cps->useSigterm);
758 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
762 ClearOptions (ChessProgramState *cps)
765 cps->nrOptions = cps->comboCnt = 0;
766 for(i=0; i<MAX_OPTIONS; i++) {
767 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
768 cps->option[i].textValue = 0;
772 char *engineNames[] = {
773 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
774 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
776 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
777 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
782 InitEngine (ChessProgramState *cps, int n)
783 { // [HGM] all engine initialiation put in a function that does one engine
787 cps->which = engineNames[n];
788 cps->maybeThinking = FALSE;
792 cps->sendDrawOffers = 1;
794 cps->program = appData.chessProgram[n];
795 cps->host = appData.host[n];
796 cps->dir = appData.directory[n];
797 cps->initString = appData.engInitString[n];
798 cps->computerString = appData.computerString[n];
799 cps->useSigint = TRUE;
800 cps->useSigterm = TRUE;
801 cps->reuse = appData.reuse[n];
802 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
803 cps->useSetboard = FALSE;
805 cps->usePing = FALSE;
808 cps->usePlayother = FALSE;
809 cps->useColors = TRUE;
810 cps->useUsermove = FALSE;
811 cps->sendICS = FALSE;
812 cps->sendName = appData.icsActive;
813 cps->sdKludge = FALSE;
814 cps->stKludge = FALSE;
815 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
816 TidyProgramName(cps->program, cps->host, cps->tidy);
818 ASSIGN(cps->variants, appData.variant);
819 cps->analysisSupport = 2; /* detect */
820 cps->analyzing = FALSE;
821 cps->initDone = FALSE;
824 /* New features added by Tord: */
825 cps->useFEN960 = FALSE;
826 cps->useOOCastle = TRUE;
827 /* End of new features added by Tord. */
828 cps->fenOverride = appData.fenOverride[n];
830 /* [HGM] time odds: set factor for each machine */
831 cps->timeOdds = appData.timeOdds[n];
833 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
834 cps->accumulateTC = appData.accumulateTC[n];
835 cps->maxNrOfSessions = 1;
840 cps->supportsNPS = UNKNOWN;
841 cps->memSize = FALSE;
842 cps->maxCores = FALSE;
843 ASSIGN(cps->egtFormats, "");
846 cps->optionSettings = appData.engOptions[n];
848 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
849 cps->isUCI = appData.isUCI[n]; /* [AS] */
850 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
853 if (appData.protocolVersion[n] > PROTOVER
854 || appData.protocolVersion[n] < 1)
859 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
860 appData.protocolVersion[n]);
861 if( (len >= MSG_SIZ) && appData.debugMode )
862 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
864 DisplayFatalError(buf, 0, 2);
868 cps->protocolVersion = appData.protocolVersion[n];
871 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
872 ParseFeatures(appData.featureDefaults, cps);
875 ChessProgramState *savCps;
883 if(WaitForEngine(savCps, LoadEngine)) return;
884 CommonEngineInit(); // recalculate time odds
885 if(gameInfo.variant != StringToVariant(appData.variant)) {
886 // we changed variant when loading the engine; this forces us to reset
887 Reset(TRUE, savCps != &first);
888 oldMode = BeginningOfGame; // to prevent restoring old mode
890 InitChessProgram(savCps, FALSE);
891 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
892 DisplayMessage("", "");
893 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
894 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
897 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
901 ReplaceEngine (ChessProgramState *cps, int n)
903 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
905 if(oldMode != BeginningOfGame) EditGameEvent();
908 appData.noChessProgram = FALSE;
909 appData.clockMode = TRUE;
912 if(n) return; // only startup first engine immediately; second can wait
913 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
917 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
918 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
920 static char resetOptions[] =
921 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
922 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
923 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
924 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
927 FloatToFront(char **list, char *engineLine)
929 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
931 if(appData.recentEngines <= 0) return;
932 TidyProgramName(engineLine, "localhost", tidy+1);
933 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
934 strncpy(buf+1, *list, MSG_SIZ-50);
935 if(p = strstr(buf, tidy)) { // tidy name appears in list
936 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
937 while(*p++ = *++q); // squeeze out
939 strcat(tidy, buf+1); // put list behind tidy name
940 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
941 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
942 ASSIGN(*list, tidy+1);
945 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
948 Load (ChessProgramState *cps, int i)
950 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
951 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
952 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
953 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
954 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
955 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
956 appData.firstProtocolVersion = PROTOVER;
957 ParseArgsFromString(buf);
959 ReplaceEngine(cps, i);
960 FloatToFront(&appData.recentEngineList, engineLine);
964 while(q = strchr(p, SLASH)) p = q+1;
965 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
966 if(engineDir[0] != NULLCHAR) {
967 ASSIGN(appData.directory[i], engineDir); p = engineName;
968 } else if(p != engineName) { // derive directory from engine path, when not given
970 ASSIGN(appData.directory[i], engineName);
972 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
973 } else { ASSIGN(appData.directory[i], "."); }
974 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
976 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
977 snprintf(command, MSG_SIZ, "%s %s", p, params);
980 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
981 ASSIGN(appData.chessProgram[i], p);
982 appData.isUCI[i] = isUCI;
983 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
984 appData.hasOwnBookUCI[i] = hasBook;
985 if(!nickName[0]) useNick = FALSE;
986 if(useNick) ASSIGN(appData.pgnName[i], nickName);
990 q = firstChessProgramNames;
991 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
992 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
993 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
994 quote, p, quote, appData.directory[i],
995 useNick ? " -fn \"" : "",
996 useNick ? nickName : "",
998 v1 ? " -firstProtocolVersion 1" : "",
999 hasBook ? "" : " -fNoOwnBookUCI",
1000 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1001 storeVariant ? " -variant " : "",
1002 storeVariant ? VariantName(gameInfo.variant) : "");
1003 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1004 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1005 if(insert != q) insert[-1] = NULLCHAR;
1006 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1008 FloatToFront(&appData.recentEngineList, buf);
1010 ReplaceEngine(cps, i);
1016 int matched, min, sec;
1018 * Parse timeControl resource
1020 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1021 appData.movesPerSession)) {
1023 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1024 DisplayFatalError(buf, 0, 2);
1028 * Parse searchTime resource
1030 if (*appData.searchTime != NULLCHAR) {
1031 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1033 searchTime = min * 60;
1034 } else if (matched == 2) {
1035 searchTime = min * 60 + sec;
1038 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1039 DisplayFatalError(buf, 0, 2);
1048 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1049 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1051 GetTimeMark(&programStartTime);
1052 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1053 appData.seedBase = random() + (random()<<15);
1054 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1056 ClearProgramStats();
1057 programStats.ok_to_send = 1;
1058 programStats.seen_stat = 0;
1061 * Initialize game list
1067 * Internet chess server status
1069 if (appData.icsActive) {
1070 appData.matchMode = FALSE;
1071 appData.matchGames = 0;
1073 appData.noChessProgram = !appData.zippyPlay;
1075 appData.zippyPlay = FALSE;
1076 appData.zippyTalk = FALSE;
1077 appData.noChessProgram = TRUE;
1079 if (*appData.icsHelper != NULLCHAR) {
1080 appData.useTelnet = TRUE;
1081 appData.telnetProgram = appData.icsHelper;
1084 appData.zippyTalk = appData.zippyPlay = FALSE;
1087 /* [AS] Initialize pv info list [HGM] and game state */
1091 for( i=0; i<=framePtr; i++ ) {
1092 pvInfoList[i].depth = -1;
1093 boards[i][EP_STATUS] = EP_NONE;
1094 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1100 /* [AS] Adjudication threshold */
1101 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1103 InitEngine(&first, 0);
1104 InitEngine(&second, 1);
1107 pairing.which = "pairing"; // pairing engine
1108 pairing.pr = NoProc;
1110 pairing.program = appData.pairingEngine;
1111 pairing.host = "localhost";
1114 if (appData.icsActive) {
1115 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1116 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1117 appData.clockMode = FALSE;
1118 first.sendTime = second.sendTime = 0;
1122 /* Override some settings from environment variables, for backward
1123 compatibility. Unfortunately it's not feasible to have the env
1124 vars just set defaults, at least in xboard. Ugh.
1126 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1131 if (!appData.icsActive) {
1135 /* Check for variants that are supported only in ICS mode,
1136 or not at all. Some that are accepted here nevertheless
1137 have bugs; see comments below.
1139 VariantClass variant = StringToVariant(appData.variant);
1141 case VariantBughouse: /* need four players and two boards */
1142 case VariantKriegspiel: /* need to hide pieces and move details */
1143 /* case VariantFischeRandom: (Fabien: moved below) */
1144 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1145 if( (len >= MSG_SIZ) && appData.debugMode )
1146 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1148 DisplayFatalError(buf, 0, 2);
1151 case VariantUnknown:
1152 case VariantLoadable:
1162 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1163 if( (len >= MSG_SIZ) && appData.debugMode )
1164 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1166 DisplayFatalError(buf, 0, 2);
1169 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1170 case VariantFairy: /* [HGM] TestLegality definitely off! */
1171 case VariantGothic: /* [HGM] should work */
1172 case VariantCapablanca: /* [HGM] should work */
1173 case VariantCourier: /* [HGM] initial forced moves not implemented */
1174 case VariantShogi: /* [HGM] could still mate with pawn drop */
1175 case VariantChu: /* [HGM] experimental */
1176 case VariantKnightmate: /* [HGM] should work */
1177 case VariantCylinder: /* [HGM] untested */
1178 case VariantFalcon: /* [HGM] untested */
1179 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1180 offboard interposition not understood */
1181 case VariantNormal: /* definitely works! */
1182 case VariantWildCastle: /* pieces not automatically shuffled */
1183 case VariantNoCastle: /* pieces not automatically shuffled */
1184 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1185 case VariantLosers: /* should work except for win condition,
1186 and doesn't know captures are mandatory */
1187 case VariantSuicide: /* should work except for win condition,
1188 and doesn't know captures are mandatory */
1189 case VariantGiveaway: /* should work except for win condition,
1190 and doesn't know captures are mandatory */
1191 case VariantTwoKings: /* should work */
1192 case VariantAtomic: /* should work except for win condition */
1193 case Variant3Check: /* should work except for win condition */
1194 case VariantShatranj: /* should work except for all win conditions */
1195 case VariantMakruk: /* should work except for draw countdown */
1196 case VariantASEAN : /* should work except for draw countdown */
1197 case VariantBerolina: /* might work if TestLegality is off */
1198 case VariantCapaRandom: /* should work */
1199 case VariantJanus: /* should work */
1200 case VariantSuper: /* experimental */
1201 case VariantGreat: /* experimental, requires legality testing to be off */
1202 case VariantSChess: /* S-Chess, should work */
1203 case VariantGrand: /* should work */
1204 case VariantSpartan: /* should work */
1205 case VariantLion: /* should work */
1213 NextIntegerFromString (char ** str, long * value)
1218 while( *s == ' ' || *s == '\t' ) {
1224 if( *s >= '0' && *s <= '9' ) {
1225 while( *s >= '0' && *s <= '9' ) {
1226 *value = *value * 10 + (*s - '0');
1239 NextTimeControlFromString (char ** str, long * value)
1242 int result = NextIntegerFromString( str, &temp );
1245 *value = temp * 60; /* Minutes */
1246 if( **str == ':' ) {
1248 result = NextIntegerFromString( str, &temp );
1249 *value += temp; /* Seconds */
1257 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1258 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1259 int result = -1, type = 0; long temp, temp2;
1261 if(**str != ':') return -1; // old params remain in force!
1263 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1264 if( NextIntegerFromString( str, &temp ) ) return -1;
1265 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1268 /* time only: incremental or sudden-death time control */
1269 if(**str == '+') { /* increment follows; read it */
1271 if(**str == '!') type = *(*str)++; // Bronstein TC
1272 if(result = NextIntegerFromString( str, &temp2)) return -1;
1273 *inc = temp2 * 1000;
1274 if(**str == '.') { // read fraction of increment
1275 char *start = ++(*str);
1276 if(result = NextIntegerFromString( str, &temp2)) return -1;
1278 while(start++ < *str) temp2 /= 10;
1282 *moves = 0; *tc = temp * 1000; *incType = type;
1286 (*str)++; /* classical time control */
1287 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1299 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1300 { /* [HGM] get time to add from the multi-session time-control string */
1301 int incType, moves=1; /* kludge to force reading of first session */
1302 long time, increment;
1305 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1307 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1308 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1309 if(movenr == -1) return time; /* last move before new session */
1310 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1311 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1312 if(!moves) return increment; /* current session is incremental */
1313 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1314 } while(movenr >= -1); /* try again for next session */
1316 return 0; // no new time quota on this move
1320 ParseTimeControl (char *tc, float ti, int mps)
1324 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1327 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1328 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1329 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1333 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1335 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1338 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1340 snprintf(buf, MSG_SIZ, ":%s", mytc);
1342 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1344 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1349 /* Parse second time control */
1352 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1360 timeControl_2 = tc2 * 1000;
1370 timeControl = tc1 * 1000;
1373 timeIncrement = ti * 1000; /* convert to ms */
1374 movesPerSession = 0;
1377 movesPerSession = mps;
1385 if (appData.debugMode) {
1386 # ifdef __GIT_VERSION
1387 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1389 fprintf(debugFP, "Version: %s\n", programVersion);
1392 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1394 set_cont_sequence(appData.wrapContSeq);
1395 if (appData.matchGames > 0) {
1396 appData.matchMode = TRUE;
1397 } else if (appData.matchMode) {
1398 appData.matchGames = 1;
1400 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1401 appData.matchGames = appData.sameColorGames;
1402 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1403 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1404 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1407 if (appData.noChessProgram || first.protocolVersion == 1) {
1410 /* kludge: allow timeout for initial "feature" commands */
1412 DisplayMessage("", _("Starting chess program"));
1413 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1418 CalculateIndex (int index, int gameNr)
1419 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1421 if(index > 0) return index; // fixed nmber
1422 if(index == 0) return 1;
1423 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1424 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1429 LoadGameOrPosition (int gameNr)
1430 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1431 if (*appData.loadGameFile != NULLCHAR) {
1432 if (!LoadGameFromFile(appData.loadGameFile,
1433 CalculateIndex(appData.loadGameIndex, gameNr),
1434 appData.loadGameFile, FALSE)) {
1435 DisplayFatalError(_("Bad game file"), 0, 1);
1438 } else if (*appData.loadPositionFile != NULLCHAR) {
1439 if (!LoadPositionFromFile(appData.loadPositionFile,
1440 CalculateIndex(appData.loadPositionIndex, gameNr),
1441 appData.loadPositionFile)) {
1442 DisplayFatalError(_("Bad position file"), 0, 1);
1450 ReserveGame (int gameNr, char resChar)
1452 FILE *tf = fopen(appData.tourneyFile, "r+");
1453 char *p, *q, c, buf[MSG_SIZ];
1454 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1455 safeStrCpy(buf, lastMsg, MSG_SIZ);
1456 DisplayMessage(_("Pick new game"), "");
1457 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1458 ParseArgsFromFile(tf);
1459 p = q = appData.results;
1460 if(appData.debugMode) {
1461 char *r = appData.participants;
1462 fprintf(debugFP, "results = '%s'\n", p);
1463 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1464 fprintf(debugFP, "\n");
1466 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1468 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1469 safeStrCpy(q, p, strlen(p) + 2);
1470 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1471 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1472 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1473 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1476 fseek(tf, -(strlen(p)+4), SEEK_END);
1478 if(c != '"') // depending on DOS or Unix line endings we can be one off
1479 fseek(tf, -(strlen(p)+2), SEEK_END);
1480 else fseek(tf, -(strlen(p)+3), SEEK_END);
1481 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1482 DisplayMessage(buf, "");
1483 free(p); appData.results = q;
1484 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1485 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1486 int round = appData.defaultMatchGames * appData.tourneyType;
1487 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1488 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1489 UnloadEngine(&first); // next game belongs to other pairing;
1490 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1492 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1496 MatchEvent (int mode)
1497 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1499 if(matchMode) { // already in match mode: switch it off
1501 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1504 // if(gameMode != BeginningOfGame) {
1505 // DisplayError(_("You can only start a match from the initial position."), 0);
1509 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1510 /* Set up machine vs. machine match */
1512 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1513 if(appData.tourneyFile[0]) {
1515 if(nextGame > appData.matchGames) {
1517 if(strchr(appData.results, '*') == NULL) {
1519 appData.tourneyCycles++;
1520 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1522 NextTourneyGame(-1, &dummy);
1524 if(nextGame <= appData.matchGames) {
1525 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1527 ScheduleDelayedEvent(NextMatchGame, 10000);
1532 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1533 DisplayError(buf, 0);
1534 appData.tourneyFile[0] = 0;
1538 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1539 DisplayFatalError(_("Can't have a match with no chess programs"),
1544 matchGame = roundNr = 1;
1545 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1549 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1552 InitBackEnd3 P((void))
1554 GameMode initialMode;
1558 InitChessProgram(&first, startedFromSetupPosition);
1560 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1561 free(programVersion);
1562 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1563 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1564 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1567 if (appData.icsActive) {
1569 /* [DM] Make a console window if needed [HGM] merged ifs */
1575 if (*appData.icsCommPort != NULLCHAR)
1576 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1577 appData.icsCommPort);
1579 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1580 appData.icsHost, appData.icsPort);
1582 if( (len >= MSG_SIZ) && appData.debugMode )
1583 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1585 DisplayFatalError(buf, err, 1);
1590 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1592 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1593 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1594 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1595 } else if (appData.noChessProgram) {
1601 if (*appData.cmailGameName != NULLCHAR) {
1603 OpenLoopback(&cmailPR);
1605 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1609 DisplayMessage("", "");
1610 if (StrCaseCmp(appData.initialMode, "") == 0) {
1611 initialMode = BeginningOfGame;
1612 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1613 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1614 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1615 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1618 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1619 initialMode = TwoMachinesPlay;
1620 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1621 initialMode = AnalyzeFile;
1622 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1623 initialMode = AnalyzeMode;
1624 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1625 initialMode = MachinePlaysWhite;
1626 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1627 initialMode = MachinePlaysBlack;
1628 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1629 initialMode = EditGame;
1630 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1631 initialMode = EditPosition;
1632 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1633 initialMode = Training;
1635 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1636 if( (len >= MSG_SIZ) && appData.debugMode )
1637 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1639 DisplayFatalError(buf, 0, 2);
1643 if (appData.matchMode) {
1644 if(appData.tourneyFile[0]) { // start tourney from command line
1646 if(f = fopen(appData.tourneyFile, "r")) {
1647 ParseArgsFromFile(f); // make sure tourney parmeters re known
1649 appData.clockMode = TRUE;
1651 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1654 } else if (*appData.cmailGameName != NULLCHAR) {
1655 /* Set up cmail mode */
1656 ReloadCmailMsgEvent(TRUE);
1658 /* Set up other modes */
1659 if (initialMode == AnalyzeFile) {
1660 if (*appData.loadGameFile == NULLCHAR) {
1661 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1665 if (*appData.loadGameFile != NULLCHAR) {
1666 (void) LoadGameFromFile(appData.loadGameFile,
1667 appData.loadGameIndex,
1668 appData.loadGameFile, TRUE);
1669 } else if (*appData.loadPositionFile != NULLCHAR) {
1670 (void) LoadPositionFromFile(appData.loadPositionFile,
1671 appData.loadPositionIndex,
1672 appData.loadPositionFile);
1673 /* [HGM] try to make self-starting even after FEN load */
1674 /* to allow automatic setup of fairy variants with wtm */
1675 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1676 gameMode = BeginningOfGame;
1677 setboardSpoiledMachineBlack = 1;
1679 /* [HGM] loadPos: make that every new game uses the setup */
1680 /* from file as long as we do not switch variant */
1681 if(!blackPlaysFirst) {
1682 startedFromPositionFile = TRUE;
1683 CopyBoard(filePosition, boards[0]);
1686 if (initialMode == AnalyzeMode) {
1687 if (appData.noChessProgram) {
1688 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1691 if (appData.icsActive) {
1692 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1696 } else if (initialMode == AnalyzeFile) {
1697 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1698 ShowThinkingEvent();
1700 AnalysisPeriodicEvent(1);
1701 } else if (initialMode == MachinePlaysWhite) {
1702 if (appData.noChessProgram) {
1703 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1707 if (appData.icsActive) {
1708 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1712 MachineWhiteEvent();
1713 } else if (initialMode == MachinePlaysBlack) {
1714 if (appData.noChessProgram) {
1715 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1719 if (appData.icsActive) {
1720 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1724 MachineBlackEvent();
1725 } else if (initialMode == TwoMachinesPlay) {
1726 if (appData.noChessProgram) {
1727 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1731 if (appData.icsActive) {
1732 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1737 } else if (initialMode == EditGame) {
1739 } else if (initialMode == EditPosition) {
1740 EditPositionEvent();
1741 } else if (initialMode == Training) {
1742 if (*appData.loadGameFile == NULLCHAR) {
1743 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1752 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1754 DisplayBook(current+1);
1756 MoveHistorySet( movelist, first, last, current, pvInfoList );
1758 EvalGraphSet( first, last, current, pvInfoList );
1760 MakeEngineOutputTitle();
1764 * Establish will establish a contact to a remote host.port.
1765 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1766 * used to talk to the host.
1767 * Returns 0 if okay, error code if not.
1774 if (*appData.icsCommPort != NULLCHAR) {
1775 /* Talk to the host through a serial comm port */
1776 return OpenCommPort(appData.icsCommPort, &icsPR);
1778 } else if (*appData.gateway != NULLCHAR) {
1779 if (*appData.remoteShell == NULLCHAR) {
1780 /* Use the rcmd protocol to run telnet program on a gateway host */
1781 snprintf(buf, sizeof(buf), "%s %s %s",
1782 appData.telnetProgram, appData.icsHost, appData.icsPort);
1783 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1786 /* Use the rsh program to run telnet program on a gateway host */
1787 if (*appData.remoteUser == NULLCHAR) {
1788 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1789 appData.gateway, appData.telnetProgram,
1790 appData.icsHost, appData.icsPort);
1792 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1793 appData.remoteShell, appData.gateway,
1794 appData.remoteUser, appData.telnetProgram,
1795 appData.icsHost, appData.icsPort);
1797 return StartChildProcess(buf, "", &icsPR);
1800 } else if (appData.useTelnet) {
1801 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1804 /* TCP socket interface differs somewhat between
1805 Unix and NT; handle details in the front end.
1807 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1812 EscapeExpand (char *p, char *q)
1813 { // [HGM] initstring: routine to shape up string arguments
1814 while(*p++ = *q++) if(p[-1] == '\\')
1816 case 'n': p[-1] = '\n'; break;
1817 case 'r': p[-1] = '\r'; break;
1818 case 't': p[-1] = '\t'; break;
1819 case '\\': p[-1] = '\\'; break;
1820 case 0: *p = 0; return;
1821 default: p[-1] = q[-1]; break;
1826 show_bytes (FILE *fp, char *buf, int count)
1829 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1830 fprintf(fp, "\\%03o", *buf & 0xff);
1839 /* Returns an errno value */
1841 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1843 char buf[8192], *p, *q, *buflim;
1844 int left, newcount, outcount;
1846 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1847 *appData.gateway != NULLCHAR) {
1848 if (appData.debugMode) {
1849 fprintf(debugFP, ">ICS: ");
1850 show_bytes(debugFP, message, count);
1851 fprintf(debugFP, "\n");
1853 return OutputToProcess(pr, message, count, outError);
1856 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1863 if (appData.debugMode) {
1864 fprintf(debugFP, ">ICS: ");
1865 show_bytes(debugFP, buf, newcount);
1866 fprintf(debugFP, "\n");
1868 outcount = OutputToProcess(pr, buf, newcount, outError);
1869 if (outcount < newcount) return -1; /* to be sure */
1876 } else if (((unsigned char) *p) == TN_IAC) {
1877 *q++ = (char) TN_IAC;
1884 if (appData.debugMode) {
1885 fprintf(debugFP, ">ICS: ");
1886 show_bytes(debugFP, buf, newcount);
1887 fprintf(debugFP, "\n");
1889 outcount = OutputToProcess(pr, buf, newcount, outError);
1890 if (outcount < newcount) return -1; /* to be sure */
1895 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1897 int outError, outCount;
1898 static int gotEof = 0;
1901 /* Pass data read from player on to ICS */
1904 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1905 if (outCount < count) {
1906 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1908 if(have_sent_ICS_logon == 2) {
1909 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1910 fprintf(ini, "%s", message);
1911 have_sent_ICS_logon = 3;
1913 have_sent_ICS_logon = 1;
1914 } else if(have_sent_ICS_logon == 3) {
1915 fprintf(ini, "%s", message);
1917 have_sent_ICS_logon = 1;
1919 } else if (count < 0) {
1920 RemoveInputSource(isr);
1921 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1922 } else if (gotEof++ > 0) {
1923 RemoveInputSource(isr);
1924 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1930 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1931 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1932 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1933 SendToICS("date\n");
1934 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1937 /* added routine for printf style output to ics */
1939 ics_printf (char *format, ...)
1941 char buffer[MSG_SIZ];
1944 va_start(args, format);
1945 vsnprintf(buffer, sizeof(buffer), format, args);
1946 buffer[sizeof(buffer)-1] = '\0';
1954 int count, outCount, outError;
1956 if (icsPR == NoProc) return;
1959 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1960 if (outCount < count) {
1961 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1965 /* This is used for sending logon scripts to the ICS. Sending
1966 without a delay causes problems when using timestamp on ICC
1967 (at least on my machine). */
1969 SendToICSDelayed (char *s, long msdelay)
1971 int count, outCount, outError;
1973 if (icsPR == NoProc) return;
1976 if (appData.debugMode) {
1977 fprintf(debugFP, ">ICS: ");
1978 show_bytes(debugFP, s, count);
1979 fprintf(debugFP, "\n");
1981 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1983 if (outCount < count) {
1984 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1989 /* Remove all highlighting escape sequences in s
1990 Also deletes any suffix starting with '('
1993 StripHighlightAndTitle (char *s)
1995 static char retbuf[MSG_SIZ];
1998 while (*s != NULLCHAR) {
1999 while (*s == '\033') {
2000 while (*s != NULLCHAR && !isalpha(*s)) s++;
2001 if (*s != NULLCHAR) s++;
2003 while (*s != NULLCHAR && *s != '\033') {
2004 if (*s == '(' || *s == '[') {
2015 /* Remove all highlighting escape sequences in s */
2017 StripHighlight (char *s)
2019 static char retbuf[MSG_SIZ];
2022 while (*s != NULLCHAR) {
2023 while (*s == '\033') {
2024 while (*s != NULLCHAR && !isalpha(*s)) s++;
2025 if (*s != NULLCHAR) s++;
2027 while (*s != NULLCHAR && *s != '\033') {
2035 char engineVariant[MSG_SIZ];
2036 char *variantNames[] = VARIANT_NAMES;
2038 VariantName (VariantClass v)
2040 if(v == VariantUnknown || *engineVariant) return engineVariant;
2041 return variantNames[v];
2045 /* Identify a variant from the strings the chess servers use or the
2046 PGN Variant tag names we use. */
2048 StringToVariant (char *e)
2052 VariantClass v = VariantNormal;
2053 int i, found = FALSE;
2059 /* [HGM] skip over optional board-size prefixes */
2060 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2061 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2062 while( *e++ != '_');
2065 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2069 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2070 if (StrCaseStr(e, variantNames[i])) {
2071 v = (VariantClass) i;
2078 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2079 || StrCaseStr(e, "wild/fr")
2080 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2081 v = VariantFischeRandom;
2082 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2083 (i = 1, p = StrCaseStr(e, "w"))) {
2085 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2092 case 0: /* FICS only, actually */
2094 /* Castling legal even if K starts on d-file */
2095 v = VariantWildCastle;
2100 /* Castling illegal even if K & R happen to start in
2101 normal positions. */
2102 v = VariantNoCastle;
2115 /* Castling legal iff K & R start in normal positions */
2121 /* Special wilds for position setup; unclear what to do here */
2122 v = VariantLoadable;
2125 /* Bizarre ICC game */
2126 v = VariantTwoKings;
2129 v = VariantKriegspiel;
2135 v = VariantFischeRandom;
2138 v = VariantCrazyhouse;
2141 v = VariantBughouse;
2147 /* Not quite the same as FICS suicide! */
2148 v = VariantGiveaway;
2154 v = VariantShatranj;
2157 /* Temporary names for future ICC types. The name *will* change in
2158 the next xboard/WinBoard release after ICC defines it. */
2196 v = VariantCapablanca;
2199 v = VariantKnightmate;
2205 v = VariantCylinder;
2211 v = VariantCapaRandom;
2214 v = VariantBerolina;
2226 /* Found "wild" or "w" in the string but no number;
2227 must assume it's normal chess. */
2231 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2232 if( (len >= MSG_SIZ) && appData.debugMode )
2233 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2235 DisplayError(buf, 0);
2241 if (appData.debugMode) {
2242 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2243 e, wnum, VariantName(v));
2248 static int leftover_start = 0, leftover_len = 0;
2249 char star_match[STAR_MATCH_N][MSG_SIZ];
2251 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2252 advance *index beyond it, and set leftover_start to the new value of
2253 *index; else return FALSE. If pattern contains the character '*', it
2254 matches any sequence of characters not containing '\r', '\n', or the
2255 character following the '*' (if any), and the matched sequence(s) are
2256 copied into star_match.
2259 looking_at ( char *buf, int *index, char *pattern)
2261 char *bufp = &buf[*index], *patternp = pattern;
2263 char *matchp = star_match[0];
2266 if (*patternp == NULLCHAR) {
2267 *index = leftover_start = bufp - buf;
2271 if (*bufp == NULLCHAR) return FALSE;
2272 if (*patternp == '*') {
2273 if (*bufp == *(patternp + 1)) {
2275 matchp = star_match[++star_count];
2279 } else if (*bufp == '\n' || *bufp == '\r') {
2281 if (*patternp == NULLCHAR)
2286 *matchp++ = *bufp++;
2290 if (*patternp != *bufp) return FALSE;
2297 SendToPlayer (char *data, int length)
2299 int error, outCount;
2300 outCount = OutputToProcess(NoProc, data, length, &error);
2301 if (outCount < length) {
2302 DisplayFatalError(_("Error writing to display"), error, 1);
2307 PackHolding (char packed[], char *holding)
2317 switch (runlength) {
2328 sprintf(q, "%d", runlength);
2340 /* Telnet protocol requests from the front end */
2342 TelnetRequest (unsigned char ddww, unsigned char option)
2344 unsigned char msg[3];
2345 int outCount, outError;
2347 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2349 if (appData.debugMode) {
2350 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2366 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2375 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2378 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2383 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2385 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2392 if (!appData.icsActive) return;
2393 TelnetRequest(TN_DO, TN_ECHO);
2399 if (!appData.icsActive) return;
2400 TelnetRequest(TN_DONT, TN_ECHO);
2404 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2406 /* put the holdings sent to us by the server on the board holdings area */
2407 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2411 if(gameInfo.holdingsWidth < 2) return;
2412 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2413 return; // prevent overwriting by pre-board holdings
2415 if( (int)lowestPiece >= BlackPawn ) {
2418 holdingsStartRow = BOARD_HEIGHT-1;
2421 holdingsColumn = BOARD_WIDTH-1;
2422 countsColumn = BOARD_WIDTH-2;
2423 holdingsStartRow = 0;
2427 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2428 board[i][holdingsColumn] = EmptySquare;
2429 board[i][countsColumn] = (ChessSquare) 0;
2431 while( (p=*holdings++) != NULLCHAR ) {
2432 piece = CharToPiece( ToUpper(p) );
2433 if(piece == EmptySquare) continue;
2434 /*j = (int) piece - (int) WhitePawn;*/
2435 j = PieceToNumber(piece);
2436 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2437 if(j < 0) continue; /* should not happen */
2438 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2439 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2440 board[holdingsStartRow+j*direction][countsColumn]++;
2446 VariantSwitch (Board board, VariantClass newVariant)
2448 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2449 static Board oldBoard;
2451 startedFromPositionFile = FALSE;
2452 if(gameInfo.variant == newVariant) return;
2454 /* [HGM] This routine is called each time an assignment is made to
2455 * gameInfo.variant during a game, to make sure the board sizes
2456 * are set to match the new variant. If that means adding or deleting
2457 * holdings, we shift the playing board accordingly
2458 * This kludge is needed because in ICS observe mode, we get boards
2459 * of an ongoing game without knowing the variant, and learn about the
2460 * latter only later. This can be because of the move list we requested,
2461 * in which case the game history is refilled from the beginning anyway,
2462 * but also when receiving holdings of a crazyhouse game. In the latter
2463 * case we want to add those holdings to the already received position.
2467 if (appData.debugMode) {
2468 fprintf(debugFP, "Switch board from %s to %s\n",
2469 VariantName(gameInfo.variant), VariantName(newVariant));
2470 setbuf(debugFP, NULL);
2472 shuffleOpenings = 0; /* [HGM] shuffle */
2473 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2477 newWidth = 9; newHeight = 9;
2478 gameInfo.holdingsSize = 7;
2479 case VariantBughouse:
2480 case VariantCrazyhouse:
2481 newHoldingsWidth = 2; break;
2485 newHoldingsWidth = 2;
2486 gameInfo.holdingsSize = 8;
2489 case VariantCapablanca:
2490 case VariantCapaRandom:
2493 newHoldingsWidth = gameInfo.holdingsSize = 0;
2496 if(newWidth != gameInfo.boardWidth ||
2497 newHeight != gameInfo.boardHeight ||
2498 newHoldingsWidth != gameInfo.holdingsWidth ) {
2500 /* shift position to new playing area, if needed */
2501 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2502 for(i=0; i<BOARD_HEIGHT; i++)
2503 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2504 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2506 for(i=0; i<newHeight; i++) {
2507 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2508 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2510 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2511 for(i=0; i<BOARD_HEIGHT; i++)
2512 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2513 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2516 board[HOLDINGS_SET] = 0;
2517 gameInfo.boardWidth = newWidth;
2518 gameInfo.boardHeight = newHeight;
2519 gameInfo.holdingsWidth = newHoldingsWidth;
2520 gameInfo.variant = newVariant;
2521 InitDrawingSizes(-2, 0);
2522 } else gameInfo.variant = newVariant;
2523 CopyBoard(oldBoard, board); // remember correctly formatted board
2524 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2525 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2528 static int loggedOn = FALSE;
2530 /*-- Game start info cache: --*/
2532 char gs_kind[MSG_SIZ];
2533 static char player1Name[128] = "";
2534 static char player2Name[128] = "";
2535 static char cont_seq[] = "\n\\ ";
2536 static int player1Rating = -1;
2537 static int player2Rating = -1;
2538 /*----------------------------*/
2540 ColorClass curColor = ColorNormal;
2541 int suppressKibitz = 0;
2544 Boolean soughtPending = FALSE;
2545 Boolean seekGraphUp;
2546 #define MAX_SEEK_ADS 200
2548 char *seekAdList[MAX_SEEK_ADS];
2549 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2550 float tcList[MAX_SEEK_ADS];
2551 char colorList[MAX_SEEK_ADS];
2552 int nrOfSeekAds = 0;
2553 int minRating = 1010, maxRating = 2800;
2554 int hMargin = 10, vMargin = 20, h, w;
2555 extern int squareSize, lineGap;
2560 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2561 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2562 if(r < minRating+100 && r >=0 ) r = minRating+100;
2563 if(r > maxRating) r = maxRating;
2564 if(tc < 1.f) tc = 1.f;
2565 if(tc > 95.f) tc = 95.f;
2566 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2567 y = ((double)r - minRating)/(maxRating - minRating)
2568 * (h-vMargin-squareSize/8-1) + vMargin;
2569 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2570 if(strstr(seekAdList[i], " u ")) color = 1;
2571 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2572 !strstr(seekAdList[i], "bullet") &&
2573 !strstr(seekAdList[i], "blitz") &&
2574 !strstr(seekAdList[i], "standard") ) color = 2;
2575 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2576 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2580 PlotSingleSeekAd (int i)
2586 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2588 char buf[MSG_SIZ], *ext = "";
2589 VariantClass v = StringToVariant(type);
2590 if(strstr(type, "wild")) {
2591 ext = type + 4; // append wild number
2592 if(v == VariantFischeRandom) type = "chess960"; else
2593 if(v == VariantLoadable) type = "setup"; else
2594 type = VariantName(v);
2596 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2597 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2598 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2599 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2600 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2601 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2602 seekNrList[nrOfSeekAds] = nr;
2603 zList[nrOfSeekAds] = 0;
2604 seekAdList[nrOfSeekAds++] = StrSave(buf);
2605 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2610 EraseSeekDot (int i)
2612 int x = xList[i], y = yList[i], d=squareSize/4, k;
2613 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2614 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2615 // now replot every dot that overlapped
2616 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2617 int xx = xList[k], yy = yList[k];
2618 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2619 DrawSeekDot(xx, yy, colorList[k]);
2624 RemoveSeekAd (int nr)
2627 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2629 if(seekAdList[i]) free(seekAdList[i]);
2630 seekAdList[i] = seekAdList[--nrOfSeekAds];
2631 seekNrList[i] = seekNrList[nrOfSeekAds];
2632 ratingList[i] = ratingList[nrOfSeekAds];
2633 colorList[i] = colorList[nrOfSeekAds];
2634 tcList[i] = tcList[nrOfSeekAds];
2635 xList[i] = xList[nrOfSeekAds];
2636 yList[i] = yList[nrOfSeekAds];
2637 zList[i] = zList[nrOfSeekAds];
2638 seekAdList[nrOfSeekAds] = NULL;
2644 MatchSoughtLine (char *line)
2646 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2647 int nr, base, inc, u=0; char dummy;
2649 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2650 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2652 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2653 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2654 // match: compact and save the line
2655 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2665 if(!seekGraphUp) return FALSE;
2666 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2667 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2669 DrawSeekBackground(0, 0, w, h);
2670 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2671 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2672 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2673 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2675 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2678 snprintf(buf, MSG_SIZ, "%d", i);
2679 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2682 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2683 for(i=1; i<100; i+=(i<10?1:5)) {
2684 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2685 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2686 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2688 snprintf(buf, MSG_SIZ, "%d", i);
2689 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2692 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2697 SeekGraphClick (ClickType click, int x, int y, int moving)
2699 static int lastDown = 0, displayed = 0, lastSecond;
2700 if(y < 0) return FALSE;
2701 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2702 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2703 if(!seekGraphUp) return FALSE;
2704 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2705 DrawPosition(TRUE, NULL);
2708 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2709 if(click == Release || moving) return FALSE;
2711 soughtPending = TRUE;
2712 SendToICS(ics_prefix);
2713 SendToICS("sought\n"); // should this be "sought all"?
2714 } else { // issue challenge based on clicked ad
2715 int dist = 10000; int i, closest = 0, second = 0;
2716 for(i=0; i<nrOfSeekAds; i++) {
2717 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2718 if(d < dist) { dist = d; closest = i; }
2719 second += (d - zList[i] < 120); // count in-range ads
2720 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2724 second = (second > 1);
2725 if(displayed != closest || second != lastSecond) {
2726 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2727 lastSecond = second; displayed = closest;
2729 if(click == Press) {
2730 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2733 } // on press 'hit', only show info
2734 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2735 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2736 SendToICS(ics_prefix);
2738 return TRUE; // let incoming board of started game pop down the graph
2739 } else if(click == Release) { // release 'miss' is ignored
2740 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2741 if(moving == 2) { // right up-click
2742 nrOfSeekAds = 0; // refresh graph
2743 soughtPending = TRUE;
2744 SendToICS(ics_prefix);
2745 SendToICS("sought\n"); // should this be "sought all"?
2748 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2749 // press miss or release hit 'pop down' seek graph
2750 seekGraphUp = FALSE;
2751 DrawPosition(TRUE, NULL);
2757 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2759 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2760 #define STARTED_NONE 0
2761 #define STARTED_MOVES 1
2762 #define STARTED_BOARD 2
2763 #define STARTED_OBSERVE 3
2764 #define STARTED_HOLDINGS 4
2765 #define STARTED_CHATTER 5
2766 #define STARTED_COMMENT 6
2767 #define STARTED_MOVES_NOHIDE 7
2769 static int started = STARTED_NONE;
2770 static char parse[20000];
2771 static int parse_pos = 0;
2772 static char buf[BUF_SIZE + 1];
2773 static int firstTime = TRUE, intfSet = FALSE;
2774 static ColorClass prevColor = ColorNormal;
2775 static int savingComment = FALSE;
2776 static int cmatch = 0; // continuation sequence match
2783 int backup; /* [DM] For zippy color lines */
2785 char talker[MSG_SIZ]; // [HGM] chat
2788 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2790 if (appData.debugMode) {
2792 fprintf(debugFP, "<ICS: ");
2793 show_bytes(debugFP, data, count);
2794 fprintf(debugFP, "\n");
2798 if (appData.debugMode) { int f = forwardMostMove;
2799 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2800 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2801 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2804 /* If last read ended with a partial line that we couldn't parse,
2805 prepend it to the new read and try again. */
2806 if (leftover_len > 0) {
2807 for (i=0; i<leftover_len; i++)
2808 buf[i] = buf[leftover_start + i];
2811 /* copy new characters into the buffer */
2812 bp = buf + leftover_len;
2813 buf_len=leftover_len;
2814 for (i=0; i<count; i++)
2817 if (data[i] == '\r')
2820 // join lines split by ICS?
2821 if (!appData.noJoin)
2824 Joining just consists of finding matches against the
2825 continuation sequence, and discarding that sequence
2826 if found instead of copying it. So, until a match
2827 fails, there's nothing to do since it might be the
2828 complete sequence, and thus, something we don't want
2831 if (data[i] == cont_seq[cmatch])
2834 if (cmatch == strlen(cont_seq))
2836 cmatch = 0; // complete match. just reset the counter
2839 it's possible for the ICS to not include the space
2840 at the end of the last word, making our [correct]
2841 join operation fuse two separate words. the server
2842 does this when the space occurs at the width setting.
2844 if (!buf_len || buf[buf_len-1] != ' ')
2855 match failed, so we have to copy what matched before
2856 falling through and copying this character. In reality,
2857 this will only ever be just the newline character, but
2858 it doesn't hurt to be precise.
2860 strncpy(bp, cont_seq, cmatch);
2872 buf[buf_len] = NULLCHAR;
2873 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2878 while (i < buf_len) {
2879 /* Deal with part of the TELNET option negotiation
2880 protocol. We refuse to do anything beyond the
2881 defaults, except that we allow the WILL ECHO option,
2882 which ICS uses to turn off password echoing when we are
2883 directly connected to it. We reject this option
2884 if localLineEditing mode is on (always on in xboard)
2885 and we are talking to port 23, which might be a real
2886 telnet server that will try to keep WILL ECHO on permanently.
2888 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2889 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2890 unsigned char option;
2892 switch ((unsigned char) buf[++i]) {
2894 if (appData.debugMode)
2895 fprintf(debugFP, "\n<WILL ");
2896 switch (option = (unsigned char) buf[++i]) {
2898 if (appData.debugMode)
2899 fprintf(debugFP, "ECHO ");
2900 /* Reply only if this is a change, according
2901 to the protocol rules. */
2902 if (remoteEchoOption) break;
2903 if (appData.localLineEditing &&
2904 atoi(appData.icsPort) == TN_PORT) {
2905 TelnetRequest(TN_DONT, TN_ECHO);
2908 TelnetRequest(TN_DO, TN_ECHO);
2909 remoteEchoOption = TRUE;
2913 if (appData.debugMode)
2914 fprintf(debugFP, "%d ", option);
2915 /* Whatever this is, we don't want it. */
2916 TelnetRequest(TN_DONT, option);
2921 if (appData.debugMode)
2922 fprintf(debugFP, "\n<WONT ");
2923 switch (option = (unsigned char) buf[++i]) {
2925 if (appData.debugMode)
2926 fprintf(debugFP, "ECHO ");
2927 /* Reply only if this is a change, according
2928 to the protocol rules. */
2929 if (!remoteEchoOption) break;
2931 TelnetRequest(TN_DONT, TN_ECHO);
2932 remoteEchoOption = FALSE;
2935 if (appData.debugMode)
2936 fprintf(debugFP, "%d ", (unsigned char) option);
2937 /* Whatever this is, it must already be turned
2938 off, because we never agree to turn on
2939 anything non-default, so according to the
2940 protocol rules, we don't reply. */
2945 if (appData.debugMode)
2946 fprintf(debugFP, "\n<DO ");
2947 switch (option = (unsigned char) buf[++i]) {
2949 /* Whatever this is, we refuse to do it. */
2950 if (appData.debugMode)
2951 fprintf(debugFP, "%d ", option);
2952 TelnetRequest(TN_WONT, option);
2957 if (appData.debugMode)
2958 fprintf(debugFP, "\n<DONT ");
2959 switch (option = (unsigned char) buf[++i]) {
2961 if (appData.debugMode)
2962 fprintf(debugFP, "%d ", option);
2963 /* Whatever this is, we are already not doing
2964 it, because we never agree to do anything
2965 non-default, so according to the protocol
2966 rules, we don't reply. */
2971 if (appData.debugMode)
2972 fprintf(debugFP, "\n<IAC ");
2973 /* Doubled IAC; pass it through */
2977 if (appData.debugMode)
2978 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2979 /* Drop all other telnet commands on the floor */
2982 if (oldi > next_out)
2983 SendToPlayer(&buf[next_out], oldi - next_out);
2989 /* OK, this at least will *usually* work */
2990 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2994 if (loggedOn && !intfSet) {
2995 if (ics_type == ICS_ICC) {
2996 snprintf(str, MSG_SIZ,
2997 "/set-quietly interface %s\n/set-quietly style 12\n",
2999 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3000 strcat(str, "/set-2 51 1\n/set seek 1\n");
3001 } else if (ics_type == ICS_CHESSNET) {
3002 snprintf(str, MSG_SIZ, "/style 12\n");
3004 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3005 strcat(str, programVersion);
3006 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3007 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3008 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3010 strcat(str, "$iset nohighlight 1\n");
3012 strcat(str, "$iset lock 1\n$style 12\n");
3015 NotifyFrontendLogin();
3019 if (started == STARTED_COMMENT) {
3020 /* Accumulate characters in comment */
3021 parse[parse_pos++] = buf[i];
3022 if (buf[i] == '\n') {
3023 parse[parse_pos] = NULLCHAR;
3024 if(chattingPartner>=0) {
3026 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3027 OutputChatMessage(chattingPartner, mess);
3028 chattingPartner = -1;
3029 next_out = i+1; // [HGM] suppress printing in ICS window
3031 if(!suppressKibitz) // [HGM] kibitz
3032 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3033 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3034 int nrDigit = 0, nrAlph = 0, j;
3035 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3036 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3037 parse[parse_pos] = NULLCHAR;
3038 // try to be smart: if it does not look like search info, it should go to
3039 // ICS interaction window after all, not to engine-output window.
3040 for(j=0; j<parse_pos; j++) { // count letters and digits
3041 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3042 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3043 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3045 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3046 int depth=0; float score;
3047 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3048 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3049 pvInfoList[forwardMostMove-1].depth = depth;
3050 pvInfoList[forwardMostMove-1].score = 100*score;
3052 OutputKibitz(suppressKibitz, parse);
3055 if(gameMode == IcsObserving) // restore original ICS messages
3056 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3057 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3059 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3060 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3061 SendToPlayer(tmp, strlen(tmp));
3063 next_out = i+1; // [HGM] suppress printing in ICS window
3065 started = STARTED_NONE;
3067 /* Don't match patterns against characters in comment */
3072 if (started == STARTED_CHATTER) {
3073 if (buf[i] != '\n') {
3074 /* Don't match patterns against characters in chatter */
3078 started = STARTED_NONE;
3079 if(suppressKibitz) next_out = i+1;
3082 /* Kludge to deal with rcmd protocol */
3083 if (firstTime && looking_at(buf, &i, "\001*")) {
3084 DisplayFatalError(&buf[1], 0, 1);
3090 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3093 if (appData.debugMode)
3094 fprintf(debugFP, "ics_type %d\n", ics_type);
3097 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3098 ics_type = ICS_FICS;
3100 if (appData.debugMode)
3101 fprintf(debugFP, "ics_type %d\n", ics_type);
3104 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3105 ics_type = ICS_CHESSNET;
3107 if (appData.debugMode)
3108 fprintf(debugFP, "ics_type %d\n", ics_type);
3113 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3114 looking_at(buf, &i, "Logging you in as \"*\"") ||
3115 looking_at(buf, &i, "will be \"*\""))) {
3116 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3120 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3122 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3123 DisplayIcsInteractionTitle(buf);
3124 have_set_title = TRUE;
3127 /* skip finger notes */
3128 if (started == STARTED_NONE &&
3129 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3130 (buf[i] == '1' && buf[i+1] == '0')) &&
3131 buf[i+2] == ':' && buf[i+3] == ' ') {
3132 started = STARTED_CHATTER;
3138 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3139 if(appData.seekGraph) {
3140 if(soughtPending && MatchSoughtLine(buf+i)) {
3141 i = strstr(buf+i, "rated") - buf;
3142 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3143 next_out = leftover_start = i;
3144 started = STARTED_CHATTER;
3145 suppressKibitz = TRUE;
3148 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3149 && looking_at(buf, &i, "* ads displayed")) {
3150 soughtPending = FALSE;
3155 if(appData.autoRefresh) {
3156 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3157 int s = (ics_type == ICS_ICC); // ICC format differs
3159 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3160 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3161 looking_at(buf, &i, "*% "); // eat prompt
3162 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3163 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3164 next_out = i; // suppress
3167 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3168 char *p = star_match[0];
3170 if(seekGraphUp) RemoveSeekAd(atoi(p));
3171 while(*p && *p++ != ' '); // next
3173 looking_at(buf, &i, "*% "); // eat prompt
3174 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3181 /* skip formula vars */
3182 if (started == STARTED_NONE &&
3183 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3184 started = STARTED_CHATTER;
3189 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3190 if (appData.autoKibitz && started == STARTED_NONE &&
3191 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3192 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3193 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3194 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3195 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3196 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3197 suppressKibitz = TRUE;
3198 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3200 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3201 && (gameMode == IcsPlayingWhite)) ||
3202 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3203 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3204 started = STARTED_CHATTER; // own kibitz we simply discard
3206 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3207 parse_pos = 0; parse[0] = NULLCHAR;
3208 savingComment = TRUE;
3209 suppressKibitz = gameMode != IcsObserving ? 2 :
3210 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3214 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3215 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3216 && atoi(star_match[0])) {
3217 // suppress the acknowledgements of our own autoKibitz
3219 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3221 SendToPlayer(star_match[0], strlen(star_match[0]));
3222 if(looking_at(buf, &i, "*% ")) // eat prompt
3223 suppressKibitz = FALSE;
3227 } // [HGM] kibitz: end of patch
3229 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3231 // [HGM] chat: intercept tells by users for which we have an open chat window
3233 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3234 looking_at(buf, &i, "* whispers:") ||
3235 looking_at(buf, &i, "* kibitzes:") ||
3236 looking_at(buf, &i, "* shouts:") ||
3237 looking_at(buf, &i, "* c-shouts:") ||
3238 looking_at(buf, &i, "--> * ") ||
3239 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3240 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3241 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3242 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3244 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3245 chattingPartner = -1;
3247 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3248 for(p=0; p<MAX_CHAT; p++) {
3249 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3250 talker[0] = '['; strcat(talker, "] ");
3251 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3252 chattingPartner = p; break;
3255 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3256 for(p=0; p<MAX_CHAT; p++) {
3257 if(!strcmp("kibitzes", chatPartner[p])) {
3258 talker[0] = '['; strcat(talker, "] ");
3259 chattingPartner = p; break;
3262 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3263 for(p=0; p<MAX_CHAT; p++) {
3264 if(!strcmp("whispers", chatPartner[p])) {
3265 talker[0] = '['; strcat(talker, "] ");
3266 chattingPartner = p; break;
3269 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3270 if(buf[i-8] == '-' && buf[i-3] == 't')
3271 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3272 if(!strcmp("c-shouts", chatPartner[p])) {
3273 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3274 chattingPartner = p; break;
3277 if(chattingPartner < 0)
3278 for(p=0; p<MAX_CHAT; p++) {
3279 if(!strcmp("shouts", chatPartner[p])) {
3280 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3281 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3282 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3283 chattingPartner = p; break;
3287 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3288 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3289 talker[0] = 0; Colorize(ColorTell, FALSE);
3290 chattingPartner = p; break;
3292 if(chattingPartner<0) i = oldi; else {
3293 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3294 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3295 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3296 started = STARTED_COMMENT;
3297 parse_pos = 0; parse[0] = NULLCHAR;
3298 savingComment = 3 + chattingPartner; // counts as TRUE
3299 suppressKibitz = TRUE;
3302 } // [HGM] chat: end of patch
3305 if (appData.zippyTalk || appData.zippyPlay) {
3306 /* [DM] Backup address for color zippy lines */
3308 if (loggedOn == TRUE)
3309 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3310 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3312 } // [DM] 'else { ' deleted
3314 /* Regular tells and says */
3315 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3316 looking_at(buf, &i, "* (your partner) tells you: ") ||
3317 looking_at(buf, &i, "* says: ") ||
3318 /* Don't color "message" or "messages" output */
3319 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3320 looking_at(buf, &i, "*. * at *:*: ") ||
3321 looking_at(buf, &i, "--* (*:*): ") ||
3322 /* Message notifications (same color as tells) */
3323 looking_at(buf, &i, "* has left a message ") ||
3324 looking_at(buf, &i, "* just sent you a message:\n") ||
3325 /* Whispers and kibitzes */
3326 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3327 looking_at(buf, &i, "* kibitzes: ") ||
3329 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3331 if (tkind == 1 && strchr(star_match[0], ':')) {
3332 /* Avoid "tells you:" spoofs in channels */
3335 if (star_match[0][0] == NULLCHAR ||
3336 strchr(star_match[0], ' ') ||
3337 (tkind == 3 && strchr(star_match[1], ' '))) {
3338 /* Reject bogus matches */
3341 if (appData.colorize) {
3342 if (oldi > next_out) {
3343 SendToPlayer(&buf[next_out], oldi - next_out);
3348 Colorize(ColorTell, FALSE);
3349 curColor = ColorTell;
3352 Colorize(ColorKibitz, FALSE);
3353 curColor = ColorKibitz;
3356 p = strrchr(star_match[1], '(');
3363 Colorize(ColorChannel1, FALSE);
3364 curColor = ColorChannel1;
3366 Colorize(ColorChannel, FALSE);
3367 curColor = ColorChannel;
3371 curColor = ColorNormal;
3375 if (started == STARTED_NONE && appData.autoComment &&
3376 (gameMode == IcsObserving ||
3377 gameMode == IcsPlayingWhite ||
3378 gameMode == IcsPlayingBlack)) {
3379 parse_pos = i - oldi;
3380 memcpy(parse, &buf[oldi], parse_pos);
3381 parse[parse_pos] = NULLCHAR;
3382 started = STARTED_COMMENT;
3383 savingComment = TRUE;
3385 started = STARTED_CHATTER;
3386 savingComment = FALSE;
3393 if (looking_at(buf, &i, "* s-shouts: ") ||
3394 looking_at(buf, &i, "* c-shouts: ")) {
3395 if (appData.colorize) {
3396 if (oldi > next_out) {
3397 SendToPlayer(&buf[next_out], oldi - next_out);
3400 Colorize(ColorSShout, FALSE);
3401 curColor = ColorSShout;
3404 started = STARTED_CHATTER;
3408 if (looking_at(buf, &i, "--->")) {
3413 if (looking_at(buf, &i, "* shouts: ") ||
3414 looking_at(buf, &i, "--> ")) {
3415 if (appData.colorize) {
3416 if (oldi > next_out) {
3417 SendToPlayer(&buf[next_out], oldi - next_out);
3420 Colorize(ColorShout, FALSE);
3421 curColor = ColorShout;
3424 started = STARTED_CHATTER;
3428 if (looking_at( buf, &i, "Challenge:")) {
3429 if (appData.colorize) {
3430 if (oldi > next_out) {
3431 SendToPlayer(&buf[next_out], oldi - next_out);
3434 Colorize(ColorChallenge, FALSE);
3435 curColor = ColorChallenge;
3441 if (looking_at(buf, &i, "* offers you") ||
3442 looking_at(buf, &i, "* offers to be") ||
3443 looking_at(buf, &i, "* would like to") ||
3444 looking_at(buf, &i, "* requests to") ||
3445 looking_at(buf, &i, "Your opponent offers") ||
3446 looking_at(buf, &i, "Your opponent requests")) {
3448 if (appData.colorize) {
3449 if (oldi > next_out) {
3450 SendToPlayer(&buf[next_out], oldi - next_out);
3453 Colorize(ColorRequest, FALSE);
3454 curColor = ColorRequest;
3459 if (looking_at(buf, &i, "* (*) seeking")) {
3460 if (appData.colorize) {
3461 if (oldi > next_out) {
3462 SendToPlayer(&buf[next_out], oldi - next_out);
3465 Colorize(ColorSeek, FALSE);
3466 curColor = ColorSeek;
3471 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3473 if (looking_at(buf, &i, "\\ ")) {
3474 if (prevColor != ColorNormal) {
3475 if (oldi > next_out) {
3476 SendToPlayer(&buf[next_out], oldi - next_out);
3479 Colorize(prevColor, TRUE);
3480 curColor = prevColor;
3482 if (savingComment) {
3483 parse_pos = i - oldi;
3484 memcpy(parse, &buf[oldi], parse_pos);
3485 parse[parse_pos] = NULLCHAR;
3486 started = STARTED_COMMENT;
3487 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3488 chattingPartner = savingComment - 3; // kludge to remember the box
3490 started = STARTED_CHATTER;
3495 if (looking_at(buf, &i, "Black Strength :") ||
3496 looking_at(buf, &i, "<<< style 10 board >>>") ||
3497 looking_at(buf, &i, "<10>") ||
3498 looking_at(buf, &i, "#@#")) {
3499 /* Wrong board style */
3501 SendToICS(ics_prefix);
3502 SendToICS("set style 12\n");
3503 SendToICS(ics_prefix);
3504 SendToICS("refresh\n");
3508 if (looking_at(buf, &i, "login:")) {
3509 if (!have_sent_ICS_logon) {
3511 have_sent_ICS_logon = 1;
3512 else // no init script was found
3513 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3514 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3515 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3520 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3521 (looking_at(buf, &i, "\n<12> ") ||
3522 looking_at(buf, &i, "<12> "))) {
3524 if (oldi > next_out) {
3525 SendToPlayer(&buf[next_out], oldi - next_out);
3528 started = STARTED_BOARD;
3533 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3534 looking_at(buf, &i, "<b1> ")) {
3535 if (oldi > next_out) {
3536 SendToPlayer(&buf[next_out], oldi - next_out);
3539 started = STARTED_HOLDINGS;
3544 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3546 /* Header for a move list -- first line */
3548 switch (ics_getting_history) {
3552 case BeginningOfGame:
3553 /* User typed "moves" or "oldmoves" while we
3554 were idle. Pretend we asked for these
3555 moves and soak them up so user can step
3556 through them and/or save them.
3559 gameMode = IcsObserving;
3562 ics_getting_history = H_GOT_UNREQ_HEADER;
3564 case EditGame: /*?*/
3565 case EditPosition: /*?*/
3566 /* Should above feature work in these modes too? */
3567 /* For now it doesn't */
3568 ics_getting_history = H_GOT_UNWANTED_HEADER;
3571 ics_getting_history = H_GOT_UNWANTED_HEADER;
3576 /* Is this the right one? */
3577 if (gameInfo.white && gameInfo.black &&
3578 strcmp(gameInfo.white, star_match[0]) == 0 &&
3579 strcmp(gameInfo.black, star_match[2]) == 0) {
3581 ics_getting_history = H_GOT_REQ_HEADER;
3584 case H_GOT_REQ_HEADER:
3585 case H_GOT_UNREQ_HEADER:
3586 case H_GOT_UNWANTED_HEADER:
3587 case H_GETTING_MOVES:
3588 /* Should not happen */
3589 DisplayError(_("Error gathering move list: two headers"), 0);
3590 ics_getting_history = H_FALSE;
3594 /* Save player ratings into gameInfo if needed */
3595 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3596 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3597 (gameInfo.whiteRating == -1 ||
3598 gameInfo.blackRating == -1)) {
3600 gameInfo.whiteRating = string_to_rating(star_match[1]);
3601 gameInfo.blackRating = string_to_rating(star_match[3]);
3602 if (appData.debugMode)
3603 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3604 gameInfo.whiteRating, gameInfo.blackRating);
3609 if (looking_at(buf, &i,
3610 "* * match, initial time: * minute*, increment: * second")) {
3611 /* Header for a move list -- second line */
3612 /* Initial board will follow if this is a wild game */
3613 if (gameInfo.event != NULL) free(gameInfo.event);
3614 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3615 gameInfo.event = StrSave(str);
3616 /* [HGM] we switched variant. Translate boards if needed. */
3617 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3621 if (looking_at(buf, &i, "Move ")) {
3622 /* Beginning of a move list */
3623 switch (ics_getting_history) {
3625 /* Normally should not happen */
3626 /* Maybe user hit reset while we were parsing */
3629 /* Happens if we are ignoring a move list that is not
3630 * the one we just requested. Common if the user
3631 * tries to observe two games without turning off
3634 case H_GETTING_MOVES:
3635 /* Should not happen */
3636 DisplayError(_("Error gathering move list: nested"), 0);
3637 ics_getting_history = H_FALSE;
3639 case H_GOT_REQ_HEADER:
3640 ics_getting_history = H_GETTING_MOVES;
3641 started = STARTED_MOVES;
3643 if (oldi > next_out) {
3644 SendToPlayer(&buf[next_out], oldi - next_out);
3647 case H_GOT_UNREQ_HEADER:
3648 ics_getting_history = H_GETTING_MOVES;
3649 started = STARTED_MOVES_NOHIDE;
3652 case H_GOT_UNWANTED_HEADER:
3653 ics_getting_history = H_FALSE;
3659 if (looking_at(buf, &i, "% ") ||
3660 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3661 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3662 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3663 soughtPending = FALSE;
3667 if(suppressKibitz) next_out = i;
3668 savingComment = FALSE;
3672 case STARTED_MOVES_NOHIDE:
3673 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3674 parse[parse_pos + i - oldi] = NULLCHAR;
3675 ParseGameHistory(parse);
3677 if (appData.zippyPlay && first.initDone) {
3678 FeedMovesToProgram(&first, forwardMostMove);
3679 if (gameMode == IcsPlayingWhite) {
3680 if (WhiteOnMove(forwardMostMove)) {
3681 if (first.sendTime) {
3682 if (first.useColors) {
3683 SendToProgram("black\n", &first);
3685 SendTimeRemaining(&first, TRUE);
3687 if (first.useColors) {
3688 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3690 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3691 first.maybeThinking = TRUE;
3693 if (first.usePlayother) {
3694 if (first.sendTime) {
3695 SendTimeRemaining(&first, TRUE);
3697 SendToProgram("playother\n", &first);
3703 } else if (gameMode == IcsPlayingBlack) {
3704 if (!WhiteOnMove(forwardMostMove)) {
3705 if (first.sendTime) {
3706 if (first.useColors) {
3707 SendToProgram("white\n", &first);
3709 SendTimeRemaining(&first, FALSE);
3711 if (first.useColors) {
3712 SendToProgram("black\n", &first);
3714 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3715 first.maybeThinking = TRUE;
3717 if (first.usePlayother) {
3718 if (first.sendTime) {
3719 SendTimeRemaining(&first, FALSE);
3721 SendToProgram("playother\n", &first);
3730 if (gameMode == IcsObserving && ics_gamenum == -1) {
3731 /* Moves came from oldmoves or moves command
3732 while we weren't doing anything else.
3734 currentMove = forwardMostMove;
3735 ClearHighlights();/*!!could figure this out*/
3736 flipView = appData.flipView;
3737 DrawPosition(TRUE, boards[currentMove]);
3738 DisplayBothClocks();
3739 snprintf(str, MSG_SIZ, "%s %s %s",
3740 gameInfo.white, _("vs."), gameInfo.black);
3744 /* Moves were history of an active game */
3745 if (gameInfo.resultDetails != NULL) {
3746 free(gameInfo.resultDetails);
3747 gameInfo.resultDetails = NULL;
3750 HistorySet(parseList, backwardMostMove,
3751 forwardMostMove, currentMove-1);
3752 DisplayMove(currentMove - 1);
3753 if (started == STARTED_MOVES) next_out = i;
3754 started = STARTED_NONE;
3755 ics_getting_history = H_FALSE;
3758 case STARTED_OBSERVE:
3759 started = STARTED_NONE;
3760 SendToICS(ics_prefix);
3761 SendToICS("refresh\n");
3767 if(bookHit) { // [HGM] book: simulate book reply
3768 static char bookMove[MSG_SIZ]; // a bit generous?
3770 programStats.nodes = programStats.depth = programStats.time =
3771 programStats.score = programStats.got_only_move = 0;
3772 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3774 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3775 strcat(bookMove, bookHit);
3776 HandleMachineMove(bookMove, &first);
3781 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3782 started == STARTED_HOLDINGS ||
3783 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3784 /* Accumulate characters in move list or board */
3785 parse[parse_pos++] = buf[i];
3788 /* Start of game messages. Mostly we detect start of game
3789 when the first board image arrives. On some versions
3790 of the ICS, though, we need to do a "refresh" after starting
3791 to observe in order to get the current board right away. */
3792 if (looking_at(buf, &i, "Adding game * to observation list")) {
3793 started = STARTED_OBSERVE;
3797 /* Handle auto-observe */
3798 if (appData.autoObserve &&
3799 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3800 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3802 /* Choose the player that was highlighted, if any. */
3803 if (star_match[0][0] == '\033' ||
3804 star_match[1][0] != '\033') {
3805 player = star_match[0];
3807 player = star_match[2];
3809 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3810 ics_prefix, StripHighlightAndTitle(player));
3813 /* Save ratings from notify string */
3814 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3815 player1Rating = string_to_rating(star_match[1]);
3816 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3817 player2Rating = string_to_rating(star_match[3]);
3819 if (appData.debugMode)
3821 "Ratings from 'Game notification:' %s %d, %s %d\n",
3822 player1Name, player1Rating,
3823 player2Name, player2Rating);
3828 /* Deal with automatic examine mode after a game,
3829 and with IcsObserving -> IcsExamining transition */
3830 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3831 looking_at(buf, &i, "has made you an examiner of game *")) {
3833 int gamenum = atoi(star_match[0]);
3834 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3835 gamenum == ics_gamenum) {
3836 /* We were already playing or observing this game;
3837 no need to refetch history */
3838 gameMode = IcsExamining;
3840 pauseExamForwardMostMove = forwardMostMove;
3841 } else if (currentMove < forwardMostMove) {
3842 ForwardInner(forwardMostMove);
3845 /* I don't think this case really can happen */
3846 SendToICS(ics_prefix);
3847 SendToICS("refresh\n");
3852 /* Error messages */
3853 // if (ics_user_moved) {
3854 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3855 if (looking_at(buf, &i, "Illegal move") ||
3856 looking_at(buf, &i, "Not a legal move") ||
3857 looking_at(buf, &i, "Your king is in check") ||
3858 looking_at(buf, &i, "It isn't your turn") ||
3859 looking_at(buf, &i, "It is not your move")) {
3861 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3862 currentMove = forwardMostMove-1;
3863 DisplayMove(currentMove - 1); /* before DMError */
3864 DrawPosition(FALSE, boards[currentMove]);
3865 SwitchClocks(forwardMostMove-1); // [HGM] race
3866 DisplayBothClocks();
3868 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3874 if (looking_at(buf, &i, "still have time") ||
3875 looking_at(buf, &i, "not out of time") ||
3876 looking_at(buf, &i, "either player is out of time") ||
3877 looking_at(buf, &i, "has timeseal; checking")) {
3878 /* We must have called his flag a little too soon */
3879 whiteFlag = blackFlag = FALSE;
3883 if (looking_at(buf, &i, "added * seconds to") ||
3884 looking_at(buf, &i, "seconds were added to")) {
3885 /* Update the clocks */
3886 SendToICS(ics_prefix);
3887 SendToICS("refresh\n");
3891 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3892 ics_clock_paused = TRUE;
3897 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3898 ics_clock_paused = FALSE;
3903 /* Grab player ratings from the Creating: message.
3904 Note we have to check for the special case when
3905 the ICS inserts things like [white] or [black]. */
3906 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3907 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3909 0 player 1 name (not necessarily white)
3911 2 empty, white, or black (IGNORED)
3912 3 player 2 name (not necessarily black)
3915 The names/ratings are sorted out when the game
3916 actually starts (below).
3918 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3919 player1Rating = string_to_rating(star_match[1]);
3920 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3921 player2Rating = string_to_rating(star_match[4]);
3923 if (appData.debugMode)
3925 "Ratings from 'Creating:' %s %d, %s %d\n",
3926 player1Name, player1Rating,
3927 player2Name, player2Rating);
3932 /* Improved generic start/end-of-game messages */
3933 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3934 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3935 /* If tkind == 0: */
3936 /* star_match[0] is the game number */
3937 /* [1] is the white player's name */
3938 /* [2] is the black player's name */
3939 /* For end-of-game: */
3940 /* [3] is the reason for the game end */
3941 /* [4] is a PGN end game-token, preceded by " " */
3942 /* For start-of-game: */
3943 /* [3] begins with "Creating" or "Continuing" */
3944 /* [4] is " *" or empty (don't care). */
3945 int gamenum = atoi(star_match[0]);
3946 char *whitename, *blackname, *why, *endtoken;
3947 ChessMove endtype = EndOfFile;
3950 whitename = star_match[1];
3951 blackname = star_match[2];
3952 why = star_match[3];
3953 endtoken = star_match[4];
3955 whitename = star_match[1];
3956 blackname = star_match[3];
3957 why = star_match[5];
3958 endtoken = star_match[6];
3961 /* Game start messages */
3962 if (strncmp(why, "Creating ", 9) == 0 ||
3963 strncmp(why, "Continuing ", 11) == 0) {
3964 gs_gamenum = gamenum;
3965 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3966 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3967 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3969 if (appData.zippyPlay) {
3970 ZippyGameStart(whitename, blackname);
3973 partnerBoardValid = FALSE; // [HGM] bughouse
3977 /* Game end messages */
3978 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3979 ics_gamenum != gamenum) {
3982 while (endtoken[0] == ' ') endtoken++;
3983 switch (endtoken[0]) {
3986 endtype = GameUnfinished;
3989 endtype = BlackWins;
3992 if (endtoken[1] == '/')
3993 endtype = GameIsDrawn;
3995 endtype = WhiteWins;
3998 GameEnds(endtype, why, GE_ICS);
4000 if (appData.zippyPlay && first.initDone) {
4001 ZippyGameEnd(endtype, why);
4002 if (first.pr == NoProc) {
4003 /* Start the next process early so that we'll
4004 be ready for the next challenge */
4005 StartChessProgram(&first);
4007 /* Send "new" early, in case this command takes
4008 a long time to finish, so that we'll be ready
4009 for the next challenge. */
4010 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4014 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4018 if (looking_at(buf, &i, "Removing game * from observation") ||
4019 looking_at(buf, &i, "no longer observing game *") ||
4020 looking_at(buf, &i, "Game * (*) has no examiners")) {
4021 if (gameMode == IcsObserving &&
4022 atoi(star_match[0]) == ics_gamenum)
4024 /* icsEngineAnalyze */
4025 if (appData.icsEngineAnalyze) {
4032 ics_user_moved = FALSE;
4037 if (looking_at(buf, &i, "no longer examining game *")) {
4038 if (gameMode == IcsExamining &&
4039 atoi(star_match[0]) == ics_gamenum)
4043 ics_user_moved = FALSE;
4048 /* Advance leftover_start past any newlines we find,
4049 so only partial lines can get reparsed */
4050 if (looking_at(buf, &i, "\n")) {
4051 prevColor = curColor;
4052 if (curColor != ColorNormal) {
4053 if (oldi > next_out) {
4054 SendToPlayer(&buf[next_out], oldi - next_out);
4057 Colorize(ColorNormal, FALSE);
4058 curColor = ColorNormal;
4060 if (started == STARTED_BOARD) {
4061 started = STARTED_NONE;
4062 parse[parse_pos] = NULLCHAR;
4063 ParseBoard12(parse);
4066 /* Send premove here */
4067 if (appData.premove) {
4069 if (currentMove == 0 &&
4070 gameMode == IcsPlayingWhite &&
4071 appData.premoveWhite) {
4072 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4073 if (appData.debugMode)
4074 fprintf(debugFP, "Sending premove:\n");
4076 } else if (currentMove == 1 &&
4077 gameMode == IcsPlayingBlack &&
4078 appData.premoveBlack) {
4079 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4080 if (appData.debugMode)
4081 fprintf(debugFP, "Sending premove:\n");
4083 } else if (gotPremove) {
4085 ClearPremoveHighlights();
4086 if (appData.debugMode)
4087 fprintf(debugFP, "Sending premove:\n");
4088 UserMoveEvent(premoveFromX, premoveFromY,
4089 premoveToX, premoveToY,
4094 /* Usually suppress following prompt */
4095 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4096 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4097 if (looking_at(buf, &i, "*% ")) {
4098 savingComment = FALSE;
4103 } else if (started == STARTED_HOLDINGS) {
4105 char new_piece[MSG_SIZ];
4106 started = STARTED_NONE;
4107 parse[parse_pos] = NULLCHAR;
4108 if (appData.debugMode)
4109 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4110 parse, currentMove);
4111 if (sscanf(parse, " game %d", &gamenum) == 1) {
4112 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4113 if (gameInfo.variant == VariantNormal) {
4114 /* [HGM] We seem to switch variant during a game!
4115 * Presumably no holdings were displayed, so we have
4116 * to move the position two files to the right to
4117 * create room for them!
4119 VariantClass newVariant;
4120 switch(gameInfo.boardWidth) { // base guess on board width
4121 case 9: newVariant = VariantShogi; break;
4122 case 10: newVariant = VariantGreat; break;
4123 default: newVariant = VariantCrazyhouse; break;
4125 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4126 /* Get a move list just to see the header, which
4127 will tell us whether this is really bug or zh */
4128 if (ics_getting_history == H_FALSE) {
4129 ics_getting_history = H_REQUESTED;
4130 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4134 new_piece[0] = NULLCHAR;
4135 sscanf(parse, "game %d white [%s black [%s <- %s",
4136 &gamenum, white_holding, black_holding,
4138 white_holding[strlen(white_holding)-1] = NULLCHAR;
4139 black_holding[strlen(black_holding)-1] = NULLCHAR;
4140 /* [HGM] copy holdings to board holdings area */
4141 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4142 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4143 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4145 if (appData.zippyPlay && first.initDone) {
4146 ZippyHoldings(white_holding, black_holding,
4150 if (tinyLayout || smallLayout) {
4151 char wh[16], bh[16];
4152 PackHolding(wh, white_holding);
4153 PackHolding(bh, black_holding);
4154 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4155 gameInfo.white, gameInfo.black);
4157 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4158 gameInfo.white, white_holding, _("vs."),
4159 gameInfo.black, black_holding);
4161 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4162 DrawPosition(FALSE, boards[currentMove]);
4164 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4165 sscanf(parse, "game %d white [%s black [%s <- %s",
4166 &gamenum, white_holding, black_holding,
4168 white_holding[strlen(white_holding)-1] = NULLCHAR;
4169 black_holding[strlen(black_holding)-1] = NULLCHAR;
4170 /* [HGM] copy holdings to partner-board holdings area */
4171 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4172 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4173 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4174 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4175 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4178 /* Suppress following prompt */
4179 if (looking_at(buf, &i, "*% ")) {
4180 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4181 savingComment = FALSE;
4189 i++; /* skip unparsed character and loop back */
4192 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4193 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4194 // SendToPlayer(&buf[next_out], i - next_out);
4195 started != STARTED_HOLDINGS && leftover_start > next_out) {
4196 SendToPlayer(&buf[next_out], leftover_start - next_out);
4200 leftover_len = buf_len - leftover_start;
4201 /* if buffer ends with something we couldn't parse,
4202 reparse it after appending the next read */
4204 } else if (count == 0) {
4205 RemoveInputSource(isr);
4206 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4208 DisplayFatalError(_("Error reading from ICS"), error, 1);
4213 /* Board style 12 looks like this:
4215 <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4217 * The "<12> " is stripped before it gets to this routine. The two
4218 * trailing 0's (flip state and clock ticking) are later addition, and
4219 * some chess servers may not have them, or may have only the first.
4220 * Additional trailing fields may be added in the future.
4223 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4225 #define RELATION_OBSERVING_PLAYED 0
4226 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4227 #define RELATION_PLAYING_MYMOVE 1
4228 #define RELATION_PLAYING_NOTMYMOVE -1
4229 #define RELATION_EXAMINING 2
4230 #define RELATION_ISOLATED_BOARD -3
4231 #define RELATION_STARTING_POSITION -4 /* FICS only */
4234 ParseBoard12 (char *string)
4238 char *bookHit = NULL; // [HGM] book
4240 GameMode newGameMode;
4241 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4242 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4243 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4244 char to_play, board_chars[200];
4245 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4246 char black[32], white[32];
4248 int prevMove = currentMove;
4251 int fromX, fromY, toX, toY;
4253 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4254 Boolean weird = FALSE, reqFlag = FALSE;
4256 fromX = fromY = toX = toY = -1;
4260 if (appData.debugMode)
4261 fprintf(debugFP, "Parsing board: %s\n", string);
4263 move_str[0] = NULLCHAR;
4264 elapsed_time[0] = NULLCHAR;
4265 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4267 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4268 if(string[i] == ' ') { ranks++; files = 0; }
4270 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4273 for(j = 0; j <i; j++) board_chars[j] = string[j];
4274 board_chars[i] = '\0';
4277 n = sscanf(string, PATTERN, &to_play, &double_push,
4278 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4279 &gamenum, white, black, &relation, &basetime, &increment,
4280 &white_stren, &black_stren, &white_time, &black_time,
4281 &moveNum, str, elapsed_time, move_str, &ics_flip,
4285 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4286 DisplayError(str, 0);
4290 /* Convert the move number to internal form */
4291 moveNum = (moveNum - 1) * 2;
4292 if (to_play == 'B') moveNum++;
4293 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4294 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4300 case RELATION_OBSERVING_PLAYED:
4301 case RELATION_OBSERVING_STATIC:
4302 if (gamenum == -1) {
4303 /* Old ICC buglet */
4304 relation = RELATION_OBSERVING_STATIC;
4306 newGameMode = IcsObserving;
4308 case RELATION_PLAYING_MYMOVE:
4309 case RELATION_PLAYING_NOTMYMOVE:
4311 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4312 IcsPlayingWhite : IcsPlayingBlack;
4313 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4315 case RELATION_EXAMINING:
4316 newGameMode = IcsExamining;
4318 case RELATION_ISOLATED_BOARD:
4320 /* Just display this board. If user was doing something else,
4321 we will forget about it until the next board comes. */
4322 newGameMode = IcsIdle;
4324 case RELATION_STARTING_POSITION:
4325 newGameMode = gameMode;
4329 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4330 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4331 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4332 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4333 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4334 static int lastBgGame = -1;
4336 for (k = 0; k < ranks; k++) {
4337 for (j = 0; j < files; j++)
4338 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4339 if(gameInfo.holdingsWidth > 1) {
4340 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4341 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4344 CopyBoard(partnerBoard, board);
4345 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4346 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4347 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4348 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4349 if(toSqr = strchr(str, '-')) {
4350 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4351 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4352 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4353 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4354 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4355 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4357 DisplayWhiteClock(white_time*fac, to_play == 'W');
4358 DisplayBlackClock(black_time*fac, to_play != 'W');
4359 activePartner = to_play;
4360 if(gamenum != lastBgGame) {
4362 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4365 lastBgGame = gamenum;
4366 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4367 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4368 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4369 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4370 if(!twoBoards) DisplayMessage(partnerStatus, "");
4371 partnerBoardValid = TRUE;
4375 if(appData.dualBoard && appData.bgObserve) {
4376 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4377 SendToICS(ics_prefix), SendToICS("pobserve\n");
4378 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4380 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4385 /* Modify behavior for initial board display on move listing
4388 switch (ics_getting_history) {
4392 case H_GOT_REQ_HEADER:
4393 case H_GOT_UNREQ_HEADER:
4394 /* This is the initial position of the current game */
4395 gamenum = ics_gamenum;
4396 moveNum = 0; /* old ICS bug workaround */
4397 if (to_play == 'B') {
4398 startedFromSetupPosition = TRUE;
4399 blackPlaysFirst = TRUE;
4401 if (forwardMostMove == 0) forwardMostMove = 1;
4402 if (backwardMostMove == 0) backwardMostMove = 1;
4403 if (currentMove == 0) currentMove = 1;
4405 newGameMode = gameMode;
4406 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4408 case H_GOT_UNWANTED_HEADER:
4409 /* This is an initial board that we don't want */
4411 case H_GETTING_MOVES:
4412 /* Should not happen */
4413 DisplayError(_("Error gathering move list: extra board"), 0);
4414 ics_getting_history = H_FALSE;
4418 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4419 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4420 weird && (int)gameInfo.variant < (int)VariantShogi) {
4421 /* [HGM] We seem to have switched variant unexpectedly
4422 * Try to guess new variant from board size
4424 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4425 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4426 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4427 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4428 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4429 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4430 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4431 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4432 /* Get a move list just to see the header, which
4433 will tell us whether this is really bug or zh */
4434 if (ics_getting_history == H_FALSE) {
4435 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4436 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4441 /* Take action if this is the first board of a new game, or of a
4442 different game than is currently being displayed. */
4443 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4444 relation == RELATION_ISOLATED_BOARD) {
4446 /* Forget the old game and get the history (if any) of the new one */
4447 if (gameMode != BeginningOfGame) {
4451 if (appData.autoRaiseBoard) BoardToTop();
4453 if (gamenum == -1) {
4454 newGameMode = IcsIdle;
4455 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4456 appData.getMoveList && !reqFlag) {
4457 /* Need to get game history */
4458 ics_getting_history = H_REQUESTED;
4459 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4463 /* Initially flip the board to have black on the bottom if playing
4464 black or if the ICS flip flag is set, but let the user change
4465 it with the Flip View button. */
4466 flipView = appData.autoFlipView ?
4467 (newGameMode == IcsPlayingBlack) || ics_flip :
4470 /* Done with values from previous mode; copy in new ones */
4471 gameMode = newGameMode;
4473 ics_gamenum = gamenum;
4474 if (gamenum == gs_gamenum) {
4475 int klen = strlen(gs_kind);
4476 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4477 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4478 gameInfo.event = StrSave(str);
4480 gameInfo.event = StrSave("ICS game");
4482 gameInfo.site = StrSave(appData.icsHost);
4483 gameInfo.date = PGNDate();
4484 gameInfo.round = StrSave("-");
4485 gameInfo.white = StrSave(white);
4486 gameInfo.black = StrSave(black);
4487 timeControl = basetime * 60 * 1000;
4489 timeIncrement = increment * 1000;
4490 movesPerSession = 0;
4491 gameInfo.timeControl = TimeControlTagValue();
4492 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4493 if (appData.debugMode) {
4494 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4495 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4496 setbuf(debugFP, NULL);
4499 gameInfo.outOfBook = NULL;
4501 /* Do we have the ratings? */
4502 if (strcmp(player1Name, white) == 0 &&
4503 strcmp(player2Name, black) == 0) {
4504 if (appData.debugMode)
4505 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4506 player1Rating, player2Rating);
4507 gameInfo.whiteRating = player1Rating;
4508 gameInfo.blackRating = player2Rating;
4509 } else if (strcmp(player2Name, white) == 0 &&
4510 strcmp(player1Name, black) == 0) {
4511 if (appData.debugMode)
4512 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4513 player2Rating, player1Rating);
4514 gameInfo.whiteRating = player2Rating;
4515 gameInfo.blackRating = player1Rating;
4517 player1Name[0] = player2Name[0] = NULLCHAR;
4519 /* Silence shouts if requested */
4520 if (appData.quietPlay &&
4521 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4522 SendToICS(ics_prefix);
4523 SendToICS("set shout 0\n");
4527 /* Deal with midgame name changes */
4529 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4530 if (gameInfo.white) free(gameInfo.white);
4531 gameInfo.white = StrSave(white);
4533 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4534 if (gameInfo.black) free(gameInfo.black);
4535 gameInfo.black = StrSave(black);
4539 /* Throw away game result if anything actually changes in examine mode */
4540 if (gameMode == IcsExamining && !newGame) {
4541 gameInfo.result = GameUnfinished;
4542 if (gameInfo.resultDetails != NULL) {
4543 free(gameInfo.resultDetails);
4544 gameInfo.resultDetails = NULL;
4548 /* In pausing && IcsExamining mode, we ignore boards coming
4549 in if they are in a different variation than we are. */
4550 if (pauseExamInvalid) return;
4551 if (pausing && gameMode == IcsExamining) {
4552 if (moveNum <= pauseExamForwardMostMove) {
4553 pauseExamInvalid = TRUE;
4554 forwardMostMove = pauseExamForwardMostMove;
4559 if (appData.debugMode) {
4560 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4562 /* Parse the board */
4563 for (k = 0; k < ranks; k++) {
4564 for (j = 0; j < files; j++)
4565 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4566 if(gameInfo.holdingsWidth > 1) {
4567 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4568 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4571 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4572 board[5][BOARD_RGHT+1] = WhiteAngel;
4573 board[6][BOARD_RGHT+1] = WhiteMarshall;
4574 board[1][0] = BlackMarshall;
4575 board[2][0] = BlackAngel;
4576 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4578 CopyBoard(boards[moveNum], board);
4579 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4581 startedFromSetupPosition =
4582 !CompareBoards(board, initialPosition);
4583 if(startedFromSetupPosition)
4584 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4587 /* [HGM] Set castling rights. Take the outermost Rooks,
4588 to make it also work for FRC opening positions. Note that board12
4589 is really defective for later FRC positions, as it has no way to
4590 indicate which Rook can castle if they are on the same side of King.
4591 For the initial position we grant rights to the outermost Rooks,
4592 and remember thos rights, and we then copy them on positions
4593 later in an FRC game. This means WB might not recognize castlings with
4594 Rooks that have moved back to their original position as illegal,
4595 but in ICS mode that is not its job anyway.
4597 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4598 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4600 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4601 if(board[0][i] == WhiteRook) j = i;
4602 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4603 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4604 if(board[0][i] == WhiteRook) j = i;
4605 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4606 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4607 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4608 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4609 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4610 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4611 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4613 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4614 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4615 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4616 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4617 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4618 if(board[BOARD_HEIGHT-1][k] == bKing)
4619 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4620 if(gameInfo.variant == VariantTwoKings) {
4621 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4622 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4623 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4626 r = boards[moveNum][CASTLING][0] = initialRights[0];
4627 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4628 r = boards[moveNum][CASTLING][1] = initialRights[1];
4629 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4630 r = boards[moveNum][CASTLING][3] = initialRights[3];
4631 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4632 r = boards[moveNum][CASTLING][4] = initialRights[4];
4633 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4634 /* wildcastle kludge: always assume King has rights */
4635 r = boards[moveNum][CASTLING][2] = initialRights[2];
4636 r = boards[moveNum][CASTLING][5] = initialRights[5];
4638 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4639 boards[moveNum][EP_STATUS] = EP_NONE;
4640 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4641 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4642 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4645 if (ics_getting_history == H_GOT_REQ_HEADER ||
4646 ics_getting_history == H_GOT_UNREQ_HEADER) {
4647 /* This was an initial position from a move list, not
4648 the current position */
4652 /* Update currentMove and known move number limits */
4653 newMove = newGame || moveNum > forwardMostMove;
4656 forwardMostMove = backwardMostMove = currentMove = moveNum;
4657 if (gameMode == IcsExamining && moveNum == 0) {
4658 /* Workaround for ICS limitation: we are not told the wild
4659 type when starting to examine a game. But if we ask for
4660 the move list, the move list header will tell us */
4661 ics_getting_history = H_REQUESTED;
4662 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4665 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4666 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4668 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4669 /* [HGM] applied this also to an engine that is silently watching */
4670 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4671 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4672 gameInfo.variant == currentlyInitializedVariant) {
4673 takeback = forwardMostMove - moveNum;
4674 for (i = 0; i < takeback; i++) {
4675 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4676 SendToProgram("undo\n", &first);
4681 forwardMostMove = moveNum;
4682 if (!pausing || currentMove > forwardMostMove)
4683 currentMove = forwardMostMove;
4685 /* New part of history that is not contiguous with old part */
4686 if (pausing && gameMode == IcsExamining) {
4687 pauseExamInvalid = TRUE;
4688 forwardMostMove = pauseExamForwardMostMove;
4691 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4693 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4694 // [HGM] when we will receive the move list we now request, it will be
4695 // fed to the engine from the first move on. So if the engine is not
4696 // in the initial position now, bring it there.
4697 InitChessProgram(&first, 0);
4700 ics_getting_history = H_REQUESTED;
4701 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4704 forwardMostMove = backwardMostMove = currentMove = moveNum;
4707 /* Update the clocks */
4708 if (strchr(elapsed_time, '.')) {
4710 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4711 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4713 /* Time is in seconds */
4714 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4715 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4720 if (appData.zippyPlay && newGame &&
4721 gameMode != IcsObserving && gameMode != IcsIdle &&
4722 gameMode != IcsExamining)
4723 ZippyFirstBoard(moveNum, basetime, increment);
4726 /* Put the move on the move list, first converting
4727 to canonical algebraic form. */
4729 if (appData.debugMode) {
4730 int f = forwardMostMove;
4731 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4732 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4733 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4734 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4735 fprintf(debugFP, "moveNum = %d\n", moveNum);
4736 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4737 setbuf(debugFP, NULL);
4739 if (moveNum <= backwardMostMove) {
4740 /* We don't know what the board looked like before
4742 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4743 strcat(parseList[moveNum - 1], " ");
4744 strcat(parseList[moveNum - 1], elapsed_time);
4745 moveList[moveNum - 1][0] = NULLCHAR;
4746 } else if (strcmp(move_str, "none") == 0) {
4747 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4748 /* Again, we don't know what the board looked like;
4749 this is really the start of the game. */
4750 parseList[moveNum - 1][0] = NULLCHAR;
4751 moveList[moveNum - 1][0] = NULLCHAR;
4752 backwardMostMove = moveNum;
4753 startedFromSetupPosition = TRUE;
4754 fromX = fromY = toX = toY = -1;
4756 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4757 // So we parse the long-algebraic move string in stead of the SAN move
4758 int valid; char buf[MSG_SIZ], *prom;
4760 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4761 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4762 // str looks something like "Q/a1-a2"; kill the slash
4764 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4765 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4766 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4767 strcat(buf, prom); // long move lacks promo specification!
4768 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4769 if(appData.debugMode)
4770 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4771 safeStrCpy(move_str, buf, MSG_SIZ);
4773 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4774 &fromX, &fromY, &toX, &toY, &promoChar)
4775 || ParseOneMove(buf, moveNum - 1, &moveType,
4776 &fromX, &fromY, &toX, &toY, &promoChar);
4777 // end of long SAN patch
4779 (void) CoordsToAlgebraic(boards[moveNum - 1],
4780 PosFlags(moveNum - 1),
4781 fromY, fromX, toY, toX, promoChar,
4782 parseList[moveNum-1]);
4783 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4789 if(gameInfo.variant != VariantShogi)
4790 strcat(parseList[moveNum - 1], "+");
4793 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4794 strcat(parseList[moveNum - 1], "#");
4797 strcat(parseList[moveNum - 1], " ");
4798 strcat(parseList[moveNum - 1], elapsed_time);
4799 /* currentMoveString is set as a side-effect of ParseOneMove */
4800 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4801 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4802 strcat(moveList[moveNum - 1], "\n");
4804 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4805 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4806 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4807 ChessSquare old, new = boards[moveNum][k][j];
4808 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4809 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4810 if(old == new) continue;
4811 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4812 else if(new == WhiteWazir || new == BlackWazir) {
4813 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4814 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4815 else boards[moveNum][k][j] = old; // preserve type of Gold
4816 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4817 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4820 /* Move from ICS was illegal!? Punt. */
4821 if (appData.debugMode) {
4822 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4823 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4825 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4826 strcat(parseList[moveNum - 1], " ");
4827 strcat(parseList[moveNum - 1], elapsed_time);
4828 moveList[moveNum - 1][0] = NULLCHAR;
4829 fromX = fromY = toX = toY = -1;
4832 if (appData.debugMode) {
4833 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4834 setbuf(debugFP, NULL);
4838 /* Send move to chess program (BEFORE animating it). */
4839 if (appData.zippyPlay && !newGame && newMove &&
4840 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4842 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4843 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4844 if (moveList[moveNum - 1][0] == NULLCHAR) {
4845 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4847 DisplayError(str, 0);
4849 if (first.sendTime) {
4850 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4852 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4853 if (firstMove && !bookHit) {
4855 if (first.useColors) {
4856 SendToProgram(gameMode == IcsPlayingWhite ?
4858 "black\ngo\n", &first);
4860 SendToProgram("go\n", &first);
4862 first.maybeThinking = TRUE;
4865 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4866 if (moveList[moveNum - 1][0] == NULLCHAR) {
4867 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4868 DisplayError(str, 0);
4870 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4871 SendMoveToProgram(moveNum - 1, &first);
4878 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4879 /* If move comes from a remote source, animate it. If it
4880 isn't remote, it will have already been animated. */
4881 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4882 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4884 if (!pausing && appData.highlightLastMove) {
4885 SetHighlights(fromX, fromY, toX, toY);
4889 /* Start the clocks */
4890 whiteFlag = blackFlag = FALSE;
4891 appData.clockMode = !(basetime == 0 && increment == 0);
4893 ics_clock_paused = TRUE;
4895 } else if (ticking == 1) {
4896 ics_clock_paused = FALSE;
4898 if (gameMode == IcsIdle ||
4899 relation == RELATION_OBSERVING_STATIC ||
4900 relation == RELATION_EXAMINING ||
4902 DisplayBothClocks();
4906 /* Display opponents and material strengths */
4907 if (gameInfo.variant != VariantBughouse &&
4908 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4909 if (tinyLayout || smallLayout) {
4910 if(gameInfo.variant == VariantNormal)
4911 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4912 gameInfo.white, white_stren, gameInfo.black, black_stren,
4913 basetime, increment);
4915 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4916 gameInfo.white, white_stren, gameInfo.black, black_stren,
4917 basetime, increment, (int) gameInfo.variant);
4919 if(gameInfo.variant == VariantNormal)
4920 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4921 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4922 basetime, increment);
4924 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4925 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4926 basetime, increment, VariantName(gameInfo.variant));
4929 if (appData.debugMode) {
4930 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4935 /* Display the board */
4936 if (!pausing && !appData.noGUI) {
4938 if (appData.premove)
4940 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4941 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4942 ClearPremoveHighlights();
4944 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4945 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4946 DrawPosition(j, boards[currentMove]);
4948 DisplayMove(moveNum - 1);
4949 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4950 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4951 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4952 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4956 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4958 if(bookHit) { // [HGM] book: simulate book reply
4959 static char bookMove[MSG_SIZ]; // a bit generous?
4961 programStats.nodes = programStats.depth = programStats.time =
4962 programStats.score = programStats.got_only_move = 0;
4963 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4965 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4966 strcat(bookMove, bookHit);
4967 HandleMachineMove(bookMove, &first);
4976 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4977 ics_getting_history = H_REQUESTED;
4978 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4984 SendToBoth (char *msg)
4985 { // to make it easy to keep two engines in step in dual analysis
4986 SendToProgram(msg, &first);
4987 if(second.analyzing) SendToProgram(msg, &second);
4991 AnalysisPeriodicEvent (int force)
4993 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4994 && !force) || !appData.periodicUpdates)
4997 /* Send . command to Crafty to collect stats */
5000 /* Don't send another until we get a response (this makes
5001 us stop sending to old Crafty's which don't understand
5002 the "." command (sending illegal cmds resets node count & time,
5003 which looks bad)) */
5004 programStats.ok_to_send = 0;
5008 ics_update_width (int new_width)
5010 ics_printf("set width %d\n", new_width);
5014 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5018 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5019 if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChu) {
5020 sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5021 SendToProgram(buf, cps);
5024 // null move in variant where engine does not understand it (for analysis purposes)
5025 SendBoard(cps, moveNum + 1); // send position after move in stead.
5028 if (cps->useUsermove) {
5029 SendToProgram("usermove ", cps);
5033 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5034 int len = space - parseList[moveNum];
5035 memcpy(buf, parseList[moveNum], len);
5037 buf[len] = NULLCHAR;
5039 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5041 SendToProgram(buf, cps);
5043 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5044 AlphaRank(moveList[moveNum], 4);
5045 SendToProgram(moveList[moveNum], cps);
5046 AlphaRank(moveList[moveNum], 4); // and back
5048 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5049 * the engine. It would be nice to have a better way to identify castle
5051 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5052 && cps->useOOCastle) {
5053 int fromX = moveList[moveNum][0] - AAA;
5054 int fromY = moveList[moveNum][1] - ONE;
5055 int toX = moveList[moveNum][2] - AAA;
5056 int toY = moveList[moveNum][3] - ONE;
5057 if((boards[moveNum][fromY][fromX] == WhiteKing
5058 && boards[moveNum][toY][toX] == WhiteRook)
5059 || (boards[moveNum][fromY][fromX] == BlackKing
5060 && boards[moveNum][toY][toX] == BlackRook)) {
5061 if(toX > fromX) SendToProgram("O-O\n", cps);
5062 else SendToProgram("O-O-O\n", cps);
5064 else SendToProgram(moveList[moveNum], cps);
5066 if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5067 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5068 moveList[moveNum][5], moveList[moveNum][6] - '0',
5069 moveList[moveNum][5], moveList[moveNum][6] - '0',
5070 moveList[moveNum][2], moveList[moveNum][3] - '0');
5071 SendToProgram(buf, cps);
5073 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5074 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5075 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5076 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5077 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5079 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5080 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5081 SendToProgram(buf, cps);
5083 else SendToProgram(moveList[moveNum], cps);
5084 /* End of additions by Tord */
5087 /* [HGM] setting up the opening has brought engine in force mode! */
5088 /* Send 'go' if we are in a mode where machine should play. */
5089 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5090 (gameMode == TwoMachinesPlay ||
5092 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5094 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5095 SendToProgram("go\n", cps);
5096 if (appData.debugMode) {
5097 fprintf(debugFP, "(extra)\n");
5100 setboardSpoiledMachineBlack = 0;
5104 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5106 char user_move[MSG_SIZ];
5109 if(gameInfo.variant == VariantSChess && promoChar) {
5110 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5111 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5112 } else suffix[0] = NULLCHAR;
5116 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5117 (int)moveType, fromX, fromY, toX, toY);
5118 DisplayError(user_move + strlen("say "), 0);
5120 case WhiteKingSideCastle:
5121 case BlackKingSideCastle:
5122 case WhiteQueenSideCastleWild:
5123 case BlackQueenSideCastleWild:
5125 case WhiteHSideCastleFR:
5126 case BlackHSideCastleFR:
5128 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5130 case WhiteQueenSideCastle:
5131 case BlackQueenSideCastle:
5132 case WhiteKingSideCastleWild:
5133 case BlackKingSideCastleWild:
5135 case WhiteASideCastleFR:
5136 case BlackASideCastleFR:
5138 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5140 case WhiteNonPromotion:
5141 case BlackNonPromotion:
5142 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5144 case WhitePromotion:
5145 case BlackPromotion:
5146 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5147 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5148 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5149 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5150 PieceToChar(WhiteFerz));
5151 else if(gameInfo.variant == VariantGreat)
5152 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5153 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5154 PieceToChar(WhiteMan));
5156 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5157 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5163 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5164 ToUpper(PieceToChar((ChessSquare) fromX)),
5165 AAA + toX, ONE + toY);
5167 case IllegalMove: /* could be a variant we don't quite understand */
5168 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5170 case WhiteCapturesEnPassant:
5171 case BlackCapturesEnPassant:
5172 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5173 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5176 SendToICS(user_move);
5177 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5178 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5183 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5184 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5185 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5186 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5187 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5190 if(gameMode != IcsExamining) { // is this ever not the case?
5191 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5193 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5194 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5195 } else { // on FICS we must first go to general examine mode
5196 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5198 if(gameInfo.variant != VariantNormal) {
5199 // try figure out wild number, as xboard names are not always valid on ICS
5200 for(i=1; i<=36; i++) {
5201 snprintf(buf, MSG_SIZ, "wild/%d", i);
5202 if(StringToVariant(buf) == gameInfo.variant) break;
5204 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5205 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5206 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5207 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5208 SendToICS(ics_prefix);
5210 if(startedFromSetupPosition || backwardMostMove != 0) {
5211 fen = PositionToFEN(backwardMostMove, NULL, 1);
5212 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5213 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5215 } else { // FICS: everything has to set by separate bsetup commands
5216 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5217 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5219 if(!WhiteOnMove(backwardMostMove)) {
5220 SendToICS("bsetup tomove black\n");
5222 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5223 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5225 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5226 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5228 i = boards[backwardMostMove][EP_STATUS];
5229 if(i >= 0) { // set e.p.
5230 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5236 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5237 SendToICS("bsetup done\n"); // switch to normal examining.
5239 for(i = backwardMostMove; i<last; i++) {
5241 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5242 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5243 int len = strlen(moveList[i]);
5244 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5245 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5249 SendToICS(ics_prefix);
5250 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5253 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5256 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5258 if (rf == DROP_RANK) {
5259 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5260 sprintf(move, "%c@%c%c\n",
5261 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5263 if (promoChar == 'x' || promoChar == NULLCHAR) {
5264 sprintf(move, "%c%c%c%c\n",
5265 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5266 if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5268 sprintf(move, "%c%c%c%c%c\n",
5269 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5275 ProcessICSInitScript (FILE *f)
5279 while (fgets(buf, MSG_SIZ, f)) {
5280 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5287 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5288 static ClickType lastClickType;
5293 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5294 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5295 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5296 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5297 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5298 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5301 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5302 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5303 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5304 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5305 if(!step) step = -1;
5306 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5307 appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion) ||
5308 IS_SHOGI(gameInfo.variant) && promoSweep != CHUPROMOTED last && last != CHUPROMOTED promoSweep && last != promoSweep);
5310 int victim = boards[currentMove][toY][toX];
5311 boards[currentMove][toY][toX] = promoSweep;
5312 DrawPosition(FALSE, boards[currentMove]);
5313 boards[currentMove][toY][toX] = victim;
5315 ChangeDragPiece(promoSweep);
5319 PromoScroll (int x, int y)
5323 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5324 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5325 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5326 if(!step) return FALSE;
5327 lastX = x; lastY = y;
5328 if((promoSweep < BlackPawn) == flipView) step = -step;
5329 if(step > 0) selectFlag = 1;
5330 if(!selectFlag) Sweep(step);
5335 NextPiece (int step)
5337 ChessSquare piece = boards[currentMove][toY][toX];
5340 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5341 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5342 if(!step) step = -1;
5343 } while(PieceToChar(pieceSweep) == '.');
5344 boards[currentMove][toY][toX] = pieceSweep;
5345 DrawPosition(FALSE, boards[currentMove]);
5346 boards[currentMove][toY][toX] = piece;
5348 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5350 AlphaRank (char *move, int n)
5352 // char *p = move, c; int x, y;
5354 if (appData.debugMode) {
5355 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5359 move[2]>='0' && move[2]<='9' &&
5360 move[3]>='a' && move[3]<='x' ) {
5362 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5363 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5365 if(move[0]>='0' && move[0]<='9' &&
5366 move[1]>='a' && move[1]<='x' &&
5367 move[2]>='0' && move[2]<='9' &&
5368 move[3]>='a' && move[3]<='x' ) {
5369 /* input move, Shogi -> normal */
5370 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5371 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5372 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5373 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5376 move[3]>='0' && move[3]<='9' &&
5377 move[2]>='a' && move[2]<='x' ) {
5379 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5380 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5383 move[0]>='a' && move[0]<='x' &&
5384 move[3]>='0' && move[3]<='9' &&
5385 move[2]>='a' && move[2]<='x' ) {
5386 /* output move, normal -> Shogi */
5387 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5388 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5389 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5390 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5391 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5393 if (appData.debugMode) {
5394 fprintf(debugFP, " out = '%s'\n", move);
5398 char yy_textstr[8000];
5400 /* Parser for moves from gnuchess, ICS, or user typein box */
5402 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5404 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5406 switch (*moveType) {
5407 case WhitePromotion:
5408 case BlackPromotion:
5409 case WhiteNonPromotion:
5410 case BlackNonPromotion:
5413 case WhiteCapturesEnPassant:
5414 case BlackCapturesEnPassant:
5415 case WhiteKingSideCastle:
5416 case WhiteQueenSideCastle:
5417 case BlackKingSideCastle:
5418 case BlackQueenSideCastle:
5419 case WhiteKingSideCastleWild:
5420 case WhiteQueenSideCastleWild:
5421 case BlackKingSideCastleWild:
5422 case BlackQueenSideCastleWild:
5423 /* Code added by Tord: */
5424 case WhiteHSideCastleFR:
5425 case WhiteASideCastleFR:
5426 case BlackHSideCastleFR:
5427 case BlackASideCastleFR:
5428 /* End of code added by Tord */
5429 case IllegalMove: /* bug or odd chess variant */
5430 *fromX = currentMoveString[0] - AAA;
5431 *fromY = currentMoveString[1] - ONE;
5432 *toX = currentMoveString[2] - AAA;
5433 *toY = currentMoveString[3] - ONE;
5434 *promoChar = currentMoveString[4];
5435 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5436 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5437 if (appData.debugMode) {
5438 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5440 *fromX = *fromY = *toX = *toY = 0;
5443 if (appData.testLegality) {
5444 return (*moveType != IllegalMove);
5446 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5447 // [HGM] lion: if this is a double move we are less critical
5448 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5453 *fromX = *moveType == WhiteDrop ?
5454 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5455 (int) CharToPiece(ToLower(currentMoveString[0]));
5457 *toX = currentMoveString[2] - AAA;
5458 *toY = currentMoveString[3] - ONE;
5459 *promoChar = NULLCHAR;
5463 case ImpossibleMove:
5473 if (appData.debugMode) {
5474 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5477 *fromX = *fromY = *toX = *toY = 0;
5478 *promoChar = NULLCHAR;
5483 Boolean pushed = FALSE;
5484 char *lastParseAttempt;
5487 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5488 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5489 int fromX, fromY, toX, toY; char promoChar;
5494 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5495 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5496 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5499 endPV = forwardMostMove;
5501 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5502 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5503 lastParseAttempt = pv;
5504 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5505 if(!valid && nr == 0 &&
5506 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5507 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5508 // Hande case where played move is different from leading PV move
5509 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5510 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5511 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5512 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5513 endPV += 2; // if position different, keep this
5514 moveList[endPV-1][0] = fromX + AAA;
5515 moveList[endPV-1][1] = fromY + ONE;
5516 moveList[endPV-1][2] = toX + AAA;
5517 moveList[endPV-1][3] = toY + ONE;
5518 parseList[endPV-1][0] = NULLCHAR;
5519 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5522 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5523 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5524 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5525 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5526 valid++; // allow comments in PV
5530 if(endPV+1 > framePtr) break; // no space, truncate
5533 CopyBoard(boards[endPV], boards[endPV-1]);
5534 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5535 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5536 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5537 CoordsToAlgebraic(boards[endPV - 1],
5538 PosFlags(endPV - 1),
5539 fromY, fromX, toY, toX, promoChar,
5540 parseList[endPV - 1]);
5542 if(atEnd == 2) return; // used hidden, for PV conversion
5543 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5544 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5545 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5546 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5547 DrawPosition(TRUE, boards[currentMove]);
5551 MultiPV (ChessProgramState *cps)
5552 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5554 for(i=0; i<cps->nrOptions; i++)
5555 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5560 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5563 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5565 int startPV, multi, lineStart, origIndex = index;
5566 char *p, buf2[MSG_SIZ];
5567 ChessProgramState *cps = (pane ? &second : &first);
5569 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5570 lastX = x; lastY = y;
5571 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5572 lineStart = startPV = index;
5573 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5574 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5576 do{ while(buf[index] && buf[index] != '\n') index++;
5577 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5579 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5580 int n = cps->option[multi].value;
5581 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5582 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5583 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5584 cps->option[multi].value = n;
5587 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5588 ExcludeClick(origIndex - lineStart);
5591 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5592 *start = startPV; *end = index-1;
5593 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5600 static char buf[10*MSG_SIZ];
5601 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5603 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5604 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5605 for(i = forwardMostMove; i<endPV; i++){
5606 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5607 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5610 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5611 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5612 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5618 LoadPV (int x, int y)
5619 { // called on right mouse click to load PV
5620 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5621 lastX = x; lastY = y;
5622 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5630 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5631 if(endPV < 0) return;
5632 if(appData.autoCopyPV) CopyFENToClipboard();
5634 if(extendGame && currentMove > forwardMostMove) {
5635 Boolean saveAnimate = appData.animate;
5637 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5638 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5639 } else storedGames--; // abandon shelved tail of original game
5642 forwardMostMove = currentMove;
5643 currentMove = oldFMM;
5644 appData.animate = FALSE;
5645 ToNrEvent(forwardMostMove);
5646 appData.animate = saveAnimate;
5648 currentMove = forwardMostMove;
5649 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5650 ClearPremoveHighlights();
5651 DrawPosition(TRUE, boards[currentMove]);
5655 MovePV (int x, int y, int h)
5656 { // step through PV based on mouse coordinates (called on mouse move)
5657 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5659 // we must somehow check if right button is still down (might be released off board!)
5660 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5661 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5662 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5664 lastX = x; lastY = y;
5666 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5667 if(endPV < 0) return;
5668 if(y < margin) step = 1; else
5669 if(y > h - margin) step = -1;
5670 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5671 currentMove += step;
5672 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5673 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5674 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5675 DrawPosition(FALSE, boards[currentMove]);
5679 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5680 // All positions will have equal probability, but the current method will not provide a unique
5681 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5687 int piecesLeft[(int)BlackPawn];
5688 int seed, nrOfShuffles;
5691 GetPositionNumber ()
5692 { // sets global variable seed
5695 seed = appData.defaultFrcPosition;
5696 if(seed < 0) { // randomize based on time for negative FRC position numbers
5697 for(i=0; i<50; i++) seed += random();
5698 seed = random() ^ random() >> 8 ^ random() << 8;
5699 if(seed<0) seed = -seed;
5704 put (Board board, int pieceType, int rank, int n, int shade)
5705 // put the piece on the (n-1)-th empty squares of the given shade
5709 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5710 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5711 board[rank][i] = (ChessSquare) pieceType;
5712 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5714 piecesLeft[pieceType]--;
5723 AddOnePiece (Board board, int pieceType, int rank, int shade)
5724 // calculate where the next piece goes, (any empty square), and put it there
5728 i = seed % squaresLeft[shade];
5729 nrOfShuffles *= squaresLeft[shade];
5730 seed /= squaresLeft[shade];
5731 put(board, pieceType, rank, i, shade);
5735 AddTwoPieces (Board board, int pieceType, int rank)
5736 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5738 int i, n=squaresLeft[ANY], j=n-1, k;
5740 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5741 i = seed % k; // pick one
5744 while(i >= j) i -= j--;
5745 j = n - 1 - j; i += j;
5746 put(board, pieceType, rank, j, ANY);
5747 put(board, pieceType, rank, i, ANY);
5751 SetUpShuffle (Board board, int number)
5755 GetPositionNumber(); nrOfShuffles = 1;
5757 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5758 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5759 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5761 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5763 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5764 p = (int) board[0][i];
5765 if(p < (int) BlackPawn) piecesLeft[p] ++;
5766 board[0][i] = EmptySquare;
5769 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5770 // shuffles restricted to allow normal castling put KRR first
5771 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5772 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5773 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5774 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5775 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5776 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5777 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5778 put(board, WhiteRook, 0, 0, ANY);
5779 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5782 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5783 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5784 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5785 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5786 while(piecesLeft[p] >= 2) {
5787 AddOnePiece(board, p, 0, LITE);
5788 AddOnePiece(board, p, 0, DARK);
5790 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5793 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5794 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5795 // but we leave King and Rooks for last, to possibly obey FRC restriction
5796 if(p == (int)WhiteRook) continue;
5797 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5798 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5801 // now everything is placed, except perhaps King (Unicorn) and Rooks
5803 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5804 // Last King gets castling rights
5805 while(piecesLeft[(int)WhiteUnicorn]) {
5806 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5807 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5810 while(piecesLeft[(int)WhiteKing]) {
5811 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5812 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5817 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5818 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5821 // Only Rooks can be left; simply place them all
5822 while(piecesLeft[(int)WhiteRook]) {
5823 i = put(board, WhiteRook, 0, 0, ANY);
5824 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5827 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5829 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5832 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5833 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5836 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5840 SetCharTable (char *table, const char * map)
5841 /* [HGM] moved here from winboard.c because of its general usefulness */
5842 /* Basically a safe strcpy that uses the last character as King */
5844 int result = FALSE; int NrPieces;
5846 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5847 && NrPieces >= 12 && !(NrPieces&1)) {
5848 int i; /* [HGM] Accept even length from 12 to 34 */
5850 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5851 for( i=0; i<NrPieces/2-1; i++ ) {
5853 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5855 table[(int) WhiteKing] = map[NrPieces/2-1];
5856 table[(int) BlackKing] = map[NrPieces-1];
5865 Prelude (Board board)
5866 { // [HGM] superchess: random selection of exo-pieces
5867 int i, j, k; ChessSquare p;
5868 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5870 GetPositionNumber(); // use FRC position number
5872 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5873 SetCharTable(pieceToChar, appData.pieceToCharTable);
5874 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5875 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5878 j = seed%4; seed /= 4;
5879 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5880 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5881 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5882 j = seed%3 + (seed%3 >= j); seed /= 3;
5883 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5884 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5885 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5886 j = seed%3; seed /= 3;
5887 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5888 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5889 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5890 j = seed%2 + (seed%2 >= j); seed /= 2;
5891 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5892 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5893 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5894 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5895 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5896 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5897 put(board, exoPieces[0], 0, 0, ANY);
5898 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5902 InitPosition (int redraw)
5904 ChessSquare (* pieces)[BOARD_FILES];
5905 int i, j, pawnRow=1, pieceRows=1, overrule,
5906 oldx = gameInfo.boardWidth,
5907 oldy = gameInfo.boardHeight,
5908 oldh = gameInfo.holdingsWidth;
5911 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5913 /* [AS] Initialize pv info list [HGM] and game status */
5915 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5916 pvInfoList[i].depth = 0;
5917 boards[i][EP_STATUS] = EP_NONE;
5918 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5921 initialRulePlies = 0; /* 50-move counter start */
5923 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5924 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5928 /* [HGM] logic here is completely changed. In stead of full positions */
5929 /* the initialized data only consist of the two backranks. The switch */
5930 /* selects which one we will use, which is than copied to the Board */
5931 /* initialPosition, which for the rest is initialized by Pawns and */
5932 /* empty squares. This initial position is then copied to boards[0], */
5933 /* possibly after shuffling, so that it remains available. */
5935 gameInfo.holdingsWidth = 0; /* default board sizes */
5936 gameInfo.boardWidth = 8;
5937 gameInfo.boardHeight = 8;
5938 gameInfo.holdingsSize = 0;
5939 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5940 for(i=0; i<BOARD_FILES-2; i++)
5941 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5942 initialPosition[EP_STATUS] = EP_NONE;
5943 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5944 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5945 SetCharTable(pieceNickName, appData.pieceNickNames);
5946 else SetCharTable(pieceNickName, "............");
5949 switch (gameInfo.variant) {
5950 case VariantFischeRandom:
5951 shuffleOpenings = TRUE;
5954 case VariantShatranj:
5955 pieces = ShatranjArray;
5956 nrCastlingRights = 0;
5957 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5960 pieces = makrukArray;
5961 nrCastlingRights = 0;
5962 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5965 pieces = aseanArray;
5966 nrCastlingRights = 0;
5967 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5969 case VariantTwoKings:
5970 pieces = twoKingsArray;
5973 pieces = GrandArray;
5974 nrCastlingRights = 0;
5975 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5976 gameInfo.boardWidth = 10;
5977 gameInfo.boardHeight = 10;
5978 gameInfo.holdingsSize = 7;
5980 case VariantCapaRandom:
5981 shuffleOpenings = TRUE;
5982 case VariantCapablanca:
5983 pieces = CapablancaArray;
5984 gameInfo.boardWidth = 10;
5985 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5988 pieces = GothicArray;
5989 gameInfo.boardWidth = 10;
5990 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5993 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5994 gameInfo.holdingsSize = 7;
5995 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5998 pieces = JanusArray;
5999 gameInfo.boardWidth = 10;
6000 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6001 nrCastlingRights = 6;
6002 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6003 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6004 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6005 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6006 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6007 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6010 pieces = FalconArray;
6011 gameInfo.boardWidth = 10;
6012 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6014 case VariantXiangqi:
6015 pieces = XiangqiArray;
6016 gameInfo.boardWidth = 9;
6017 gameInfo.boardHeight = 10;
6018 nrCastlingRights = 0;
6019 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6022 pieces = ShogiArray;
6023 gameInfo.boardWidth = 9;
6024 gameInfo.boardHeight = 9;
6025 gameInfo.holdingsSize = 7;
6026 nrCastlingRights = 0;
6027 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6030 pieces = ChuArray; pieceRows = 3;
6031 gameInfo.boardWidth = 12;
6032 gameInfo.boardHeight = 12;
6033 nrCastlingRights = 0;
6034 SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6035 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6037 case VariantCourier:
6038 pieces = CourierArray;
6039 gameInfo.boardWidth = 12;
6040 nrCastlingRights = 0;
6041 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6043 case VariantKnightmate:
6044 pieces = KnightmateArray;
6045 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6047 case VariantSpartan:
6048 pieces = SpartanArray;
6049 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6053 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6056 pieces = fairyArray;
6057 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6060 pieces = GreatArray;
6061 gameInfo.boardWidth = 10;
6062 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6063 gameInfo.holdingsSize = 8;
6067 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6068 gameInfo.holdingsSize = 8;
6069 startedFromSetupPosition = TRUE;
6071 case VariantCrazyhouse:
6072 case VariantBughouse:
6074 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6075 gameInfo.holdingsSize = 5;
6077 case VariantWildCastle:
6079 /* !!?shuffle with kings guaranteed to be on d or e file */
6080 shuffleOpenings = 1;
6082 case VariantNoCastle:
6084 nrCastlingRights = 0;
6085 /* !!?unconstrained back-rank shuffle */
6086 shuffleOpenings = 1;
6091 if(appData.NrFiles >= 0) {
6092 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6093 gameInfo.boardWidth = appData.NrFiles;
6095 if(appData.NrRanks >= 0) {
6096 gameInfo.boardHeight = appData.NrRanks;
6098 if(appData.holdingsSize >= 0) {
6099 i = appData.holdingsSize;
6100 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6101 gameInfo.holdingsSize = i;
6103 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6104 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6105 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6107 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6108 if(pawnRow < 1) pawnRow = 1;
6109 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6110 if(gameInfo.variant == VariantChu) pawnRow = 3;
6112 /* User pieceToChar list overrules defaults */
6113 if(appData.pieceToCharTable != NULL)
6114 SetCharTable(pieceToChar, appData.pieceToCharTable);
6116 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6118 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6119 s = (ChessSquare) 0; /* account holding counts in guard band */
6120 for( i=0; i<BOARD_HEIGHT; i++ )
6121 initialPosition[i][j] = s;
6123 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6124 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6125 initialPosition[pawnRow][j] = WhitePawn;
6126 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6127 if(gameInfo.variant == VariantXiangqi) {
6129 initialPosition[pawnRow][j] =
6130 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6131 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6132 initialPosition[2][j] = WhiteCannon;
6133 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6137 if(gameInfo.variant == VariantChu) {
6138 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6139 initialPosition[pawnRow+1][j] = WhiteCobra,
6140 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6141 for(i=1; i<pieceRows; i++) {
6142 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6143 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6146 if(gameInfo.variant == VariantGrand) {
6147 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6148 initialPosition[0][j] = WhiteRook;
6149 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6152 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
6154 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6157 initialPosition[1][j] = WhiteBishop;
6158 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6160 initialPosition[1][j] = WhiteRook;
6161 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6164 if( nrCastlingRights == -1) {
6165 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6166 /* This sets default castling rights from none to normal corners */
6167 /* Variants with other castling rights must set them themselves above */
6168 nrCastlingRights = 6;
6170 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6171 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6172 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6173 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6174 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6175 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6178 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6179 if(gameInfo.variant == VariantGreat) { // promotion commoners
6180 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6181 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6182 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6183 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6185 if( gameInfo.variant == VariantSChess ) {
6186 initialPosition[1][0] = BlackMarshall;
6187 initialPosition[2][0] = BlackAngel;
6188 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6189 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6190 initialPosition[1][1] = initialPosition[2][1] =
6191 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6193 if (appData.debugMode) {
6194 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6196 if(shuffleOpenings) {
6197 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6198 startedFromSetupPosition = TRUE;
6200 if(startedFromPositionFile) {
6201 /* [HGM] loadPos: use PositionFile for every new game */
6202 CopyBoard(initialPosition, filePosition);
6203 for(i=0; i<nrCastlingRights; i++)
6204 initialRights[i] = filePosition[CASTLING][i];
6205 startedFromSetupPosition = TRUE;
6208 CopyBoard(boards[0], initialPosition);
6210 if(oldx != gameInfo.boardWidth ||
6211 oldy != gameInfo.boardHeight ||
6212 oldv != gameInfo.variant ||
6213 oldh != gameInfo.holdingsWidth
6215 InitDrawingSizes(-2 ,0);
6217 oldv = gameInfo.variant;
6219 DrawPosition(TRUE, boards[currentMove]);
6223 SendBoard (ChessProgramState *cps, int moveNum)
6225 char message[MSG_SIZ];
6227 if (cps->useSetboard) {
6228 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6229 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6230 SendToProgram(message, cps);
6235 int i, j, left=0, right=BOARD_WIDTH;
6236 /* Kludge to set black to move, avoiding the troublesome and now
6237 * deprecated "black" command.
6239 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6240 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6242 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6244 SendToProgram("edit\n", cps);
6245 SendToProgram("#\n", cps);
6246 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6247 bp = &boards[moveNum][i][left];
6248 for (j = left; j < right; j++, bp++) {
6249 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6250 if ((int) *bp < (int) BlackPawn) {
6251 if(j == BOARD_RGHT+1)
6252 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6253 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6254 if(message[0] == '+' || message[0] == '~') {
6255 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6256 PieceToChar((ChessSquare)(DEMOTED *bp)),
6259 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6260 message[1] = BOARD_RGHT - 1 - j + '1';
6261 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6263 SendToProgram(message, cps);
6268 SendToProgram("c\n", cps);
6269 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6270 bp = &boards[moveNum][i][left];
6271 for (j = left; j < right; j++, bp++) {
6272 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6273 if (((int) *bp != (int) EmptySquare)
6274 && ((int) *bp >= (int) BlackPawn)) {
6275 if(j == BOARD_LEFT-2)
6276 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6277 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6279 if(message[0] == '+' || message[0] == '~') {
6280 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6281 PieceToChar((ChessSquare)(DEMOTED *bp)),
6284 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6285 message[1] = BOARD_RGHT - 1 - j + '1';
6286 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6288 SendToProgram(message, cps);
6293 SendToProgram(".\n", cps);
6295 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6298 char exclusionHeader[MSG_SIZ];
6299 int exCnt, excludePtr;
6300 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6301 static Exclusion excluTab[200];
6302 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6308 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6309 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6315 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6316 excludePtr = 24; exCnt = 0;
6321 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6322 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6323 char buf[2*MOVE_LEN], *p;
6324 Exclusion *e = excluTab;
6326 for(i=0; i<exCnt; i++)
6327 if(e[i].ff == fromX && e[i].fr == fromY &&
6328 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6329 if(i == exCnt) { // was not in exclude list; add it
6330 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6331 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6332 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6335 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6336 excludePtr++; e[i].mark = excludePtr++;
6337 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6340 exclusionHeader[e[i].mark] = state;
6344 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6345 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6349 if((signed char)promoChar == -1) { // kludge to indicate best move
6350 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6351 return 1; // if unparsable, abort
6353 // update exclusion map (resolving toggle by consulting existing state)
6354 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6356 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6357 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6358 excludeMap[k] |= 1<<j;
6359 else excludeMap[k] &= ~(1<<j);
6361 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6363 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6364 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6366 return (state == '+');
6370 ExcludeClick (int index)
6373 Exclusion *e = excluTab;
6374 if(index < 25) { // none, best or tail clicked
6375 if(index < 13) { // none: include all
6376 WriteMap(0); // clear map
6377 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6378 SendToBoth("include all\n"); // and inform engine
6379 } else if(index > 18) { // tail
6380 if(exclusionHeader[19] == '-') { // tail was excluded
6381 SendToBoth("include all\n");
6382 WriteMap(0); // clear map completely
6383 // now re-exclude selected moves
6384 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6385 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6386 } else { // tail was included or in mixed state
6387 SendToBoth("exclude all\n");
6388 WriteMap(0xFF); // fill map completely
6389 // now re-include selected moves
6390 j = 0; // count them
6391 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6392 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6393 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6396 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6399 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6400 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6401 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6408 DefaultPromoChoice (int white)
6411 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6412 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6413 result = WhiteFerz; // no choice
6414 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6415 result= WhiteKing; // in Suicide Q is the last thing we want
6416 else if(gameInfo.variant == VariantSpartan)
6417 result = white ? WhiteQueen : WhiteAngel;
6418 else result = WhiteQueen;
6419 if(!white) result = WHITE_TO_BLACK result;
6423 static int autoQueen; // [HGM] oneclick
6426 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6428 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6429 /* [HGM] add Shogi promotions */
6430 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6435 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6436 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6438 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6439 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6442 piece = boards[currentMove][fromY][fromX];
6443 if(gameInfo.variant == VariantChu) {
6444 int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6445 promotionZoneSize = BOARD_HEIGHT/3;
6446 highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6447 } else if(gameInfo.variant == VariantShogi) {
6448 promotionZoneSize = BOARD_HEIGHT/3;
6449 highestPromotingPiece = (int)WhiteAlfil;
6450 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6451 promotionZoneSize = 3;
6454 // Treat Lance as Pawn when it is not representing Amazon
6455 if(gameInfo.variant != VariantSuper) {
6456 if(piece == WhiteLance) piece = WhitePawn; else
6457 if(piece == BlackLance) piece = BlackPawn;
6460 // next weed out all moves that do not touch the promotion zone at all
6461 if((int)piece >= BlackPawn) {
6462 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6464 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6466 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6467 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6470 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6472 // weed out mandatory Shogi promotions
6473 if(gameInfo.variant == VariantShogi) {
6474 if(piece >= BlackPawn) {
6475 if(toY == 0 && piece == BlackPawn ||
6476 toY == 0 && piece == BlackQueen ||
6477 toY <= 1 && piece == BlackKnight) {
6482 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6483 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6484 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6491 // weed out obviously illegal Pawn moves
6492 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6493 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6494 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6495 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6496 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6497 // note we are not allowed to test for valid (non-)capture, due to premove
6500 // we either have a choice what to promote to, or (in Shogi) whether to promote
6501 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6502 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6503 *promoChoice = PieceToChar(BlackFerz); // no choice
6506 // no sense asking what we must promote to if it is going to explode...
6507 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6508 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6511 // give caller the default choice even if we will not make it
6512 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6513 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6514 if( sweepSelect && gameInfo.variant != VariantGreat
6515 && gameInfo.variant != VariantGrand
6516 && gameInfo.variant != VariantSuper) return FALSE;
6517 if(autoQueen) return FALSE; // predetermined
6519 // suppress promotion popup on illegal moves that are not premoves
6520 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6521 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6522 if(appData.testLegality && !premove) {
6523 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6524 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) ? '+' : NULLCHAR);
6525 if(moveType != WhitePromotion && moveType != BlackPromotion)
6533 InPalace (int row, int column)
6534 { /* [HGM] for Xiangqi */
6535 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6536 column < (BOARD_WIDTH + 4)/2 &&
6537 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6542 PieceForSquare (int x, int y)
6544 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6547 return boards[currentMove][y][x];
6551 OKToStartUserMove (int x, int y)
6553 ChessSquare from_piece;
6556 if (matchMode) return FALSE;
6557 if (gameMode == EditPosition) return TRUE;
6559 if (x >= 0 && y >= 0)
6560 from_piece = boards[currentMove][y][x];
6562 from_piece = EmptySquare;
6564 if (from_piece == EmptySquare) return FALSE;
6566 white_piece = (int)from_piece >= (int)WhitePawn &&
6567 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6571 case TwoMachinesPlay:
6579 case MachinePlaysWhite:
6580 case IcsPlayingBlack:
6581 if (appData.zippyPlay) return FALSE;
6583 DisplayMoveError(_("You are playing Black"));
6588 case MachinePlaysBlack:
6589 case IcsPlayingWhite:
6590 if (appData.zippyPlay) return FALSE;
6592 DisplayMoveError(_("You are playing White"));
6597 case PlayFromGameFile:
6598 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6600 if (!white_piece && WhiteOnMove(currentMove)) {
6601 DisplayMoveError(_("It is White's turn"));
6604 if (white_piece && !WhiteOnMove(currentMove)) {
6605 DisplayMoveError(_("It is Black's turn"));
6608 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6609 /* Editing correspondence game history */
6610 /* Could disallow this or prompt for confirmation */
6615 case BeginningOfGame:
6616 if (appData.icsActive) return FALSE;
6617 if (!appData.noChessProgram) {
6619 DisplayMoveError(_("You are playing White"));
6626 if (!white_piece && WhiteOnMove(currentMove)) {
6627 DisplayMoveError(_("It is White's turn"));
6630 if (white_piece && !WhiteOnMove(currentMove)) {
6631 DisplayMoveError(_("It is Black's turn"));
6640 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6641 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6642 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6643 && gameMode != AnalyzeFile && gameMode != Training) {
6644 DisplayMoveError(_("Displayed position is not current"));
6651 OnlyMove (int *x, int *y, Boolean captures)
6653 DisambiguateClosure cl;
6654 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6656 case MachinePlaysBlack:
6657 case IcsPlayingWhite:
6658 case BeginningOfGame:
6659 if(!WhiteOnMove(currentMove)) return FALSE;
6661 case MachinePlaysWhite:
6662 case IcsPlayingBlack:
6663 if(WhiteOnMove(currentMove)) return FALSE;
6670 cl.pieceIn = EmptySquare;
6675 cl.promoCharIn = NULLCHAR;
6676 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6677 if( cl.kind == NormalMove ||
6678 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6679 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6680 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6687 if(cl.kind != ImpossibleMove) return FALSE;
6688 cl.pieceIn = EmptySquare;
6693 cl.promoCharIn = NULLCHAR;
6694 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6695 if( cl.kind == NormalMove ||
6696 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6697 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6698 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6703 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6709 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6710 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6711 int lastLoadGameUseList = FALSE;
6712 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6713 ChessMove lastLoadGameStart = EndOfFile;
6717 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6721 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6723 /* Check if the user is playing in turn. This is complicated because we
6724 let the user "pick up" a piece before it is his turn. So the piece he
6725 tried to pick up may have been captured by the time he puts it down!
6726 Therefore we use the color the user is supposed to be playing in this
6727 test, not the color of the piece that is currently on the starting
6728 square---except in EditGame mode, where the user is playing both
6729 sides; fortunately there the capture race can't happen. (It can
6730 now happen in IcsExamining mode, but that's just too bad. The user
6731 will get a somewhat confusing message in that case.)
6736 case TwoMachinesPlay:
6740 /* We switched into a game mode where moves are not accepted,
6741 perhaps while the mouse button was down. */
6744 case MachinePlaysWhite:
6745 /* User is moving for Black */
6746 if (WhiteOnMove(currentMove)) {
6747 DisplayMoveError(_("It is White's turn"));
6752 case MachinePlaysBlack:
6753 /* User is moving for White */
6754 if (!WhiteOnMove(currentMove)) {
6755 DisplayMoveError(_("It is Black's turn"));
6760 case PlayFromGameFile:
6761 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6764 case BeginningOfGame:
6767 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6768 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6769 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6770 /* User is moving for Black */
6771 if (WhiteOnMove(currentMove)) {
6772 DisplayMoveError(_("It is White's turn"));
6776 /* User is moving for White */
6777 if (!WhiteOnMove(currentMove)) {
6778 DisplayMoveError(_("It is Black's turn"));
6784 case IcsPlayingBlack:
6785 /* User is moving for Black */
6786 if (WhiteOnMove(currentMove)) {
6787 if (!appData.premove) {
6788 DisplayMoveError(_("It is White's turn"));
6789 } else if (toX >= 0 && toY >= 0) {
6792 premoveFromX = fromX;
6793 premoveFromY = fromY;
6794 premovePromoChar = promoChar;
6796 if (appData.debugMode)
6797 fprintf(debugFP, "Got premove: fromX %d,"
6798 "fromY %d, toX %d, toY %d\n",
6799 fromX, fromY, toX, toY);
6805 case IcsPlayingWhite:
6806 /* User is moving for White */
6807 if (!WhiteOnMove(currentMove)) {
6808 if (!appData.premove) {
6809 DisplayMoveError(_("It is Black's turn"));
6810 } else if (toX >= 0 && toY >= 0) {
6813 premoveFromX = fromX;
6814 premoveFromY = fromY;
6815 premovePromoChar = promoChar;
6817 if (appData.debugMode)
6818 fprintf(debugFP, "Got premove: fromX %d,"
6819 "fromY %d, toX %d, toY %d\n",
6820 fromX, fromY, toX, toY);
6830 /* EditPosition, empty square, or different color piece;
6831 click-click move is possible */
6832 if (toX == -2 || toY == -2) {
6833 boards[0][fromY][fromX] = EmptySquare;
6834 DrawPosition(FALSE, boards[currentMove]);
6836 } else if (toX >= 0 && toY >= 0) {
6837 boards[0][toY][toX] = boards[0][fromY][fromX];
6838 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6839 if(boards[0][fromY][0] != EmptySquare) {
6840 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6841 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6844 if(fromX == BOARD_RGHT+1) {
6845 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6846 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6847 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6850 boards[0][fromY][fromX] = gatingPiece;
6851 DrawPosition(FALSE, boards[currentMove]);
6857 if(toX < 0 || toY < 0) return;
6858 pup = boards[currentMove][toY][toX];
6860 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6861 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6862 if( pup != EmptySquare ) return;
6863 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6864 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6865 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6866 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6867 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6868 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6869 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6873 /* [HGM] always test for legality, to get promotion info */
6874 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6875 fromY, fromX, toY, toX, promoChar);
6877 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6879 /* [HGM] but possibly ignore an IllegalMove result */
6880 if (appData.testLegality) {
6881 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6882 DisplayMoveError(_("Illegal move"));
6887 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6888 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6889 ClearPremoveHighlights(); // was included
6890 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6894 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6897 /* Common tail of UserMoveEvent and DropMenuEvent */
6899 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6903 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6904 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6905 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6906 if(WhiteOnMove(currentMove)) {
6907 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6909 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6913 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6914 move type in caller when we know the move is a legal promotion */
6915 if(moveType == NormalMove && promoChar)
6916 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6918 /* [HGM] <popupFix> The following if has been moved here from
6919 UserMoveEvent(). Because it seemed to belong here (why not allow
6920 piece drops in training games?), and because it can only be
6921 performed after it is known to what we promote. */
6922 if (gameMode == Training) {
6923 /* compare the move played on the board to the next move in the
6924 * game. If they match, display the move and the opponent's response.
6925 * If they don't match, display an error message.
6929 CopyBoard(testBoard, boards[currentMove]);
6930 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6932 if (CompareBoards(testBoard, boards[currentMove+1])) {
6933 ForwardInner(currentMove+1);
6935 /* Autoplay the opponent's response.
6936 * if appData.animate was TRUE when Training mode was entered,
6937 * the response will be animated.
6939 saveAnimate = appData.animate;
6940 appData.animate = animateTraining;
6941 ForwardInner(currentMove+1);
6942 appData.animate = saveAnimate;
6944 /* check for the end of the game */
6945 if (currentMove >= forwardMostMove) {
6946 gameMode = PlayFromGameFile;
6948 SetTrainingModeOff();
6949 DisplayInformation(_("End of game"));
6952 DisplayError(_("Incorrect move"), 0);
6957 /* Ok, now we know that the move is good, so we can kill
6958 the previous line in Analysis Mode */
6959 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6960 && currentMove < forwardMostMove) {
6961 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6962 else forwardMostMove = currentMove;
6967 /* If we need the chess program but it's dead, restart it */
6968 ResurrectChessProgram();
6970 /* A user move restarts a paused game*/
6974 thinkOutput[0] = NULLCHAR;
6976 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6978 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6979 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6983 if (gameMode == BeginningOfGame) {
6984 if (appData.noChessProgram) {
6985 gameMode = EditGame;
6989 gameMode = MachinePlaysBlack;
6992 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6994 if (first.sendName) {
6995 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6996 SendToProgram(buf, &first);
7003 /* Relay move to ICS or chess engine */
7004 if (appData.icsActive) {
7005 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7006 gameMode == IcsExamining) {
7007 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7008 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7010 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7012 // also send plain move, in case ICS does not understand atomic claims
7013 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7017 if (first.sendTime && (gameMode == BeginningOfGame ||
7018 gameMode == MachinePlaysWhite ||
7019 gameMode == MachinePlaysBlack)) {
7020 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7022 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7023 // [HGM] book: if program might be playing, let it use book
7024 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7025 first.maybeThinking = TRUE;
7026 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7027 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7028 SendBoard(&first, currentMove+1);
7029 if(second.analyzing) {
7030 if(!second.useSetboard) SendToProgram("undo\n", &second);
7031 SendBoard(&second, currentMove+1);
7034 SendMoveToProgram(forwardMostMove-1, &first);
7035 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7037 if (currentMove == cmailOldMove + 1) {
7038 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7042 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7046 if(appData.testLegality)
7047 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7053 if (WhiteOnMove(currentMove)) {
7054 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7056 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7060 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7065 case MachinePlaysBlack:
7066 case MachinePlaysWhite:
7067 /* disable certain menu options while machine is thinking */
7068 SetMachineThinkingEnables();
7075 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7076 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7078 if(bookHit) { // [HGM] book: simulate book reply
7079 static char bookMove[MSG_SIZ]; // a bit generous?
7081 programStats.nodes = programStats.depth = programStats.time =
7082 programStats.score = programStats.got_only_move = 0;
7083 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7085 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7086 strcat(bookMove, bookHit);
7087 HandleMachineMove(bookMove, &first);
7093 MarkByFEN(char *fen)
7096 if(!appData.markers || !appData.highlightDragging) return;
7097 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7098 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7102 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7103 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7104 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7105 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7106 if(*fen == 'T') marker[r][f++] = 0; else
7107 if(*fen == 'Y') marker[r][f++] = 1; else
7108 if(*fen == 'G') marker[r][f++] = 3; else
7109 if(*fen == 'B') marker[r][f++] = 4; else
7110 if(*fen == 'C') marker[r][f++] = 5; else
7111 if(*fen == 'M') marker[r][f++] = 6; else
7112 if(*fen == 'W') marker[r][f++] = 7; else
7113 if(*fen == 'D') marker[r][f++] = 8; else
7114 if(*fen == 'R') marker[r][f++] = 2; else {
7115 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7118 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7122 DrawPosition(TRUE, NULL);
7125 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7128 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7130 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7131 Markers *m = (Markers *) closure;
7132 if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7133 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7134 || kind == WhiteCapturesEnPassant
7135 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7136 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7140 MarkTargetSquares (int clear)
7143 if(clear) { // no reason to ever suppress clearing
7144 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = baseMarker[y][x] = 0;
7145 if(!sum) return; // nothing was cleared,no redraw needed
7148 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7149 !appData.testLegality || gameMode == EditPosition) return;
7150 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7151 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7152 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7154 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7157 DrawPosition(FALSE, NULL);
7161 Explode (Board board, int fromX, int fromY, int toX, int toY)
7163 if(gameInfo.variant == VariantAtomic &&
7164 (board[toY][toX] != EmptySquare || // capture?
7165 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7166 board[fromY][fromX] == BlackPawn )
7168 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7174 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7177 CanPromote (ChessSquare piece, int y)
7179 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7180 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7181 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7182 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7183 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7184 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7185 return (piece == BlackPawn && y == 1 ||
7186 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7187 piece == BlackLance && y == 1 ||
7188 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7192 HoverEvent (int xPix, int yPix, int x, int y)
7194 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7196 if(dragging == 2) DragPieceMove(xPix, yPix); // [HGM] lion: drag without button for second leg
7197 if(!first.highlight) return;
7198 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7199 if(x == oldX && y == oldY) return; // only do something if we enter new square
7200 oldFromX = fromX; oldFromY = fromY;
7201 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7202 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7203 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7204 else if(oldX != x || oldY != y) {
7205 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7206 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7207 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7208 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7210 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7211 SendToProgram(buf, &first);
7214 // SetHighlights(fromX, fromY, x, y);
7218 void ReportClick(char *action, int x, int y)
7220 char buf[MSG_SIZ]; // Inform engine of what user does
7222 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7223 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7224 if(!first.highlight || gameMode == EditPosition) return;
7225 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7226 SendToProgram(buf, &first);
7230 LeftClick (ClickType clickType, int xPix, int yPix)
7233 Boolean saveAnimate;
7234 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7235 char promoChoice = NULLCHAR;
7237 static TimeMark lastClickTime, prevClickTime;
7239 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7241 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7243 if (clickType == Press) ErrorPopDown();
7244 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7246 x = EventToSquare(xPix, BOARD_WIDTH);
7247 y = EventToSquare(yPix, BOARD_HEIGHT);
7248 if (!flipView && y >= 0) {
7249 y = BOARD_HEIGHT - 1 - y;
7251 if (flipView && x >= 0) {
7252 x = BOARD_WIDTH - 1 - x;
7255 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7256 defaultPromoChoice = promoSweep;
7257 promoSweep = EmptySquare; // terminate sweep
7258 promoDefaultAltered = TRUE;
7259 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7262 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7263 if(clickType == Release) return; // ignore upclick of click-click destination
7264 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7265 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7266 if(gameInfo.holdingsWidth &&
7267 (WhiteOnMove(currentMove)
7268 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7269 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7270 // click in right holdings, for determining promotion piece
7271 ChessSquare p = boards[currentMove][y][x];
7272 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7273 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7274 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7275 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7280 DrawPosition(FALSE, boards[currentMove]);
7284 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7285 if(clickType == Press
7286 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7287 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7288 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7291 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7292 // could be static click on premove from-square: abort premove
7294 ClearPremoveHighlights();
7297 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7298 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7300 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7301 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7302 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7303 defaultPromoChoice = DefaultPromoChoice(side);
7306 autoQueen = appData.alwaysPromoteToQueen;
7310 gatingPiece = EmptySquare;
7311 if (clickType != Press) {
7312 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7313 DragPieceEnd(xPix, yPix); dragging = 0;
7314 DrawPosition(FALSE, NULL);
7318 doubleClick = FALSE;
7319 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7320 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7322 fromX = x; fromY = y; toX = toY = killX = killY = -1;
7323 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7324 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7325 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7327 if (OKToStartUserMove(fromX, fromY)) {
7329 ReportClick("lift", x, y);
7330 MarkTargetSquares(0);
7331 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7332 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7333 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7334 promoSweep = defaultPromoChoice;
7335 selectFlag = 0; lastX = xPix; lastY = yPix;
7336 Sweep(0); // Pawn that is going to promote: preview promotion piece
7337 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7339 if (appData.highlightDragging) {
7340 SetHighlights(fromX, fromY, -1, -1);
7344 } else fromX = fromY = -1;
7350 if (clickType == Press && gameMode != EditPosition) {
7355 // ignore off-board to clicks
7356 if(y < 0 || x < 0) return;
7358 /* Check if clicking again on the same color piece */
7359 fromP = boards[currentMove][fromY][fromX];
7360 toP = boards[currentMove][y][x];
7361 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7362 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7363 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7364 WhitePawn <= toP && toP <= WhiteKing &&
7365 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7366 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7367 (BlackPawn <= fromP && fromP <= BlackKing &&
7368 BlackPawn <= toP && toP <= BlackKing &&
7369 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7370 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7371 /* Clicked again on same color piece -- changed his mind */
7372 second = (x == fromX && y == fromY);
7374 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7375 second = FALSE; // first double-click rather than scond click
7376 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7378 promoDefaultAltered = FALSE;
7379 MarkTargetSquares(1);
7380 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7381 if (appData.highlightDragging) {
7382 SetHighlights(x, y, -1, -1);
7386 if (OKToStartUserMove(x, y)) {
7387 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7388 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7389 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7390 gatingPiece = boards[currentMove][fromY][fromX];
7391 else gatingPiece = doubleClick ? fromP : EmptySquare;
7393 fromY = y; dragging = 1;
7394 ReportClick("lift", x, y);
7395 MarkTargetSquares(0);
7396 DragPieceBegin(xPix, yPix, FALSE);
7397 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7398 promoSweep = defaultPromoChoice;
7399 selectFlag = 0; lastX = xPix; lastY = yPix;
7400 Sweep(0); // Pawn that is going to promote: preview promotion piece
7404 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7407 // ignore clicks on holdings
7408 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7411 if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7412 DragPieceEnd(xPix, yPix); dragging = 0;
7414 // a deferred attempt to click-click move an empty square on top of a piece
7415 boards[currentMove][y][x] = EmptySquare;
7417 DrawPosition(FALSE, boards[currentMove]);
7418 fromX = fromY = -1; clearFlag = 0;
7421 if (appData.animateDragging) {
7422 /* Undo animation damage if any */
7423 DrawPosition(FALSE, NULL);
7425 if (second || sweepSelecting) {
7426 /* Second up/down in same square; just abort move */
7427 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7428 second = sweepSelecting = 0;
7430 gatingPiece = EmptySquare;
7431 MarkTargetSquares(1);
7434 ClearPremoveHighlights();
7436 /* First upclick in same square; start click-click mode */
7437 SetHighlights(x, y, -1, -1);
7444 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7445 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7446 DisplayMessage(_("only marked squares are legal"),"");
7447 DrawPosition(TRUE, NULL);
7448 return; // ignore to-click
7451 /* we now have a different from- and (possibly off-board) to-square */
7452 /* Completed move */
7453 if(!sweepSelecting) {
7458 saveAnimate = appData.animate;
7459 if (clickType == Press) {
7460 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7461 // must be Edit Position mode with empty-square selected
7462 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7463 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7466 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7470 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7471 killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7473 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7474 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7475 if(appData.sweepSelect) {
7476 ChessSquare piece = boards[currentMove][fromY][fromX];
7477 promoSweep = defaultPromoChoice;
7478 if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7479 selectFlag = 0; lastX = xPix; lastY = yPix;
7480 Sweep(0); // Pawn that is going to promote: preview promotion piece
7482 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7483 MarkTargetSquares(1);
7485 return; // promo popup appears on up-click
7487 /* Finish clickclick move */
7488 if (appData.animate || appData.highlightLastMove) {
7489 SetHighlights(fromX, fromY, toX, toY);
7493 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7495 if (appData.animate || appData.highlightLastMove) {
7496 SetHighlights(fromX, fromY, toX, toY);
7502 // [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
7503 /* Finish drag move */
7504 if (appData.highlightLastMove) {
7505 SetHighlights(fromX, fromY, toX, toY);
7510 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7511 dragging *= 2; // flag button-less dragging if we are dragging
7512 MarkTargetSquares(1);
7513 if(x == killX && y == killY) killX = killY = -1; else {
7514 killX = x; killY = y; //remeber this square as intermediate
7515 ReportClick("put", x, y); // and inform engine
7516 ReportClick("lift", x, y);
7517 MarkTargetSquares(0);
7521 DragPieceEnd(xPix, yPix); dragging = 0;
7522 /* Don't animate move and drag both */
7523 appData.animate = FALSE;
7526 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7527 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7528 ChessSquare piece = boards[currentMove][fromY][fromX];
7529 if(gameMode == EditPosition && piece != EmptySquare &&
7530 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7533 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7534 n = PieceToNumber(piece - (int)BlackPawn);
7535 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7536 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7537 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7539 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7540 n = PieceToNumber(piece);
7541 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7542 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7543 boards[currentMove][n][BOARD_WIDTH-2]++;
7545 boards[currentMove][fromY][fromX] = EmptySquare;
7549 MarkTargetSquares(1);
7550 DrawPosition(TRUE, boards[currentMove]);
7554 // off-board moves should not be highlighted
7555 if(x < 0 || y < 0) ClearHighlights();
7556 else ReportClick("put", x, y);
7558 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7560 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7561 SetHighlights(fromX, fromY, toX, toY);
7562 MarkTargetSquares(1);
7563 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7564 // [HGM] super: promotion to captured piece selected from holdings
7565 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7566 promotionChoice = TRUE;
7567 // kludge follows to temporarily execute move on display, without promoting yet
7568 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7569 boards[currentMove][toY][toX] = p;
7570 DrawPosition(FALSE, boards[currentMove]);
7571 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7572 boards[currentMove][toY][toX] = q;
7573 DisplayMessage("Click in holdings to choose piece", "");
7578 int oldMove = currentMove;
7579 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7580 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7581 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7582 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7583 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7584 DrawPosition(TRUE, boards[currentMove]);
7585 MarkTargetSquares(1);
7588 appData.animate = saveAnimate;
7589 if (appData.animate || appData.animateDragging) {
7590 /* Undo animation damage if needed */
7591 DrawPosition(FALSE, NULL);
7596 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7597 { // front-end-free part taken out of PieceMenuPopup
7598 int whichMenu; int xSqr, ySqr;
7600 if(seekGraphUp) { // [HGM] seekgraph
7601 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7602 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7606 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7607 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7608 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7609 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7610 if(action == Press) {
7611 originalFlip = flipView;
7612 flipView = !flipView; // temporarily flip board to see game from partners perspective
7613 DrawPosition(TRUE, partnerBoard);
7614 DisplayMessage(partnerStatus, "");
7616 } else if(action == Release) {
7617 flipView = originalFlip;
7618 DrawPosition(TRUE, boards[currentMove]);
7624 xSqr = EventToSquare(x, BOARD_WIDTH);
7625 ySqr = EventToSquare(y, BOARD_HEIGHT);
7626 if (action == Release) {
7627 if(pieceSweep != EmptySquare) {
7628 EditPositionMenuEvent(pieceSweep, toX, toY);
7629 pieceSweep = EmptySquare;
7630 } else UnLoadPV(); // [HGM] pv
7632 if (action != Press) return -2; // return code to be ignored
7635 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7637 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7638 if (xSqr < 0 || ySqr < 0) return -1;
7639 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7640 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7641 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7642 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7646 if(!appData.icsEngineAnalyze) return -1;
7647 case IcsPlayingWhite:
7648 case IcsPlayingBlack:
7649 if(!appData.zippyPlay) goto noZip;
7652 case MachinePlaysWhite:
7653 case MachinePlaysBlack:
7654 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7655 if (!appData.dropMenu) {
7657 return 2; // flag front-end to grab mouse events
7659 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7660 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7663 if (xSqr < 0 || ySqr < 0) return -1;
7664 if (!appData.dropMenu || appData.testLegality &&
7665 gameInfo.variant != VariantBughouse &&
7666 gameInfo.variant != VariantCrazyhouse) return -1;
7667 whichMenu = 1; // drop menu
7673 if (((*fromX = xSqr) < 0) ||
7674 ((*fromY = ySqr) < 0)) {
7675 *fromX = *fromY = -1;
7679 *fromX = BOARD_WIDTH - 1 - *fromX;
7681 *fromY = BOARD_HEIGHT - 1 - *fromY;
7687 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7689 // char * hint = lastHint;
7690 FrontEndProgramStats stats;
7692 stats.which = cps == &first ? 0 : 1;
7693 stats.depth = cpstats->depth;
7694 stats.nodes = cpstats->nodes;
7695 stats.score = cpstats->score;
7696 stats.time = cpstats->time;
7697 stats.pv = cpstats->movelist;
7698 stats.hint = lastHint;
7699 stats.an_move_index = 0;
7700 stats.an_move_count = 0;
7702 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7703 stats.hint = cpstats->move_name;
7704 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7705 stats.an_move_count = cpstats->nr_moves;
7708 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
7710 SetProgramStats( &stats );
7714 ClearEngineOutputPane (int which)
7716 static FrontEndProgramStats dummyStats;
7717 dummyStats.which = which;
7718 dummyStats.pv = "#";
7719 SetProgramStats( &dummyStats );
7722 #define MAXPLAYERS 500
7725 TourneyStandings (int display)
7727 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7728 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7729 char result, *p, *names[MAXPLAYERS];
7731 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7732 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7733 names[0] = p = strdup(appData.participants);
7734 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7736 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7738 while(result = appData.results[nr]) {
7739 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7740 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7741 wScore = bScore = 0;
7743 case '+': wScore = 2; break;
7744 case '-': bScore = 2; break;
7745 case '=': wScore = bScore = 1; break;
7747 case '*': return strdup("busy"); // tourney not finished
7755 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7756 for(w=0; w<nPlayers; w++) {
7758 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7759 ranking[w] = b; points[w] = bScore; score[b] = -2;
7761 p = malloc(nPlayers*34+1);
7762 for(w=0; w<nPlayers && w<display; w++)
7763 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7769 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7770 { // count all piece types
7772 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7773 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7774 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7777 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7778 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7779 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7780 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7781 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7782 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7787 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7789 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7790 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7792 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7793 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7794 if(myPawns == 2 && nMine == 3) // KPP
7795 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7796 if(myPawns == 1 && nMine == 2) // KP
7797 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7798 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7799 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7800 if(myPawns) return FALSE;
7801 if(pCnt[WhiteRook+side])
7802 return pCnt[BlackRook-side] ||
7803 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7804 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7805 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7806 if(pCnt[WhiteCannon+side]) {
7807 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7808 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7810 if(pCnt[WhiteKnight+side])
7811 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7816 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7818 VariantClass v = gameInfo.variant;
7820 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7821 if(v == VariantShatranj) return TRUE; // always winnable through baring
7822 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7823 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7825 if(v == VariantXiangqi) {
7826 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7828 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7829 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7830 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7831 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7832 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7833 if(stale) // we have at least one last-rank P plus perhaps C
7834 return majors // KPKX
7835 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7837 return pCnt[WhiteFerz+side] // KCAK
7838 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7839 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7840 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7842 } else if(v == VariantKnightmate) {
7843 if(nMine == 1) return FALSE;
7844 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7845 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7846 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7848 if(nMine == 1) return FALSE; // bare King
7849 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
7850 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7851 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7852 // by now we have King + 1 piece (or multiple Bishops on the same color)
7853 if(pCnt[WhiteKnight+side])
7854 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7855 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7856 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7858 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7859 if(pCnt[WhiteAlfil+side])
7860 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7861 if(pCnt[WhiteWazir+side])
7862 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7869 CompareWithRights (Board b1, Board b2)
7872 if(!CompareBoards(b1, b2)) return FALSE;
7873 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7874 /* compare castling rights */
7875 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7876 rights++; /* King lost rights, while rook still had them */
7877 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7878 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7879 rights++; /* but at least one rook lost them */
7881 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7883 if( b1[CASTLING][5] != NoRights ) {
7884 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7891 Adjudicate (ChessProgramState *cps)
7892 { // [HGM] some adjudications useful with buggy engines
7893 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7894 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7895 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7896 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7897 int k, drop, count = 0; static int bare = 1;
7898 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7899 Boolean canAdjudicate = !appData.icsActive;
7901 // most tests only when we understand the game, i.e. legality-checking on
7902 if( appData.testLegality )
7903 { /* [HGM] Some more adjudications for obstinate engines */
7904 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7905 static int moveCount = 6;
7907 char *reason = NULL;
7909 /* Count what is on board. */
7910 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7912 /* Some material-based adjudications that have to be made before stalemate test */
7913 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7914 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7915 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7916 if(canAdjudicate && appData.checkMates) {
7918 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7919 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7920 "Xboard adjudication: King destroyed", GE_XBOARD );
7925 /* Bare King in Shatranj (loses) or Losers (wins) */
7926 if( nrW == 1 || nrB == 1) {
7927 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7928 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7929 if(canAdjudicate && appData.checkMates) {
7931 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7932 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7933 "Xboard adjudication: Bare king", GE_XBOARD );
7937 if( gameInfo.variant == VariantShatranj && --bare < 0)
7939 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7940 if(canAdjudicate && appData.checkMates) {
7941 /* but only adjudicate if adjudication enabled */
7943 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7944 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7945 "Xboard adjudication: Bare king", GE_XBOARD );
7952 // don't wait for engine to announce game end if we can judge ourselves
7953 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7955 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7956 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7957 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7958 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7961 reason = "Xboard adjudication: 3rd check";
7962 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7972 reason = "Xboard adjudication: Stalemate";
7973 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7974 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7975 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7976 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7977 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7978 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7979 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7980 EP_CHECKMATE : EP_WINS);
7981 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7982 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7986 reason = "Xboard adjudication: Checkmate";
7987 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7988 if(gameInfo.variant == VariantShogi) {
7989 if(forwardMostMove > backwardMostMove
7990 && moveList[forwardMostMove-1][1] == '@'
7991 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7992 reason = "XBoard adjudication: pawn-drop mate";
7993 boards[forwardMostMove][EP_STATUS] = EP_WINS;
7999 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8001 result = GameIsDrawn; break;
8003 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8005 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8009 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8011 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8012 GameEnds( result, reason, GE_XBOARD );
8016 /* Next absolutely insufficient mating material. */
8017 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8018 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8019 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8021 /* always flag draws, for judging claims */
8022 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8024 if(canAdjudicate && appData.materialDraws) {
8025 /* but only adjudicate them if adjudication enabled */
8026 if(engineOpponent) {
8027 SendToProgram("force\n", engineOpponent); // suppress reply
8028 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8030 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8035 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8036 if(gameInfo.variant == VariantXiangqi ?
8037 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8039 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8040 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8041 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8042 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8044 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8045 { /* if the first 3 moves do not show a tactical win, declare draw */
8046 if(engineOpponent) {
8047 SendToProgram("force\n", engineOpponent); // suppress reply
8048 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8050 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8053 } else moveCount = 6;
8056 // Repetition draws and 50-move rule can be applied independently of legality testing
8058 /* Check for rep-draws */
8060 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8061 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8062 for(k = forwardMostMove-2;
8063 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8064 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8065 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8068 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8069 /* compare castling rights */
8070 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8071 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8072 rights++; /* King lost rights, while rook still had them */
8073 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8074 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8075 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8076 rights++; /* but at least one rook lost them */
8078 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8079 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8081 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8082 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8083 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8086 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8087 && appData.drawRepeats > 1) {
8088 /* adjudicate after user-specified nr of repeats */
8089 int result = GameIsDrawn;
8090 char *details = "XBoard adjudication: repetition draw";
8091 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8092 // [HGM] xiangqi: check for forbidden perpetuals
8093 int m, ourPerpetual = 1, hisPerpetual = 1;
8094 for(m=forwardMostMove; m>k; m-=2) {
8095 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8096 ourPerpetual = 0; // the current mover did not always check
8097 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8098 hisPerpetual = 0; // the opponent did not always check
8100 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8101 ourPerpetual, hisPerpetual);
8102 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8103 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8104 details = "Xboard adjudication: perpetual checking";
8106 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8107 break; // (or we would have caught him before). Abort repetition-checking loop.
8109 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8110 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8112 details = "Xboard adjudication: repetition";
8114 } else // it must be XQ
8115 // Now check for perpetual chases
8116 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8117 hisPerpetual = PerpetualChase(k, forwardMostMove);
8118 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8119 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8120 static char resdet[MSG_SIZ];
8121 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8123 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8125 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8126 break; // Abort repetition-checking loop.
8128 // if neither of us is checking or chasing all the time, or both are, it is draw
8130 if(engineOpponent) {
8131 SendToProgram("force\n", engineOpponent); // suppress reply
8132 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8134 GameEnds( result, details, GE_XBOARD );
8137 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8138 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8142 /* Now we test for 50-move draws. Determine ply count */
8143 count = forwardMostMove;
8144 /* look for last irreversble move */
8145 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8147 /* if we hit starting position, add initial plies */
8148 if( count == backwardMostMove )
8149 count -= initialRulePlies;
8150 count = forwardMostMove - count;
8151 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8152 // adjust reversible move counter for checks in Xiangqi
8153 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8154 if(i < backwardMostMove) i = backwardMostMove;
8155 while(i <= forwardMostMove) {
8156 lastCheck = inCheck; // check evasion does not count
8157 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8158 if(inCheck || lastCheck) count--; // check does not count
8163 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8164 /* this is used to judge if draw claims are legal */
8165 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8166 if(engineOpponent) {
8167 SendToProgram("force\n", engineOpponent); // suppress reply
8168 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8170 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8174 /* if draw offer is pending, treat it as a draw claim
8175 * when draw condition present, to allow engines a way to
8176 * claim draws before making their move to avoid a race
8177 * condition occurring after their move
8179 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8181 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8182 p = "Draw claim: 50-move rule";
8183 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8184 p = "Draw claim: 3-fold repetition";
8185 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8186 p = "Draw claim: insufficient mating material";
8187 if( p != NULL && canAdjudicate) {
8188 if(engineOpponent) {
8189 SendToProgram("force\n", engineOpponent); // suppress reply
8190 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8192 GameEnds( GameIsDrawn, p, GE_XBOARD );
8197 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8198 if(engineOpponent) {
8199 SendToProgram("force\n", engineOpponent); // suppress reply
8200 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8202 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8209 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8210 { // [HGM] book: this routine intercepts moves to simulate book replies
8211 char *bookHit = NULL;
8213 //first determine if the incoming move brings opponent into his book
8214 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8215 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8216 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8217 if(bookHit != NULL && !cps->bookSuspend) {
8218 // make sure opponent is not going to reply after receiving move to book position
8219 SendToProgram("force\n", cps);
8220 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8222 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8223 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8224 // now arrange restart after book miss
8226 // after a book hit we never send 'go', and the code after the call to this routine
8227 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8228 char buf[MSG_SIZ], *move = bookHit;
8230 int fromX, fromY, toX, toY;
8234 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8235 &fromX, &fromY, &toX, &toY, &promoChar)) {
8236 (void) CoordsToAlgebraic(boards[forwardMostMove],
8237 PosFlags(forwardMostMove),
8238 fromY, fromX, toY, toX, promoChar, move);
8240 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8244 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8245 SendToProgram(buf, cps);
8246 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8247 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8248 SendToProgram("go\n", cps);
8249 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8250 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8251 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8252 SendToProgram("go\n", cps);
8253 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8255 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8259 LoadError (char *errmess, ChessProgramState *cps)
8260 { // unloads engine and switches back to -ncp mode if it was first
8261 if(cps->initDone) return FALSE;
8262 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8263 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8266 appData.noChessProgram = TRUE;
8267 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8268 gameMode = BeginningOfGame; ModeHighlight();
8271 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8272 DisplayMessage("", ""); // erase waiting message
8273 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8278 ChessProgramState *savedState;
8280 DeferredBookMove (void)
8282 if(savedState->lastPing != savedState->lastPong)
8283 ScheduleDelayedEvent(DeferredBookMove, 10);
8285 HandleMachineMove(savedMessage, savedState);
8288 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8289 static ChessProgramState *stalledEngine;
8290 static char stashedInputMove[MSG_SIZ];
8293 HandleMachineMove (char *message, ChessProgramState *cps)
8295 static char firstLeg[20];
8296 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8297 char realname[MSG_SIZ];
8298 int fromX, fromY, toX, toY;
8300 char promoChar, roar;
8302 int machineWhite, oldError;
8305 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8306 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8307 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8308 DisplayError(_("Invalid pairing from pairing engine"), 0);
8311 pairingReceived = 1;
8313 return; // Skim the pairing messages here.
8316 oldError = cps->userError; cps->userError = 0;
8318 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8320 * Kludge to ignore BEL characters
8322 while (*message == '\007') message++;
8325 * [HGM] engine debug message: ignore lines starting with '#' character
8327 if(cps->debug && *message == '#') return;
8330 * Look for book output
8332 if (cps == &first && bookRequested) {
8333 if (message[0] == '\t' || message[0] == ' ') {
8334 /* Part of the book output is here; append it */
8335 strcat(bookOutput, message);
8336 strcat(bookOutput, " \n");
8338 } else if (bookOutput[0] != NULLCHAR) {
8339 /* All of book output has arrived; display it */
8340 char *p = bookOutput;
8341 while (*p != NULLCHAR) {
8342 if (*p == '\t') *p = ' ';
8345 DisplayInformation(bookOutput);
8346 bookRequested = FALSE;
8347 /* Fall through to parse the current output */
8352 * Look for machine move.
8354 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8355 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8357 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8358 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8359 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8360 stalledEngine = cps;
8361 if(appData.ponderNextMove) { // bring opponent out of ponder
8362 if(gameMode == TwoMachinesPlay) {
8363 if(cps->other->pause)
8364 PauseEngine(cps->other);
8366 SendToProgram("easy\n", cps->other);
8373 /* This method is only useful on engines that support ping */
8374 if (cps->lastPing != cps->lastPong) {
8375 if (gameMode == BeginningOfGame) {
8376 /* Extra move from before last new; ignore */
8377 if (appData.debugMode) {
8378 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8381 if (appData.debugMode) {
8382 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8383 cps->which, gameMode);
8386 SendToProgram("undo\n", cps);
8392 case BeginningOfGame:
8393 /* Extra move from before last reset; ignore */
8394 if (appData.debugMode) {
8395 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8402 /* Extra move after we tried to stop. The mode test is
8403 not a reliable way of detecting this problem, but it's
8404 the best we can do on engines that don't support ping.
8406 if (appData.debugMode) {
8407 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8408 cps->which, gameMode);
8410 SendToProgram("undo\n", cps);
8413 case MachinePlaysWhite:
8414 case IcsPlayingWhite:
8415 machineWhite = TRUE;
8418 case MachinePlaysBlack:
8419 case IcsPlayingBlack:
8420 machineWhite = FALSE;
8423 case TwoMachinesPlay:
8424 machineWhite = (cps->twoMachinesColor[0] == 'w');
8427 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8428 if (appData.debugMode) {
8430 "Ignoring move out of turn by %s, gameMode %d"
8431 ", forwardMost %d\n",
8432 cps->which, gameMode, forwardMostMove);
8437 if(cps->alphaRank) AlphaRank(machineMove, 4);
8439 // [HGM] lion: (some very limited) support for Alien protocol
8441 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8442 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8444 } else if(firstLeg[0]) { // there was a previous leg;
8445 // only support case where same piece makes two step (and don't even test that!)
8446 char buf[20], *p = machineMove+1, *q = buf+1, f;
8447 safeStrCpy(buf, machineMove, 20);
8448 while(isdigit(*q)) q++; // find start of to-square
8449 safeStrCpy(machineMove, firstLeg, 20);
8450 while(isdigit(*p)) p++;
8451 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8452 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8453 firstLeg[0] = NULLCHAR;
8456 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8457 &fromX, &fromY, &toX, &toY, &promoChar)) {
8458 /* Machine move could not be parsed; ignore it. */
8459 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8460 machineMove, _(cps->which));
8461 DisplayMoveError(buf1);
8462 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8463 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8464 if (gameMode == TwoMachinesPlay) {
8465 GameEnds(machineWhite ? BlackWins : WhiteWins,
8471 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8472 /* So we have to redo legality test with true e.p. status here, */
8473 /* to make sure an illegal e.p. capture does not slip through, */
8474 /* to cause a forfeit on a justified illegal-move complaint */
8475 /* of the opponent. */
8476 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8478 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8479 fromY, fromX, toY, toX, promoChar);
8480 if(moveType == IllegalMove) {
8481 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8482 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8483 GameEnds(machineWhite ? BlackWins : WhiteWins,
8486 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8487 /* [HGM] Kludge to handle engines that send FRC-style castling
8488 when they shouldn't (like TSCP-Gothic) */
8490 case WhiteASideCastleFR:
8491 case BlackASideCastleFR:
8493 currentMoveString[2]++;
8495 case WhiteHSideCastleFR:
8496 case BlackHSideCastleFR:
8498 currentMoveString[2]--;
8500 default: ; // nothing to do, but suppresses warning of pedantic compilers
8503 hintRequested = FALSE;
8504 lastHint[0] = NULLCHAR;
8505 bookRequested = FALSE;
8506 /* Program may be pondering now */
8507 cps->maybeThinking = TRUE;
8508 if (cps->sendTime == 2) cps->sendTime = 1;
8509 if (cps->offeredDraw) cps->offeredDraw--;
8511 /* [AS] Save move info*/
8512 pvInfoList[ forwardMostMove ].score = programStats.score;
8513 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8514 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8516 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8518 /* Test suites abort the 'game' after one move */
8519 if(*appData.finger) {
8521 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8522 if(!f) f = fopen(appData.finger, "w");
8523 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8524 else { DisplayFatalError("Bad output file", errno, 0); return; }
8526 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8529 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8530 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8533 while( count < adjudicateLossPlies ) {
8534 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8537 score = -score; /* Flip score for winning side */
8540 if( score > adjudicateLossThreshold ) {
8547 if( count >= adjudicateLossPlies ) {
8548 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8550 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8551 "Xboard adjudication",
8558 if(Adjudicate(cps)) {
8559 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8560 return; // [HGM] adjudicate: for all automatic game ends
8564 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8566 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8567 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8569 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8571 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8573 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8574 char buf[3*MSG_SIZ];
8576 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8577 programStats.score / 100.,
8579 programStats.time / 100.,
8580 (unsigned int)programStats.nodes,
8581 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8582 programStats.movelist);
8584 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8589 /* [AS] Clear stats for next move */
8590 ClearProgramStats();
8591 thinkOutput[0] = NULLCHAR;
8592 hiddenThinkOutputState = 0;
8595 if (gameMode == TwoMachinesPlay) {
8596 /* [HGM] relaying draw offers moved to after reception of move */
8597 /* and interpreting offer as claim if it brings draw condition */
8598 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8599 SendToProgram("draw\n", cps->other);
8601 if (cps->other->sendTime) {
8602 SendTimeRemaining(cps->other,
8603 cps->other->twoMachinesColor[0] == 'w');
8605 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8606 if (firstMove && !bookHit) {
8608 if (cps->other->useColors) {
8609 SendToProgram(cps->other->twoMachinesColor, cps->other);
8611 SendToProgram("go\n", cps->other);
8613 cps->other->maybeThinking = TRUE;
8616 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8618 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8620 if (!pausing && appData.ringBellAfterMoves) {
8621 if(!roar) RingBell();
8625 * Reenable menu items that were disabled while
8626 * machine was thinking
8628 if (gameMode != TwoMachinesPlay)
8629 SetUserThinkingEnables();
8631 // [HGM] book: after book hit opponent has received move and is now in force mode
8632 // force the book reply into it, and then fake that it outputted this move by jumping
8633 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8635 static char bookMove[MSG_SIZ]; // a bit generous?
8637 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8638 strcat(bookMove, bookHit);
8641 programStats.nodes = programStats.depth = programStats.time =
8642 programStats.score = programStats.got_only_move = 0;
8643 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8645 if(cps->lastPing != cps->lastPong) {
8646 savedMessage = message; // args for deferred call
8648 ScheduleDelayedEvent(DeferredBookMove, 10);
8657 /* Set special modes for chess engines. Later something general
8658 * could be added here; for now there is just one kludge feature,
8659 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8660 * when "xboard" is given as an interactive command.
8662 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8663 cps->useSigint = FALSE;
8664 cps->useSigterm = FALSE;
8666 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8667 ParseFeatures(message+8, cps);
8668 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8671 if (!strncmp(message, "setup ", 6) &&
8672 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8673 ) { // [HGM] allow first engine to define opening position
8674 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8675 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8677 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8678 if(startedFromSetupPosition) return;
8679 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8681 while(message[s] && message[s++] != ' ');
8682 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8683 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8684 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8685 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8686 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8687 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8690 ParseFEN(boards[0], &dummy, message+s, FALSE);
8691 DrawPosition(TRUE, boards[0]);
8692 startedFromSetupPosition = TRUE;
8695 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8696 * want this, I was asked to put it in, and obliged.
8698 if (!strncmp(message, "setboard ", 9)) {
8699 Board initial_position;
8701 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8703 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8704 DisplayError(_("Bad FEN received from engine"), 0);
8708 CopyBoard(boards[0], initial_position);
8709 initialRulePlies = FENrulePlies;
8710 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8711 else gameMode = MachinePlaysBlack;
8712 DrawPosition(FALSE, boards[currentMove]);
8718 * Look for communication commands
8720 if (!strncmp(message, "telluser ", 9)) {
8721 if(message[9] == '\\' && message[10] == '\\')
8722 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8724 DisplayNote(message + 9);
8727 if (!strncmp(message, "tellusererror ", 14)) {
8729 if(message[14] == '\\' && message[15] == '\\')
8730 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8732 DisplayError(message + 14, 0);
8735 if (!strncmp(message, "tellopponent ", 13)) {
8736 if (appData.icsActive) {
8738 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8742 DisplayNote(message + 13);
8746 if (!strncmp(message, "tellothers ", 11)) {
8747 if (appData.icsActive) {
8749 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8752 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8755 if (!strncmp(message, "tellall ", 8)) {
8756 if (appData.icsActive) {
8758 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8762 DisplayNote(message + 8);
8766 if (strncmp(message, "warning", 7) == 0) {
8767 /* Undocumented feature, use tellusererror in new code */
8768 DisplayError(message, 0);
8771 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8772 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8773 strcat(realname, " query");
8774 AskQuestion(realname, buf2, buf1, cps->pr);
8777 /* Commands from the engine directly to ICS. We don't allow these to be
8778 * sent until we are logged on. Crafty kibitzes have been known to
8779 * interfere with the login process.
8782 if (!strncmp(message, "tellics ", 8)) {
8783 SendToICS(message + 8);
8787 if (!strncmp(message, "tellicsnoalias ", 15)) {
8788 SendToICS(ics_prefix);
8789 SendToICS(message + 15);
8793 /* The following are for backward compatibility only */
8794 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8795 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8796 SendToICS(ics_prefix);
8802 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8805 if(!strncmp(message, "highlight ", 10)) {
8806 if(appData.testLegality && appData.markers) return;
8807 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8810 if(!strncmp(message, "click ", 6)) {
8811 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8812 if(appData.testLegality || !appData.oneClick) return;
8813 sscanf(message+6, "%c%d%c", &f, &y, &c);
8814 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8815 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8816 x = x*squareSize + (x+1)*lineGap + squareSize/2;
8817 y = y*squareSize + (y+1)*lineGap + squareSize/2;
8818 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8819 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8820 LeftClick(Release, lastLeftX, lastLeftY);
8821 controlKey = (c == ',');
8822 LeftClick(Press, x, y);
8823 LeftClick(Release, x, y);
8824 first.highlight = f;
8828 * If the move is illegal, cancel it and redraw the board.
8829 * Also deal with other error cases. Matching is rather loose
8830 * here to accommodate engines written before the spec.
8832 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8833 strncmp(message, "Error", 5) == 0) {
8834 if (StrStr(message, "name") ||
8835 StrStr(message, "rating") || StrStr(message, "?") ||
8836 StrStr(message, "result") || StrStr(message, "board") ||
8837 StrStr(message, "bk") || StrStr(message, "computer") ||
8838 StrStr(message, "variant") || StrStr(message, "hint") ||
8839 StrStr(message, "random") || StrStr(message, "depth") ||
8840 StrStr(message, "accepted")) {
8843 if (StrStr(message, "protover")) {
8844 /* Program is responding to input, so it's apparently done
8845 initializing, and this error message indicates it is
8846 protocol version 1. So we don't need to wait any longer
8847 for it to initialize and send feature commands. */
8848 FeatureDone(cps, 1);
8849 cps->protocolVersion = 1;
8852 cps->maybeThinking = FALSE;
8854 if (StrStr(message, "draw")) {
8855 /* Program doesn't have "draw" command */
8856 cps->sendDrawOffers = 0;
8859 if (cps->sendTime != 1 &&
8860 (StrStr(message, "time") || StrStr(message, "otim"))) {
8861 /* Program apparently doesn't have "time" or "otim" command */
8865 if (StrStr(message, "analyze")) {
8866 cps->analysisSupport = FALSE;
8867 cps->analyzing = FALSE;
8868 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8869 EditGameEvent(); // [HGM] try to preserve loaded game
8870 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8871 DisplayError(buf2, 0);
8874 if (StrStr(message, "(no matching move)st")) {
8875 /* Special kludge for GNU Chess 4 only */
8876 cps->stKludge = TRUE;
8877 SendTimeControl(cps, movesPerSession, timeControl,
8878 timeIncrement, appData.searchDepth,
8882 if (StrStr(message, "(no matching move)sd")) {
8883 /* Special kludge for GNU Chess 4 only */
8884 cps->sdKludge = TRUE;
8885 SendTimeControl(cps, movesPerSession, timeControl,
8886 timeIncrement, appData.searchDepth,
8890 if (!StrStr(message, "llegal")) {
8893 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8894 gameMode == IcsIdle) return;
8895 if (forwardMostMove <= backwardMostMove) return;
8896 if (pausing) PauseEvent();
8897 if(appData.forceIllegal) {
8898 // [HGM] illegal: machine refused move; force position after move into it
8899 SendToProgram("force\n", cps);
8900 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8901 // we have a real problem now, as SendBoard will use the a2a3 kludge
8902 // when black is to move, while there might be nothing on a2 or black
8903 // might already have the move. So send the board as if white has the move.
8904 // But first we must change the stm of the engine, as it refused the last move
8905 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8906 if(WhiteOnMove(forwardMostMove)) {
8907 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8908 SendBoard(cps, forwardMostMove); // kludgeless board
8910 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8911 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8912 SendBoard(cps, forwardMostMove+1); // kludgeless board
8914 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8915 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8916 gameMode == TwoMachinesPlay)
8917 SendToProgram("go\n", cps);
8920 if (gameMode == PlayFromGameFile) {
8921 /* Stop reading this game file */
8922 gameMode = EditGame;
8925 /* [HGM] illegal-move claim should forfeit game when Xboard */
8926 /* only passes fully legal moves */
8927 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8928 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8929 "False illegal-move claim", GE_XBOARD );
8930 return; // do not take back move we tested as valid
8932 currentMove = forwardMostMove-1;
8933 DisplayMove(currentMove-1); /* before DisplayMoveError */
8934 SwitchClocks(forwardMostMove-1); // [HGM] race
8935 DisplayBothClocks();
8936 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8937 parseList[currentMove], _(cps->which));
8938 DisplayMoveError(buf1);
8939 DrawPosition(FALSE, boards[currentMove]);
8941 SetUserThinkingEnables();
8944 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8945 /* Program has a broken "time" command that
8946 outputs a string not ending in newline.
8952 * If chess program startup fails, exit with an error message.
8953 * Attempts to recover here are futile. [HGM] Well, we try anyway
8955 if ((StrStr(message, "unknown host") != NULL)
8956 || (StrStr(message, "No remote directory") != NULL)
8957 || (StrStr(message, "not found") != NULL)
8958 || (StrStr(message, "No such file") != NULL)
8959 || (StrStr(message, "can't alloc") != NULL)
8960 || (StrStr(message, "Permission denied") != NULL)) {
8962 cps->maybeThinking = FALSE;
8963 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8964 _(cps->which), cps->program, cps->host, message);
8965 RemoveInputSource(cps->isr);
8966 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8967 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8968 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8974 * Look for hint output
8976 if (sscanf(message, "Hint: %s", buf1) == 1) {
8977 if (cps == &first && hintRequested) {
8978 hintRequested = FALSE;
8979 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8980 &fromX, &fromY, &toX, &toY, &promoChar)) {
8981 (void) CoordsToAlgebraic(boards[forwardMostMove],
8982 PosFlags(forwardMostMove),
8983 fromY, fromX, toY, toX, promoChar, buf1);
8984 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8985 DisplayInformation(buf2);
8987 /* Hint move could not be parsed!? */
8988 snprintf(buf2, sizeof(buf2),
8989 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8990 buf1, _(cps->which));
8991 DisplayError(buf2, 0);
8994 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9000 * Ignore other messages if game is not in progress
9002 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9003 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9006 * look for win, lose, draw, or draw offer
9008 if (strncmp(message, "1-0", 3) == 0) {
9009 char *p, *q, *r = "";
9010 p = strchr(message, '{');
9018 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9020 } else if (strncmp(message, "0-1", 3) == 0) {
9021 char *p, *q, *r = "";
9022 p = strchr(message, '{');
9030 /* Kludge for Arasan 4.1 bug */
9031 if (strcmp(r, "Black resigns") == 0) {
9032 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9035 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9037 } else if (strncmp(message, "1/2", 3) == 0) {
9038 char *p, *q, *r = "";
9039 p = strchr(message, '{');
9048 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9051 } else if (strncmp(message, "White resign", 12) == 0) {
9052 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9054 } else if (strncmp(message, "Black resign", 12) == 0) {
9055 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9057 } else if (strncmp(message, "White matches", 13) == 0 ||
9058 strncmp(message, "Black matches", 13) == 0 ) {
9059 /* [HGM] ignore GNUShogi noises */
9061 } else if (strncmp(message, "White", 5) == 0 &&
9062 message[5] != '(' &&
9063 StrStr(message, "Black") == NULL) {
9064 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9066 } else if (strncmp(message, "Black", 5) == 0 &&
9067 message[5] != '(') {
9068 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9070 } else if (strcmp(message, "resign") == 0 ||
9071 strcmp(message, "computer resigns") == 0) {
9073 case MachinePlaysBlack:
9074 case IcsPlayingBlack:
9075 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9077 case MachinePlaysWhite:
9078 case IcsPlayingWhite:
9079 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9081 case TwoMachinesPlay:
9082 if (cps->twoMachinesColor[0] == 'w')
9083 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9085 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9092 } else if (strncmp(message, "opponent mates", 14) == 0) {
9094 case MachinePlaysBlack:
9095 case IcsPlayingBlack:
9096 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9098 case MachinePlaysWhite:
9099 case IcsPlayingWhite:
9100 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9102 case TwoMachinesPlay:
9103 if (cps->twoMachinesColor[0] == 'w')
9104 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9106 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9113 } else if (strncmp(message, "computer mates", 14) == 0) {
9115 case MachinePlaysBlack:
9116 case IcsPlayingBlack:
9117 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9119 case MachinePlaysWhite:
9120 case IcsPlayingWhite:
9121 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9123 case TwoMachinesPlay:
9124 if (cps->twoMachinesColor[0] == 'w')
9125 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9127 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9134 } else if (strncmp(message, "checkmate", 9) == 0) {
9135 if (WhiteOnMove(forwardMostMove)) {
9136 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9138 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9141 } else if (strstr(message, "Draw") != NULL ||
9142 strstr(message, "game is a draw") != NULL) {
9143 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9145 } else if (strstr(message, "offer") != NULL &&
9146 strstr(message, "draw") != NULL) {
9148 if (appData.zippyPlay && first.initDone) {
9149 /* Relay offer to ICS */
9150 SendToICS(ics_prefix);
9151 SendToICS("draw\n");
9154 cps->offeredDraw = 2; /* valid until this engine moves twice */
9155 if (gameMode == TwoMachinesPlay) {
9156 if (cps->other->offeredDraw) {
9157 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9158 /* [HGM] in two-machine mode we delay relaying draw offer */
9159 /* until after we also have move, to see if it is really claim */
9161 } else if (gameMode == MachinePlaysWhite ||
9162 gameMode == MachinePlaysBlack) {
9163 if (userOfferedDraw) {
9164 DisplayInformation(_("Machine accepts your draw offer"));
9165 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9167 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9174 * Look for thinking output
9176 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9177 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9179 int plylev, mvleft, mvtot, curscore, time;
9180 char mvname[MOVE_LEN];
9184 int prefixHint = FALSE;
9185 mvname[0] = NULLCHAR;
9188 case MachinePlaysBlack:
9189 case IcsPlayingBlack:
9190 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9192 case MachinePlaysWhite:
9193 case IcsPlayingWhite:
9194 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9199 case IcsObserving: /* [DM] icsEngineAnalyze */
9200 if (!appData.icsEngineAnalyze) ignore = TRUE;
9202 case TwoMachinesPlay:
9203 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9213 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9215 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9216 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9218 if (plyext != ' ' && plyext != '\t') {
9222 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9223 if( cps->scoreIsAbsolute &&
9224 ( gameMode == MachinePlaysBlack ||
9225 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9226 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9227 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9228 !WhiteOnMove(currentMove)
9231 curscore = -curscore;
9234 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9236 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9239 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9240 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9241 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9242 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9243 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9244 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9248 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9249 DisplayError(_("failed writing PV"), 0);
9252 tempStats.depth = plylev;
9253 tempStats.nodes = nodes;
9254 tempStats.time = time;
9255 tempStats.score = curscore;
9256 tempStats.got_only_move = 0;
9258 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9261 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9262 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9263 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9264 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9265 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9266 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9267 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9268 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9271 /* Buffer overflow protection */
9272 if (pv[0] != NULLCHAR) {
9273 if (strlen(pv) >= sizeof(tempStats.movelist)
9274 && appData.debugMode) {
9276 "PV is too long; using the first %u bytes.\n",
9277 (unsigned) sizeof(tempStats.movelist) - 1);
9280 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9282 sprintf(tempStats.movelist, " no PV\n");
9285 if (tempStats.seen_stat) {
9286 tempStats.ok_to_send = 1;
9289 if (strchr(tempStats.movelist, '(') != NULL) {
9290 tempStats.line_is_book = 1;
9291 tempStats.nr_moves = 0;
9292 tempStats.moves_left = 0;
9294 tempStats.line_is_book = 0;
9297 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9298 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9300 SendProgramStatsToFrontend( cps, &tempStats );
9303 [AS] Protect the thinkOutput buffer from overflow... this
9304 is only useful if buf1 hasn't overflowed first!
9306 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9308 (gameMode == TwoMachinesPlay ?
9309 ToUpper(cps->twoMachinesColor[0]) : ' '),
9310 ((double) curscore) / 100.0,
9311 prefixHint ? lastHint : "",
9312 prefixHint ? " " : "" );
9314 if( buf1[0] != NULLCHAR ) {
9315 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9317 if( strlen(pv) > max_len ) {
9318 if( appData.debugMode) {
9319 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9321 pv[max_len+1] = '\0';
9324 strcat( thinkOutput, pv);
9327 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9328 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9329 DisplayMove(currentMove - 1);
9333 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9334 /* crafty (9.25+) says "(only move) <move>"
9335 * if there is only 1 legal move
9337 sscanf(p, "(only move) %s", buf1);
9338 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9339 sprintf(programStats.movelist, "%s (only move)", buf1);
9340 programStats.depth = 1;
9341 programStats.nr_moves = 1;
9342 programStats.moves_left = 1;
9343 programStats.nodes = 1;
9344 programStats.time = 1;
9345 programStats.got_only_move = 1;
9347 /* Not really, but we also use this member to
9348 mean "line isn't going to change" (Crafty
9349 isn't searching, so stats won't change) */
9350 programStats.line_is_book = 1;
9352 SendProgramStatsToFrontend( cps, &programStats );
9354 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9355 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9356 DisplayMove(currentMove - 1);
9359 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9360 &time, &nodes, &plylev, &mvleft,
9361 &mvtot, mvname) >= 5) {
9362 /* The stat01: line is from Crafty (9.29+) in response
9363 to the "." command */
9364 programStats.seen_stat = 1;
9365 cps->maybeThinking = TRUE;
9367 if (programStats.got_only_move || !appData.periodicUpdates)
9370 programStats.depth = plylev;
9371 programStats.time = time;
9372 programStats.nodes = nodes;
9373 programStats.moves_left = mvleft;
9374 programStats.nr_moves = mvtot;
9375 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9376 programStats.ok_to_send = 1;
9377 programStats.movelist[0] = '\0';
9379 SendProgramStatsToFrontend( cps, &programStats );
9383 } else if (strncmp(message,"++",2) == 0) {
9384 /* Crafty 9.29+ outputs this */
9385 programStats.got_fail = 2;
9388 } else if (strncmp(message,"--",2) == 0) {
9389 /* Crafty 9.29+ outputs this */
9390 programStats.got_fail = 1;
9393 } else if (thinkOutput[0] != NULLCHAR &&
9394 strncmp(message, " ", 4) == 0) {
9395 unsigned message_len;
9398 while (*p && *p == ' ') p++;
9400 message_len = strlen( p );
9402 /* [AS] Avoid buffer overflow */
9403 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9404 strcat(thinkOutput, " ");
9405 strcat(thinkOutput, p);
9408 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9409 strcat(programStats.movelist, " ");
9410 strcat(programStats.movelist, p);
9413 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9414 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9415 DisplayMove(currentMove - 1);
9423 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9424 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9426 ChessProgramStats cpstats;
9428 if (plyext != ' ' && plyext != '\t') {
9432 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9433 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9434 curscore = -curscore;
9437 cpstats.depth = plylev;
9438 cpstats.nodes = nodes;
9439 cpstats.time = time;
9440 cpstats.score = curscore;
9441 cpstats.got_only_move = 0;
9442 cpstats.movelist[0] = '\0';
9444 if (buf1[0] != NULLCHAR) {
9445 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9448 cpstats.ok_to_send = 0;
9449 cpstats.line_is_book = 0;
9450 cpstats.nr_moves = 0;
9451 cpstats.moves_left = 0;
9453 SendProgramStatsToFrontend( cps, &cpstats );
9460 /* Parse a game score from the character string "game", and
9461 record it as the history of the current game. The game
9462 score is NOT assumed to start from the standard position.
9463 The display is not updated in any way.
9466 ParseGameHistory (char *game)
9469 int fromX, fromY, toX, toY, boardIndex;
9474 if (appData.debugMode)
9475 fprintf(debugFP, "Parsing game history: %s\n", game);
9477 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9478 gameInfo.site = StrSave(appData.icsHost);
9479 gameInfo.date = PGNDate();
9480 gameInfo.round = StrSave("-");
9482 /* Parse out names of players */
9483 while (*game == ' ') game++;
9485 while (*game != ' ') *p++ = *game++;
9487 gameInfo.white = StrSave(buf);
9488 while (*game == ' ') game++;
9490 while (*game != ' ' && *game != '\n') *p++ = *game++;
9492 gameInfo.black = StrSave(buf);
9495 boardIndex = blackPlaysFirst ? 1 : 0;
9498 yyboardindex = boardIndex;
9499 moveType = (ChessMove) Myylex();
9501 case IllegalMove: /* maybe suicide chess, etc. */
9502 if (appData.debugMode) {
9503 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9504 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9505 setbuf(debugFP, NULL);
9507 case WhitePromotion:
9508 case BlackPromotion:
9509 case WhiteNonPromotion:
9510 case BlackNonPromotion:
9513 case WhiteCapturesEnPassant:
9514 case BlackCapturesEnPassant:
9515 case WhiteKingSideCastle:
9516 case WhiteQueenSideCastle:
9517 case BlackKingSideCastle:
9518 case BlackQueenSideCastle:
9519 case WhiteKingSideCastleWild:
9520 case WhiteQueenSideCastleWild:
9521 case BlackKingSideCastleWild:
9522 case BlackQueenSideCastleWild:
9524 case WhiteHSideCastleFR:
9525 case WhiteASideCastleFR:
9526 case BlackHSideCastleFR:
9527 case BlackASideCastleFR:
9529 fromX = currentMoveString[0] - AAA;
9530 fromY = currentMoveString[1] - ONE;
9531 toX = currentMoveString[2] - AAA;
9532 toY = currentMoveString[3] - ONE;
9533 promoChar = currentMoveString[4];
9537 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9538 fromX = moveType == WhiteDrop ?
9539 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9540 (int) CharToPiece(ToLower(currentMoveString[0]));
9542 toX = currentMoveString[2] - AAA;
9543 toY = currentMoveString[3] - ONE;
9544 promoChar = NULLCHAR;
9548 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9549 if (appData.debugMode) {
9550 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9551 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9552 setbuf(debugFP, NULL);
9554 DisplayError(buf, 0);
9556 case ImpossibleMove:
9558 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9559 if (appData.debugMode) {
9560 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9561 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9562 setbuf(debugFP, NULL);
9564 DisplayError(buf, 0);
9567 if (boardIndex < backwardMostMove) {
9568 /* Oops, gap. How did that happen? */
9569 DisplayError(_("Gap in move list"), 0);
9572 backwardMostMove = blackPlaysFirst ? 1 : 0;
9573 if (boardIndex > forwardMostMove) {
9574 forwardMostMove = boardIndex;
9578 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9579 strcat(parseList[boardIndex-1], " ");
9580 strcat(parseList[boardIndex-1], yy_text);
9592 case GameUnfinished:
9593 if (gameMode == IcsExamining) {
9594 if (boardIndex < backwardMostMove) {
9595 /* Oops, gap. How did that happen? */
9598 backwardMostMove = blackPlaysFirst ? 1 : 0;
9601 gameInfo.result = moveType;
9602 p = strchr(yy_text, '{');
9603 if (p == NULL) p = strchr(yy_text, '(');
9606 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9608 q = strchr(p, *p == '{' ? '}' : ')');
9609 if (q != NULL) *q = NULLCHAR;
9612 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9613 gameInfo.resultDetails = StrSave(p);
9616 if (boardIndex >= forwardMostMove &&
9617 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9618 backwardMostMove = blackPlaysFirst ? 1 : 0;
9621 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9622 fromY, fromX, toY, toX, promoChar,
9623 parseList[boardIndex]);
9624 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9625 /* currentMoveString is set as a side-effect of yylex */
9626 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9627 strcat(moveList[boardIndex], "\n");
9629 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9630 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9636 if(gameInfo.variant != VariantShogi)
9637 strcat(parseList[boardIndex - 1], "+");
9641 strcat(parseList[boardIndex - 1], "#");
9648 /* Apply a move to the given board */
9650 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9652 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9653 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9655 /* [HGM] compute & store e.p. status and castling rights for new position */
9656 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9658 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9659 oldEP = (signed char)board[EP_STATUS];
9660 board[EP_STATUS] = EP_NONE;
9662 if (fromY == DROP_RANK) {
9664 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9665 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9668 piece = board[toY][toX] = (ChessSquare) fromX;
9673 if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9674 victim = board[killY][killX],
9675 board[killY][killX] = EmptySquare,
9676 board[EP_STATUS] = EP_CAPTURE;
9678 if( board[toY][toX] != EmptySquare ) {
9679 board[EP_STATUS] = EP_CAPTURE;
9680 if( (fromX != toX || fromY != toY) && // not igui!
9681 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9682 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
9683 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9687 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9688 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9689 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9691 if( board[fromY][fromX] == WhitePawn ) {
9692 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9693 board[EP_STATUS] = EP_PAWN_MOVE;
9695 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9696 gameInfo.variant != VariantBerolina || toX < fromX)
9697 board[EP_STATUS] = toX | berolina;
9698 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9699 gameInfo.variant != VariantBerolina || toX > fromX)
9700 board[EP_STATUS] = toX;
9703 if( board[fromY][fromX] == BlackPawn ) {
9704 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9705 board[EP_STATUS] = EP_PAWN_MOVE;
9706 if( toY-fromY== -2) {
9707 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9708 gameInfo.variant != VariantBerolina || toX < fromX)
9709 board[EP_STATUS] = toX | berolina;
9710 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9711 gameInfo.variant != VariantBerolina || toX > fromX)
9712 board[EP_STATUS] = toX;
9716 for(i=0; i<nrCastlingRights; i++) {
9717 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9718 board[CASTLING][i] == toX && castlingRank[i] == toY
9719 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9722 if(gameInfo.variant == VariantSChess) { // update virginity
9723 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9724 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9725 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9726 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9729 if (fromX == toX && fromY == toY) return;
9731 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9732 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9733 if(gameInfo.variant == VariantKnightmate)
9734 king += (int) WhiteUnicorn - (int) WhiteKing;
9736 /* Code added by Tord: */
9737 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9738 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9739 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9740 board[fromY][fromX] = EmptySquare;
9741 board[toY][toX] = EmptySquare;
9742 if((toX > fromX) != (piece == WhiteRook)) {
9743 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9745 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9747 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9748 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9749 board[fromY][fromX] = EmptySquare;
9750 board[toY][toX] = EmptySquare;
9751 if((toX > fromX) != (piece == BlackRook)) {
9752 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9754 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9756 /* End of code added by Tord */
9758 } else if (board[fromY][fromX] == king
9759 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9760 && toY == fromY && toX > fromX+1) {
9761 board[fromY][fromX] = EmptySquare;
9762 board[toY][toX] = king;
9763 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9764 board[fromY][BOARD_RGHT-1] = EmptySquare;
9765 } else if (board[fromY][fromX] == king
9766 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9767 && toY == fromY && toX < fromX-1) {
9768 board[fromY][fromX] = EmptySquare;
9769 board[toY][toX] = king;
9770 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9771 board[fromY][BOARD_LEFT] = EmptySquare;
9772 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9773 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9774 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9776 /* white pawn promotion */
9777 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9778 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9779 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9780 board[fromY][fromX] = EmptySquare;
9781 } else if ((fromY >= BOARD_HEIGHT>>1)
9782 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9784 && gameInfo.variant != VariantXiangqi
9785 && gameInfo.variant != VariantBerolina
9786 && (board[fromY][fromX] == WhitePawn)
9787 && (board[toY][toX] == EmptySquare)) {
9788 board[fromY][fromX] = EmptySquare;
9789 board[toY][toX] = WhitePawn;
9790 captured = board[toY - 1][toX];
9791 board[toY - 1][toX] = EmptySquare;
9792 } else if ((fromY == BOARD_HEIGHT-4)
9794 && gameInfo.variant == VariantBerolina
9795 && (board[fromY][fromX] == WhitePawn)
9796 && (board[toY][toX] == EmptySquare)) {
9797 board[fromY][fromX] = EmptySquare;
9798 board[toY][toX] = WhitePawn;
9799 if(oldEP & EP_BEROLIN_A) {
9800 captured = board[fromY][fromX-1];
9801 board[fromY][fromX-1] = EmptySquare;
9802 }else{ captured = board[fromY][fromX+1];
9803 board[fromY][fromX+1] = EmptySquare;
9805 } else if (board[fromY][fromX] == king
9806 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9807 && toY == fromY && toX > fromX+1) {
9808 board[fromY][fromX] = EmptySquare;
9809 board[toY][toX] = king;
9810 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9811 board[fromY][BOARD_RGHT-1] = EmptySquare;
9812 } else if (board[fromY][fromX] == king
9813 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9814 && toY == fromY && toX < fromX-1) {
9815 board[fromY][fromX] = EmptySquare;
9816 board[toY][toX] = king;
9817 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9818 board[fromY][BOARD_LEFT] = EmptySquare;
9819 } else if (fromY == 7 && fromX == 3
9820 && board[fromY][fromX] == BlackKing
9821 && toY == 7 && toX == 5) {
9822 board[fromY][fromX] = EmptySquare;
9823 board[toY][toX] = BlackKing;
9824 board[fromY][7] = EmptySquare;
9825 board[toY][4] = BlackRook;
9826 } else if (fromY == 7 && fromX == 3
9827 && board[fromY][fromX] == BlackKing
9828 && toY == 7 && toX == 1) {
9829 board[fromY][fromX] = EmptySquare;
9830 board[toY][toX] = BlackKing;
9831 board[fromY][0] = EmptySquare;
9832 board[toY][2] = BlackRook;
9833 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9834 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9835 && toY < promoRank && promoChar
9837 /* black pawn promotion */
9838 board[toY][toX] = CharToPiece(ToLower(promoChar));
9839 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9840 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9841 board[fromY][fromX] = EmptySquare;
9842 } else if ((fromY < BOARD_HEIGHT>>1)
9843 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9845 && gameInfo.variant != VariantXiangqi
9846 && gameInfo.variant != VariantBerolina
9847 && (board[fromY][fromX] == BlackPawn)
9848 && (board[toY][toX] == EmptySquare)) {
9849 board[fromY][fromX] = EmptySquare;
9850 board[toY][toX] = BlackPawn;
9851 captured = board[toY + 1][toX];
9852 board[toY + 1][toX] = EmptySquare;
9853 } else if ((fromY == 3)
9855 && gameInfo.variant == VariantBerolina
9856 && (board[fromY][fromX] == BlackPawn)
9857 && (board[toY][toX] == EmptySquare)) {
9858 board[fromY][fromX] = EmptySquare;
9859 board[toY][toX] = BlackPawn;
9860 if(oldEP & EP_BEROLIN_A) {
9861 captured = board[fromY][fromX-1];
9862 board[fromY][fromX-1] = EmptySquare;
9863 }else{ captured = board[fromY][fromX+1];
9864 board[fromY][fromX+1] = EmptySquare;
9867 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9868 board[fromY][fromX] = EmptySquare;
9869 board[toY][toX] = piece;
9873 if (gameInfo.holdingsWidth != 0) {
9875 /* !!A lot more code needs to be written to support holdings */
9876 /* [HGM] OK, so I have written it. Holdings are stored in the */
9877 /* penultimate board files, so they are automaticlly stored */
9878 /* in the game history. */
9879 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9880 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9881 /* Delete from holdings, by decreasing count */
9882 /* and erasing image if necessary */
9883 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9884 if(p < (int) BlackPawn) { /* white drop */
9885 p -= (int)WhitePawn;
9886 p = PieceToNumber((ChessSquare)p);
9887 if(p >= gameInfo.holdingsSize) p = 0;
9888 if(--board[p][BOARD_WIDTH-2] <= 0)
9889 board[p][BOARD_WIDTH-1] = EmptySquare;
9890 if((int)board[p][BOARD_WIDTH-2] < 0)
9891 board[p][BOARD_WIDTH-2] = 0;
9892 } else { /* black drop */
9893 p -= (int)BlackPawn;
9894 p = PieceToNumber((ChessSquare)p);
9895 if(p >= gameInfo.holdingsSize) p = 0;
9896 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9897 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9898 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9899 board[BOARD_HEIGHT-1-p][1] = 0;
9902 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9903 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9904 /* [HGM] holdings: Add to holdings, if holdings exist */
9905 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9906 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9907 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9910 if (p >= (int) BlackPawn) {
9911 p -= (int)BlackPawn;
9912 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9913 /* in Shogi restore piece to its original first */
9914 captured = (ChessSquare) (DEMOTED captured);
9917 p = PieceToNumber((ChessSquare)p);
9918 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9919 board[p][BOARD_WIDTH-2]++;
9920 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9922 p -= (int)WhitePawn;
9923 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9924 captured = (ChessSquare) (DEMOTED captured);
9927 p = PieceToNumber((ChessSquare)p);
9928 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9929 board[BOARD_HEIGHT-1-p][1]++;
9930 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9933 } else if (gameInfo.variant == VariantAtomic) {
9934 if (captured != EmptySquare) {
9936 for (y = toY-1; y <= toY+1; y++) {
9937 for (x = toX-1; x <= toX+1; x++) {
9938 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9939 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9940 board[y][x] = EmptySquare;
9944 board[toY][toX] = EmptySquare;
9947 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9948 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9950 if(promoChar == '+') {
9951 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9952 board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9953 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9954 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9955 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9956 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9957 board[toY][toX] = newPiece;
9959 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9960 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9961 // [HGM] superchess: take promotion piece out of holdings
9962 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9963 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9964 if(!--board[k][BOARD_WIDTH-2])
9965 board[k][BOARD_WIDTH-1] = EmptySquare;
9967 if(!--board[BOARD_HEIGHT-1-k][1])
9968 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9973 /* Updates forwardMostMove */
9975 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9977 int x = toX, y = toY;
9978 char *s = parseList[forwardMostMove];
9979 ChessSquare p = boards[forwardMostMove][toY][toX];
9980 // forwardMostMove++; // [HGM] bare: moved downstream
9982 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
9983 (void) CoordsToAlgebraic(boards[forwardMostMove],
9984 PosFlags(forwardMostMove),
9985 fromY, fromX, y, x, promoChar,
9987 if(killX >= 0 && killY >= 0)
9988 sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
9990 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9991 int timeLeft; static int lastLoadFlag=0; int king, piece;
9992 piece = boards[forwardMostMove][fromY][fromX];
9993 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9994 if(gameInfo.variant == VariantKnightmate)
9995 king += (int) WhiteUnicorn - (int) WhiteKing;
9996 if(forwardMostMove == 0) {
9997 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9998 fprintf(serverMoves, "%s;", UserName());
9999 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10000 fprintf(serverMoves, "%s;", second.tidy);
10001 fprintf(serverMoves, "%s;", first.tidy);
10002 if(gameMode == MachinePlaysWhite)
10003 fprintf(serverMoves, "%s;", UserName());
10004 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10005 fprintf(serverMoves, "%s;", second.tidy);
10006 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10007 lastLoadFlag = loadFlag;
10009 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10010 // print castling suffix
10011 if( toY == fromY && piece == king ) {
10013 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10015 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10018 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10019 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10020 boards[forwardMostMove][toY][toX] == EmptySquare
10021 && fromX != toX && fromY != toY)
10022 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10023 // promotion suffix
10024 if(promoChar != NULLCHAR) {
10025 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10026 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10027 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10028 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10031 char buf[MOVE_LEN*2], *p; int len;
10032 fprintf(serverMoves, "/%d/%d",
10033 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10034 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10035 else timeLeft = blackTimeRemaining/1000;
10036 fprintf(serverMoves, "/%d", timeLeft);
10037 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10038 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10039 if(p = strchr(buf, '=')) *p = NULLCHAR;
10040 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10041 fprintf(serverMoves, "/%s", buf);
10043 fflush(serverMoves);
10046 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10047 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10050 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10051 if (commentList[forwardMostMove+1] != NULL) {
10052 free(commentList[forwardMostMove+1]);
10053 commentList[forwardMostMove+1] = NULL;
10055 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10056 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10057 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10058 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10059 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10060 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10061 adjustedClock = FALSE;
10062 gameInfo.result = GameUnfinished;
10063 if (gameInfo.resultDetails != NULL) {
10064 free(gameInfo.resultDetails);
10065 gameInfo.resultDetails = NULL;
10067 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10068 moveList[forwardMostMove - 1]);
10069 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10075 if(gameInfo.variant != VariantShogi)
10076 strcat(parseList[forwardMostMove - 1], "+");
10080 strcat(parseList[forwardMostMove - 1], "#");
10085 /* Updates currentMove if not pausing */
10087 ShowMove (int fromX, int fromY, int toX, int toY)
10089 int instant = (gameMode == PlayFromGameFile) ?
10090 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10091 if(appData.noGUI) return;
10092 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10094 if (forwardMostMove == currentMove + 1) {
10095 AnimateMove(boards[forwardMostMove - 1],
10096 fromX, fromY, toX, toY);
10099 currentMove = forwardMostMove;
10102 killX = killY = -1; // [HGM] lion: used up
10104 if (instant) return;
10106 DisplayMove(currentMove - 1);
10107 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10108 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10109 SetHighlights(fromX, fromY, toX, toY);
10112 DrawPosition(FALSE, boards[currentMove]);
10113 DisplayBothClocks();
10114 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10118 SendEgtPath (ChessProgramState *cps)
10119 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10120 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10122 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10125 char c, *q = name+1, *r, *s;
10127 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10128 while(*p && *p != ',') *q++ = *p++;
10129 *q++ = ':'; *q = 0;
10130 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10131 strcmp(name, ",nalimov:") == 0 ) {
10132 // take nalimov path from the menu-changeable option first, if it is defined
10133 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10134 SendToProgram(buf,cps); // send egtbpath command for nalimov
10136 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10137 (s = StrStr(appData.egtFormats, name)) != NULL) {
10138 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10139 s = r = StrStr(s, ":") + 1; // beginning of path info
10140 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10141 c = *r; *r = 0; // temporarily null-terminate path info
10142 *--q = 0; // strip of trailig ':' from name
10143 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10145 SendToProgram(buf,cps); // send egtbpath command for this format
10147 if(*p == ',') p++; // read away comma to position for next format name
10152 NonStandardBoardSize ()
10154 /* [HGM] Awkward testing. Should really be a table */
10155 int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10156 if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10157 if( gameInfo.variant == VariantXiangqi )
10158 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10159 if( gameInfo.variant == VariantShogi )
10160 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10161 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10162 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10163 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10164 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10165 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10166 if( gameInfo.variant == VariantCourier )
10167 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10168 if( gameInfo.variant == VariantSuper )
10169 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10170 if( gameInfo.variant == VariantGreat )
10171 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10172 if( gameInfo.variant == VariantSChess )
10173 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10174 if( gameInfo.variant == VariantGrand )
10175 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10176 if( gameInfo.variant == VariantChu )
10177 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
10182 InitChessProgram (ChessProgramState *cps, int setup)
10183 /* setup needed to setup FRC opening position */
10185 char buf[MSG_SIZ], b[MSG_SIZ];
10186 if (appData.noChessProgram) return;
10187 hintRequested = FALSE;
10188 bookRequested = FALSE;
10190 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10191 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10192 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10193 if(cps->memSize) { /* [HGM] memory */
10194 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10195 SendToProgram(buf, cps);
10197 SendEgtPath(cps); /* [HGM] EGT */
10198 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10199 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10200 SendToProgram(buf, cps);
10203 SendToProgram(cps->initString, cps);
10204 if (gameInfo.variant != VariantNormal &&
10205 gameInfo.variant != VariantLoadable
10206 /* [HGM] also send variant if board size non-standard */
10207 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10209 char *v = VariantName(gameInfo.variant);
10210 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10211 /* [HGM] in protocol 1 we have to assume all variants valid */
10212 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10213 DisplayFatalError(buf, 0, 1);
10217 if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10218 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10219 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10220 /* [HGM] varsize: try first if this defiant size variant is specifically known */
10221 if(StrStr(cps->variants, b) == NULL) {
10222 // specific sized variant not known, check if general sizing allowed
10223 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10224 if(StrStr(cps->variants, "boardsize") == NULL) {
10225 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10226 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10227 DisplayFatalError(buf, 0, 1);
10230 /* [HGM] here we really should compare with the maximum supported board size */
10233 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10234 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10235 SendToProgram(buf, cps);
10237 currentlyInitializedVariant = gameInfo.variant;
10239 /* [HGM] send opening position in FRC to first engine */
10241 SendToProgram("force\n", cps);
10243 /* engine is now in force mode! Set flag to wake it up after first move. */
10244 setboardSpoiledMachineBlack = 1;
10247 if (cps->sendICS) {
10248 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10249 SendToProgram(buf, cps);
10251 cps->maybeThinking = FALSE;
10252 cps->offeredDraw = 0;
10253 if (!appData.icsActive) {
10254 SendTimeControl(cps, movesPerSession, timeControl,
10255 timeIncrement, appData.searchDepth,
10258 if (appData.showThinking
10259 // [HGM] thinking: four options require thinking output to be sent
10260 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10262 SendToProgram("post\n", cps);
10264 SendToProgram("hard\n", cps);
10265 if (!appData.ponderNextMove) {
10266 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10267 it without being sure what state we are in first. "hard"
10268 is not a toggle, so that one is OK.
10270 SendToProgram("easy\n", cps);
10272 if (cps->usePing) {
10273 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10274 SendToProgram(buf, cps);
10276 cps->initDone = TRUE;
10277 ClearEngineOutputPane(cps == &second);
10282 ResendOptions (ChessProgramState *cps)
10283 { // send the stored value of the options
10286 Option *opt = cps->option;
10287 for(i=0; i<cps->nrOptions; i++, opt++) {
10288 switch(opt->type) {
10292 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10295 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10298 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10304 SendToProgram(buf, cps);
10309 StartChessProgram (ChessProgramState *cps)
10314 if (appData.noChessProgram) return;
10315 cps->initDone = FALSE;
10317 if (strcmp(cps->host, "localhost") == 0) {
10318 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10319 } else if (*appData.remoteShell == NULLCHAR) {
10320 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10322 if (*appData.remoteUser == NULLCHAR) {
10323 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10326 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10327 cps->host, appData.remoteUser, cps->program);
10329 err = StartChildProcess(buf, "", &cps->pr);
10333 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10334 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10335 if(cps != &first) return;
10336 appData.noChessProgram = TRUE;
10339 // DisplayFatalError(buf, err, 1);
10340 // cps->pr = NoProc;
10341 // cps->isr = NULL;
10345 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10346 if (cps->protocolVersion > 1) {
10347 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10348 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10349 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10350 cps->comboCnt = 0; // and values of combo boxes
10352 SendToProgram(buf, cps);
10353 if(cps->reload) ResendOptions(cps);
10355 SendToProgram("xboard\n", cps);
10360 TwoMachinesEventIfReady P((void))
10362 static int curMess = 0;
10363 if (first.lastPing != first.lastPong) {
10364 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10365 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10368 if (second.lastPing != second.lastPong) {
10369 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10370 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10373 DisplayMessage("", ""); curMess = 0;
10374 TwoMachinesEvent();
10378 MakeName (char *template)
10382 static char buf[MSG_SIZ];
10386 clock = time((time_t *)NULL);
10387 tm = localtime(&clock);
10389 while(*p++ = *template++) if(p[-1] == '%') {
10390 switch(*template++) {
10391 case 0: *p = 0; return buf;
10392 case 'Y': i = tm->tm_year+1900; break;
10393 case 'y': i = tm->tm_year-100; break;
10394 case 'M': i = tm->tm_mon+1; break;
10395 case 'd': i = tm->tm_mday; break;
10396 case 'h': i = tm->tm_hour; break;
10397 case 'm': i = tm->tm_min; break;
10398 case 's': i = tm->tm_sec; break;
10401 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10407 CountPlayers (char *p)
10410 while(p = strchr(p, '\n')) p++, n++; // count participants
10415 WriteTourneyFile (char *results, FILE *f)
10416 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10417 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10418 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10419 // create a file with tournament description
10420 fprintf(f, "-participants {%s}\n", appData.participants);
10421 fprintf(f, "-seedBase %d\n", appData.seedBase);
10422 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10423 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10424 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10425 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10426 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10427 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10428 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10429 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10430 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10431 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10432 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10433 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10434 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10435 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10436 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10437 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10438 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10439 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10440 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10441 fprintf(f, "-smpCores %d\n", appData.smpCores);
10443 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10445 fprintf(f, "-mps %d\n", appData.movesPerSession);
10446 fprintf(f, "-tc %s\n", appData.timeControl);
10447 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10449 fprintf(f, "-results \"%s\"\n", results);
10454 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10457 Substitute (char *participants, int expunge)
10459 int i, changed, changes=0, nPlayers=0;
10460 char *p, *q, *r, buf[MSG_SIZ];
10461 if(participants == NULL) return;
10462 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10463 r = p = participants; q = appData.participants;
10464 while(*p && *p == *q) {
10465 if(*p == '\n') r = p+1, nPlayers++;
10468 if(*p) { // difference
10469 while(*p && *p++ != '\n');
10470 while(*q && *q++ != '\n');
10471 changed = nPlayers;
10472 changes = 1 + (strcmp(p, q) != 0);
10474 if(changes == 1) { // a single engine mnemonic was changed
10475 q = r; while(*q) nPlayers += (*q++ == '\n');
10476 p = buf; while(*r && (*p = *r++) != '\n') p++;
10478 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10479 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10480 if(mnemonic[i]) { // The substitute is valid
10482 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10483 flock(fileno(f), LOCK_EX);
10484 ParseArgsFromFile(f);
10485 fseek(f, 0, SEEK_SET);
10486 FREE(appData.participants); appData.participants = participants;
10487 if(expunge) { // erase results of replaced engine
10488 int len = strlen(appData.results), w, b, dummy;
10489 for(i=0; i<len; i++) {
10490 Pairing(i, nPlayers, &w, &b, &dummy);
10491 if((w == changed || b == changed) && appData.results[i] == '*') {
10492 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10497 for(i=0; i<len; i++) {
10498 Pairing(i, nPlayers, &w, &b, &dummy);
10499 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10502 WriteTourneyFile(appData.results, f);
10503 fclose(f); // release lock
10506 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10508 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10509 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10510 free(participants);
10515 CheckPlayers (char *participants)
10518 char buf[MSG_SIZ], *p;
10519 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10520 while(p = strchr(participants, '\n')) {
10522 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10524 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10526 DisplayError(buf, 0);
10530 participants = p + 1;
10536 CreateTourney (char *name)
10539 if(matchMode && strcmp(name, appData.tourneyFile)) {
10540 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10542 if(name[0] == NULLCHAR) {
10543 if(appData.participants[0])
10544 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10547 f = fopen(name, "r");
10548 if(f) { // file exists
10549 ASSIGN(appData.tourneyFile, name);
10550 ParseArgsFromFile(f); // parse it
10552 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10553 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10554 DisplayError(_("Not enough participants"), 0);
10557 if(CheckPlayers(appData.participants)) return 0;
10558 ASSIGN(appData.tourneyFile, name);
10559 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10560 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10563 appData.noChessProgram = FALSE;
10564 appData.clockMode = TRUE;
10570 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10572 char buf[MSG_SIZ], *p, *q;
10573 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10574 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10575 skip = !all && group[0]; // if group requested, we start in skip mode
10576 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10577 p = names; q = buf; header = 0;
10578 while(*p && *p != '\n') *q++ = *p++;
10580 if(*p == '\n') p++;
10581 if(buf[0] == '#') {
10582 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10583 depth++; // we must be entering a new group
10584 if(all) continue; // suppress printing group headers when complete list requested
10586 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10588 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10589 if(engineList[i]) free(engineList[i]);
10590 engineList[i] = strdup(buf);
10591 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10592 if(engineMnemonic[i]) free(engineMnemonic[i]);
10593 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10595 sscanf(q + 8, "%s", buf + strlen(buf));
10598 engineMnemonic[i] = strdup(buf);
10601 engineList[i] = engineMnemonic[i] = NULL;
10605 // following implemented as macro to avoid type limitations
10606 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10609 SwapEngines (int n)
10610 { // swap settings for first engine and other engine (so far only some selected options)
10615 SWAP(chessProgram, p)
10617 SWAP(hasOwnBookUCI, h)
10618 SWAP(protocolVersion, h)
10620 SWAP(scoreIsAbsolute, h)
10625 SWAP(engOptions, p)
10626 SWAP(engInitString, p)
10627 SWAP(computerString, p)
10629 SWAP(fenOverride, p)
10631 SWAP(accumulateTC, h)
10636 GetEngineLine (char *s, int n)
10640 extern char *icsNames;
10641 if(!s || !*s) return 0;
10642 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10643 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10644 if(!mnemonic[i]) return 0;
10645 if(n == 11) return 1; // just testing if there was a match
10646 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10647 if(n == 1) SwapEngines(n);
10648 ParseArgsFromString(buf);
10649 if(n == 1) SwapEngines(n);
10650 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10651 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10652 ParseArgsFromString(buf);
10658 SetPlayer (int player, char *p)
10659 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10661 char buf[MSG_SIZ], *engineName;
10662 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10663 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10664 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10666 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10667 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10668 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10669 ParseArgsFromString(buf);
10670 } else { // no engine with this nickname is installed!
10671 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10672 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10673 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10675 DisplayError(buf, 0);
10682 char *recentEngines;
10685 RecentEngineEvent (int nr)
10688 // SwapEngines(1); // bump first to second
10689 // ReplaceEngine(&second, 1); // and load it there
10690 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10691 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10692 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10693 ReplaceEngine(&first, 0);
10694 FloatToFront(&appData.recentEngineList, command[n]);
10699 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10700 { // determine players from game number
10701 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10703 if(appData.tourneyType == 0) {
10704 roundsPerCycle = (nPlayers - 1) | 1;
10705 pairingsPerRound = nPlayers / 2;
10706 } else if(appData.tourneyType > 0) {
10707 roundsPerCycle = nPlayers - appData.tourneyType;
10708 pairingsPerRound = appData.tourneyType;
10710 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10711 gamesPerCycle = gamesPerRound * roundsPerCycle;
10712 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10713 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10714 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10715 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10716 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10717 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10719 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10720 if(appData.roundSync) *syncInterval = gamesPerRound;
10722 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10724 if(appData.tourneyType == 0) {
10725 if(curPairing == (nPlayers-1)/2 ) {
10726 *whitePlayer = curRound;
10727 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10729 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10730 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10731 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10732 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10734 } else if(appData.tourneyType > 1) {
10735 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10736 *whitePlayer = curRound + appData.tourneyType;
10737 } else if(appData.tourneyType > 0) {
10738 *whitePlayer = curPairing;
10739 *blackPlayer = curRound + appData.tourneyType;
10742 // take care of white/black alternation per round.
10743 // For cycles and games this is already taken care of by default, derived from matchGame!
10744 return curRound & 1;
10748 NextTourneyGame (int nr, int *swapColors)
10749 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10751 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10753 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10754 tf = fopen(appData.tourneyFile, "r");
10755 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10756 ParseArgsFromFile(tf); fclose(tf);
10757 InitTimeControls(); // TC might be altered from tourney file
10759 nPlayers = CountPlayers(appData.participants); // count participants
10760 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10761 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10764 p = q = appData.results;
10765 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10766 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10767 DisplayMessage(_("Waiting for other game(s)"),"");
10768 waitingForGame = TRUE;
10769 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10772 waitingForGame = FALSE;
10775 if(appData.tourneyType < 0) {
10776 if(nr>=0 && !pairingReceived) {
10778 if(pairing.pr == NoProc) {
10779 if(!appData.pairingEngine[0]) {
10780 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10783 StartChessProgram(&pairing); // starts the pairing engine
10785 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10786 SendToProgram(buf, &pairing);
10787 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10788 SendToProgram(buf, &pairing);
10789 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10791 pairingReceived = 0; // ... so we continue here
10793 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10794 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10795 matchGame = 1; roundNr = nr / syncInterval + 1;
10798 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10800 // redefine engines, engine dir, etc.
10801 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10802 if(first.pr == NoProc) {
10803 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10804 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10806 if(second.pr == NoProc) {
10808 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10809 SwapEngines(1); // and make that valid for second engine by swapping
10810 InitEngine(&second, 1);
10812 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10813 UpdateLogos(FALSE); // leave display to ModeHiglight()
10819 { // performs game initialization that does not invoke engines, and then tries to start the game
10820 int res, firstWhite, swapColors = 0;
10821 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10822 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
10824 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10825 if(strcmp(buf, currentDebugFile)) { // name has changed
10826 FILE *f = fopen(buf, "w");
10827 if(f) { // if opening the new file failed, just keep using the old one
10828 ASSIGN(currentDebugFile, buf);
10832 if(appData.serverFileName) {
10833 if(serverFP) fclose(serverFP);
10834 serverFP = fopen(appData.serverFileName, "w");
10835 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10836 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10840 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10841 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10842 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10843 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10844 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10845 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10846 Reset(FALSE, first.pr != NoProc);
10847 res = LoadGameOrPosition(matchGame); // setup game
10848 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10849 if(!res) return; // abort when bad game/pos file
10850 TwoMachinesEvent();
10854 UserAdjudicationEvent (int result)
10856 ChessMove gameResult = GameIsDrawn;
10859 gameResult = WhiteWins;
10861 else if( result < 0 ) {
10862 gameResult = BlackWins;
10865 if( gameMode == TwoMachinesPlay ) {
10866 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10871 // [HGM] save: calculate checksum of game to make games easily identifiable
10873 StringCheckSum (char *s)
10876 if(s==NULL) return 0;
10877 while(*s) i = i*259 + *s++;
10885 for(i=backwardMostMove; i<forwardMostMove; i++) {
10886 sum += pvInfoList[i].depth;
10887 sum += StringCheckSum(parseList[i]);
10888 sum += StringCheckSum(commentList[i]);
10891 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10892 return sum + StringCheckSum(commentList[i]);
10893 } // end of save patch
10896 GameEnds (ChessMove result, char *resultDetails, int whosays)
10898 GameMode nextGameMode;
10900 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10902 if(endingGame) return; /* [HGM] crash: forbid recursion */
10904 if(twoBoards) { // [HGM] dual: switch back to one board
10905 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10906 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10908 if (appData.debugMode) {
10909 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10910 result, resultDetails ? resultDetails : "(null)", whosays);
10913 fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10915 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10917 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10918 /* If we are playing on ICS, the server decides when the
10919 game is over, but the engine can offer to draw, claim
10923 if (appData.zippyPlay && first.initDone) {
10924 if (result == GameIsDrawn) {
10925 /* In case draw still needs to be claimed */
10926 SendToICS(ics_prefix);
10927 SendToICS("draw\n");
10928 } else if (StrCaseStr(resultDetails, "resign")) {
10929 SendToICS(ics_prefix);
10930 SendToICS("resign\n");
10934 endingGame = 0; /* [HGM] crash */
10938 /* If we're loading the game from a file, stop */
10939 if (whosays == GE_FILE) {
10940 (void) StopLoadGameTimer();
10944 /* Cancel draw offers */
10945 first.offeredDraw = second.offeredDraw = 0;
10947 /* If this is an ICS game, only ICS can really say it's done;
10948 if not, anyone can. */
10949 isIcsGame = (gameMode == IcsPlayingWhite ||
10950 gameMode == IcsPlayingBlack ||
10951 gameMode == IcsObserving ||
10952 gameMode == IcsExamining);
10954 if (!isIcsGame || whosays == GE_ICS) {
10955 /* OK -- not an ICS game, or ICS said it was done */
10957 if (!isIcsGame && !appData.noChessProgram)
10958 SetUserThinkingEnables();
10960 /* [HGM] if a machine claims the game end we verify this claim */
10961 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10962 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10964 ChessMove trueResult = (ChessMove) -1;
10966 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10967 first.twoMachinesColor[0] :
10968 second.twoMachinesColor[0] ;
10970 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10971 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10972 /* [HGM] verify: engine mate claims accepted if they were flagged */
10973 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10975 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10976 /* [HGM] verify: engine mate claims accepted if they were flagged */
10977 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10979 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10980 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10983 // now verify win claims, but not in drop games, as we don't understand those yet
10984 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10985 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10986 (result == WhiteWins && claimer == 'w' ||
10987 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10988 if (appData.debugMode) {
10989 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10990 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10992 if(result != trueResult) {
10993 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10994 result = claimer == 'w' ? BlackWins : WhiteWins;
10995 resultDetails = buf;
10998 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10999 && (forwardMostMove <= backwardMostMove ||
11000 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11001 (claimer=='b')==(forwardMostMove&1))
11003 /* [HGM] verify: draws that were not flagged are false claims */
11004 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11005 result = claimer == 'w' ? BlackWins : WhiteWins;
11006 resultDetails = buf;
11008 /* (Claiming a loss is accepted no questions asked!) */
11009 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11010 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11011 result = GameUnfinished;
11012 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11014 /* [HGM] bare: don't allow bare King to win */
11015 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11016 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11017 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11018 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11019 && result != GameIsDrawn)
11020 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11021 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11022 int p = (signed char)boards[forwardMostMove][i][j] - color;
11023 if(p >= 0 && p <= (int)WhiteKing) k++;
11025 if (appData.debugMode) {
11026 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11027 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11030 result = GameIsDrawn;
11031 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11032 resultDetails = buf;
11038 if(serverMoves != NULL && !loadFlag) { char c = '=';
11039 if(result==WhiteWins) c = '+';
11040 if(result==BlackWins) c = '-';
11041 if(resultDetails != NULL)
11042 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11044 if (resultDetails != NULL) {
11045 gameInfo.result = result;
11046 gameInfo.resultDetails = StrSave(resultDetails);
11048 /* display last move only if game was not loaded from file */
11049 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11050 DisplayMove(currentMove - 1);
11052 if (forwardMostMove != 0) {
11053 if (gameMode != PlayFromGameFile && gameMode != EditGame
11054 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11056 if (*appData.saveGameFile != NULLCHAR) {
11057 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11058 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11060 SaveGameToFile(appData.saveGameFile, TRUE);
11061 } else if (appData.autoSaveGames) {
11062 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11064 if (*appData.savePositionFile != NULLCHAR) {
11065 SavePositionToFile(appData.savePositionFile);
11067 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11071 /* Tell program how game ended in case it is learning */
11072 /* [HGM] Moved this to after saving the PGN, just in case */
11073 /* engine died and we got here through time loss. In that */
11074 /* case we will get a fatal error writing the pipe, which */
11075 /* would otherwise lose us the PGN. */
11076 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11077 /* output during GameEnds should never be fatal anymore */
11078 if (gameMode == MachinePlaysWhite ||
11079 gameMode == MachinePlaysBlack ||
11080 gameMode == TwoMachinesPlay ||
11081 gameMode == IcsPlayingWhite ||
11082 gameMode == IcsPlayingBlack ||
11083 gameMode == BeginningOfGame) {
11085 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11087 if (first.pr != NoProc) {
11088 SendToProgram(buf, &first);
11090 if (second.pr != NoProc &&
11091 gameMode == TwoMachinesPlay) {
11092 SendToProgram(buf, &second);
11097 if (appData.icsActive) {
11098 if (appData.quietPlay &&
11099 (gameMode == IcsPlayingWhite ||
11100 gameMode == IcsPlayingBlack)) {
11101 SendToICS(ics_prefix);
11102 SendToICS("set shout 1\n");
11104 nextGameMode = IcsIdle;
11105 ics_user_moved = FALSE;
11106 /* clean up premove. It's ugly when the game has ended and the
11107 * premove highlights are still on the board.
11110 gotPremove = FALSE;
11111 ClearPremoveHighlights();
11112 DrawPosition(FALSE, boards[currentMove]);
11114 if (whosays == GE_ICS) {
11117 if (gameMode == IcsPlayingWhite)
11119 else if(gameMode == IcsPlayingBlack)
11120 PlayIcsLossSound();
11123 if (gameMode == IcsPlayingBlack)
11125 else if(gameMode == IcsPlayingWhite)
11126 PlayIcsLossSound();
11129 PlayIcsDrawSound();
11132 PlayIcsUnfinishedSound();
11135 if(appData.quitNext) { ExitEvent(0); return; }
11136 } else if (gameMode == EditGame ||
11137 gameMode == PlayFromGameFile ||
11138 gameMode == AnalyzeMode ||
11139 gameMode == AnalyzeFile) {
11140 nextGameMode = gameMode;
11142 nextGameMode = EndOfGame;
11147 nextGameMode = gameMode;
11150 if (appData.noChessProgram) {
11151 gameMode = nextGameMode;
11153 endingGame = 0; /* [HGM] crash */
11158 /* Put first chess program into idle state */
11159 if (first.pr != NoProc &&
11160 (gameMode == MachinePlaysWhite ||
11161 gameMode == MachinePlaysBlack ||
11162 gameMode == TwoMachinesPlay ||
11163 gameMode == IcsPlayingWhite ||
11164 gameMode == IcsPlayingBlack ||
11165 gameMode == BeginningOfGame)) {
11166 SendToProgram("force\n", &first);
11167 if (first.usePing) {
11169 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11170 SendToProgram(buf, &first);
11173 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11174 /* Kill off first chess program */
11175 if (first.isr != NULL)
11176 RemoveInputSource(first.isr);
11179 if (first.pr != NoProc) {
11181 DoSleep( appData.delayBeforeQuit );
11182 SendToProgram("quit\n", &first);
11183 DoSleep( appData.delayAfterQuit );
11184 DestroyChildProcess(first.pr, first.useSigterm);
11185 first.reload = TRUE;
11189 if (second.reuse) {
11190 /* Put second chess program into idle state */
11191 if (second.pr != NoProc &&
11192 gameMode == TwoMachinesPlay) {
11193 SendToProgram("force\n", &second);
11194 if (second.usePing) {
11196 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11197 SendToProgram(buf, &second);
11200 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11201 /* Kill off second chess program */
11202 if (second.isr != NULL)
11203 RemoveInputSource(second.isr);
11206 if (second.pr != NoProc) {
11207 DoSleep( appData.delayBeforeQuit );
11208 SendToProgram("quit\n", &second);
11209 DoSleep( appData.delayAfterQuit );
11210 DestroyChildProcess(second.pr, second.useSigterm);
11211 second.reload = TRUE;
11213 second.pr = NoProc;
11216 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11217 char resChar = '=';
11221 if (first.twoMachinesColor[0] == 'w') {
11224 second.matchWins++;
11229 if (first.twoMachinesColor[0] == 'b') {
11232 second.matchWins++;
11235 case GameUnfinished:
11241 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11242 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11243 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11244 ReserveGame(nextGame, resChar); // sets nextGame
11245 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11246 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11247 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11249 if (nextGame <= appData.matchGames && !abortMatch) {
11250 gameMode = nextGameMode;
11251 matchGame = nextGame; // this will be overruled in tourney mode!
11252 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11253 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11254 endingGame = 0; /* [HGM] crash */
11257 gameMode = nextGameMode;
11258 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11259 first.tidy, second.tidy,
11260 first.matchWins, second.matchWins,
11261 appData.matchGames - (first.matchWins + second.matchWins));
11262 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11263 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11264 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11265 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11266 first.twoMachinesColor = "black\n";
11267 second.twoMachinesColor = "white\n";
11269 first.twoMachinesColor = "white\n";
11270 second.twoMachinesColor = "black\n";
11274 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11275 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11277 gameMode = nextGameMode;
11279 endingGame = 0; /* [HGM] crash */
11280 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11281 if(matchMode == TRUE) { // match through command line: exit with or without popup
11283 ToNrEvent(forwardMostMove);
11284 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11286 } else DisplayFatalError(buf, 0, 0);
11287 } else { // match through menu; just stop, with or without popup
11288 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11291 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11292 } else DisplayNote(buf);
11294 if(ranking) free(ranking);
11298 /* Assumes program was just initialized (initString sent).
11299 Leaves program in force mode. */
11301 FeedMovesToProgram (ChessProgramState *cps, int upto)
11305 if (appData.debugMode)
11306 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11307 startedFromSetupPosition ? "position and " : "",
11308 backwardMostMove, upto, cps->which);
11309 if(currentlyInitializedVariant != gameInfo.variant) {
11311 // [HGM] variantswitch: make engine aware of new variant
11312 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11313 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11314 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11315 SendToProgram(buf, cps);
11316 currentlyInitializedVariant = gameInfo.variant;
11318 SendToProgram("force\n", cps);
11319 if (startedFromSetupPosition) {
11320 SendBoard(cps, backwardMostMove);
11321 if (appData.debugMode) {
11322 fprintf(debugFP, "feedMoves\n");
11325 for (i = backwardMostMove; i < upto; i++) {
11326 SendMoveToProgram(i, cps);
11332 ResurrectChessProgram ()
11334 /* The chess program may have exited.
11335 If so, restart it and feed it all the moves made so far. */
11336 static int doInit = 0;
11338 if (appData.noChessProgram) return 1;
11340 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11341 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11342 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11343 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11345 if (first.pr != NoProc) return 1;
11346 StartChessProgram(&first);
11348 InitChessProgram(&first, FALSE);
11349 FeedMovesToProgram(&first, currentMove);
11351 if (!first.sendTime) {
11352 /* can't tell gnuchess what its clock should read,
11353 so we bow to its notion. */
11355 timeRemaining[0][currentMove] = whiteTimeRemaining;
11356 timeRemaining[1][currentMove] = blackTimeRemaining;
11359 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11360 appData.icsEngineAnalyze) && first.analysisSupport) {
11361 SendToProgram("analyze\n", &first);
11362 first.analyzing = TRUE;
11368 * Button procedures
11371 Reset (int redraw, int init)
11375 if (appData.debugMode) {
11376 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11377 redraw, init, gameMode);
11379 CleanupTail(); // [HGM] vari: delete any stored variations
11380 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11381 pausing = pauseExamInvalid = FALSE;
11382 startedFromSetupPosition = blackPlaysFirst = FALSE;
11384 whiteFlag = blackFlag = FALSE;
11385 userOfferedDraw = FALSE;
11386 hintRequested = bookRequested = FALSE;
11387 first.maybeThinking = FALSE;
11388 second.maybeThinking = FALSE;
11389 first.bookSuspend = FALSE; // [HGM] book
11390 second.bookSuspend = FALSE;
11391 thinkOutput[0] = NULLCHAR;
11392 lastHint[0] = NULLCHAR;
11393 ClearGameInfo(&gameInfo);
11394 gameInfo.variant = StringToVariant(appData.variant);
11395 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11396 ics_user_moved = ics_clock_paused = FALSE;
11397 ics_getting_history = H_FALSE;
11399 white_holding[0] = black_holding[0] = NULLCHAR;
11400 ClearProgramStats();
11401 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11405 flipView = appData.flipView;
11406 ClearPremoveHighlights();
11407 gotPremove = FALSE;
11408 alarmSounded = FALSE;
11409 killX = killY = -1; // [HGM] lion
11411 GameEnds(EndOfFile, NULL, GE_PLAYER);
11412 if(appData.serverMovesName != NULL) {
11413 /* [HGM] prepare to make moves file for broadcasting */
11414 clock_t t = clock();
11415 if(serverMoves != NULL) fclose(serverMoves);
11416 serverMoves = fopen(appData.serverMovesName, "r");
11417 if(serverMoves != NULL) {
11418 fclose(serverMoves);
11419 /* delay 15 sec before overwriting, so all clients can see end */
11420 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11422 serverMoves = fopen(appData.serverMovesName, "w");
11426 gameMode = BeginningOfGame;
11428 if(appData.icsActive) gameInfo.variant = VariantNormal;
11429 currentMove = forwardMostMove = backwardMostMove = 0;
11430 MarkTargetSquares(1);
11431 InitPosition(redraw);
11432 for (i = 0; i < MAX_MOVES; i++) {
11433 if (commentList[i] != NULL) {
11434 free(commentList[i]);
11435 commentList[i] = NULL;
11439 timeRemaining[0][0] = whiteTimeRemaining;
11440 timeRemaining[1][0] = blackTimeRemaining;
11442 if (first.pr == NoProc) {
11443 StartChessProgram(&first);
11446 InitChessProgram(&first, startedFromSetupPosition);
11449 DisplayMessage("", "");
11450 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11451 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11452 ClearMap(); // [HGM] exclude: invalidate map
11456 AutoPlayGameLoop ()
11459 if (!AutoPlayOneMove())
11461 if (matchMode || appData.timeDelay == 0)
11463 if (appData.timeDelay < 0)
11465 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11473 ReloadGame(1); // next game
11479 int fromX, fromY, toX, toY;
11481 if (appData.debugMode) {
11482 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11485 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11488 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11489 pvInfoList[currentMove].depth = programStats.depth;
11490 pvInfoList[currentMove].score = programStats.score;
11491 pvInfoList[currentMove].time = 0;
11492 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11493 else { // append analysis of final position as comment
11495 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11496 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11498 programStats.depth = 0;
11501 if (currentMove >= forwardMostMove) {
11502 if(gameMode == AnalyzeFile) {
11503 if(appData.loadGameIndex == -1) {
11504 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11505 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11507 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11510 // gameMode = EndOfGame;
11511 // ModeHighlight();
11513 /* [AS] Clear current move marker at the end of a game */
11514 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11519 toX = moveList[currentMove][2] - AAA;
11520 toY = moveList[currentMove][3] - ONE;
11522 if (moveList[currentMove][1] == '@') {
11523 if (appData.highlightLastMove) {
11524 SetHighlights(-1, -1, toX, toY);
11527 fromX = moveList[currentMove][0] - AAA;
11528 fromY = moveList[currentMove][1] - ONE;
11530 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11532 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11534 if (appData.highlightLastMove) {
11535 SetHighlights(fromX, fromY, toX, toY);
11538 DisplayMove(currentMove);
11539 SendMoveToProgram(currentMove++, &first);
11540 DisplayBothClocks();
11541 DrawPosition(FALSE, boards[currentMove]);
11542 // [HGM] PV info: always display, routine tests if empty
11543 DisplayComment(currentMove - 1, commentList[currentMove]);
11549 LoadGameOneMove (ChessMove readAhead)
11551 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11552 char promoChar = NULLCHAR;
11553 ChessMove moveType;
11554 char move[MSG_SIZ];
11557 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11558 gameMode != AnalyzeMode && gameMode != Training) {
11563 yyboardindex = forwardMostMove;
11564 if (readAhead != EndOfFile) {
11565 moveType = readAhead;
11567 if (gameFileFP == NULL)
11569 moveType = (ChessMove) Myylex();
11573 switch (moveType) {
11575 if (appData.debugMode)
11576 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11579 /* append the comment but don't display it */
11580 AppendComment(currentMove, p, FALSE);
11583 case WhiteCapturesEnPassant:
11584 case BlackCapturesEnPassant:
11585 case WhitePromotion:
11586 case BlackPromotion:
11587 case WhiteNonPromotion:
11588 case BlackNonPromotion:
11591 case WhiteKingSideCastle:
11592 case WhiteQueenSideCastle:
11593 case BlackKingSideCastle:
11594 case BlackQueenSideCastle:
11595 case WhiteKingSideCastleWild:
11596 case WhiteQueenSideCastleWild:
11597 case BlackKingSideCastleWild:
11598 case BlackQueenSideCastleWild:
11600 case WhiteHSideCastleFR:
11601 case WhiteASideCastleFR:
11602 case BlackHSideCastleFR:
11603 case BlackASideCastleFR:
11605 if (appData.debugMode)
11606 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11607 fromX = currentMoveString[0] - AAA;
11608 fromY = currentMoveString[1] - ONE;
11609 toX = currentMoveString[2] - AAA;
11610 toY = currentMoveString[3] - ONE;
11611 promoChar = currentMoveString[4];
11612 if(promoChar == ';') promoChar = NULLCHAR;
11617 if (appData.debugMode)
11618 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11619 fromX = moveType == WhiteDrop ?
11620 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11621 (int) CharToPiece(ToLower(currentMoveString[0]));
11623 toX = currentMoveString[2] - AAA;
11624 toY = currentMoveString[3] - ONE;
11630 case GameUnfinished:
11631 if (appData.debugMode)
11632 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11633 p = strchr(yy_text, '{');
11634 if (p == NULL) p = strchr(yy_text, '(');
11637 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11639 q = strchr(p, *p == '{' ? '}' : ')');
11640 if (q != NULL) *q = NULLCHAR;
11643 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11644 GameEnds(moveType, p, GE_FILE);
11646 if (cmailMsgLoaded) {
11648 flipView = WhiteOnMove(currentMove);
11649 if (moveType == GameUnfinished) flipView = !flipView;
11650 if (appData.debugMode)
11651 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11656 if (appData.debugMode)
11657 fprintf(debugFP, "Parser hit end of file\n");
11658 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11664 if (WhiteOnMove(currentMove)) {
11665 GameEnds(BlackWins, "Black mates", GE_FILE);
11667 GameEnds(WhiteWins, "White mates", GE_FILE);
11671 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11677 case MoveNumberOne:
11678 if (lastLoadGameStart == GNUChessGame) {
11679 /* GNUChessGames have numbers, but they aren't move numbers */
11680 if (appData.debugMode)
11681 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11682 yy_text, (int) moveType);
11683 return LoadGameOneMove(EndOfFile); /* tail recursion */
11685 /* else fall thru */
11690 /* Reached start of next game in file */
11691 if (appData.debugMode)
11692 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11693 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11699 if (WhiteOnMove(currentMove)) {
11700 GameEnds(BlackWins, "Black mates", GE_FILE);
11702 GameEnds(WhiteWins, "White mates", GE_FILE);
11706 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11712 case PositionDiagram: /* should not happen; ignore */
11713 case ElapsedTime: /* ignore */
11714 case NAG: /* ignore */
11715 if (appData.debugMode)
11716 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11717 yy_text, (int) moveType);
11718 return LoadGameOneMove(EndOfFile); /* tail recursion */
11721 if (appData.testLegality) {
11722 if (appData.debugMode)
11723 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11724 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11725 (forwardMostMove / 2) + 1,
11726 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11727 DisplayError(move, 0);
11730 if (appData.debugMode)
11731 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11732 yy_text, currentMoveString);
11733 fromX = currentMoveString[0] - AAA;
11734 fromY = currentMoveString[1] - ONE;
11735 toX = currentMoveString[2] - AAA;
11736 toY = currentMoveString[3] - ONE;
11737 promoChar = currentMoveString[4];
11741 case AmbiguousMove:
11742 if (appData.debugMode)
11743 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11744 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11745 (forwardMostMove / 2) + 1,
11746 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11747 DisplayError(move, 0);
11752 case ImpossibleMove:
11753 if (appData.debugMode)
11754 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11755 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11756 (forwardMostMove / 2) + 1,
11757 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11758 DisplayError(move, 0);
11764 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11765 DrawPosition(FALSE, boards[currentMove]);
11766 DisplayBothClocks();
11767 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11768 DisplayComment(currentMove - 1, commentList[currentMove]);
11770 (void) StopLoadGameTimer();
11772 cmailOldMove = forwardMostMove;
11775 /* currentMoveString is set as a side-effect of yylex */
11777 thinkOutput[0] = NULLCHAR;
11778 MakeMove(fromX, fromY, toX, toY, promoChar);
11779 killX = killY = -1; // [HGM] lion: used up
11780 currentMove = forwardMostMove;
11785 /* Load the nth game from the given file */
11787 LoadGameFromFile (char *filename, int n, char *title, int useList)
11792 if (strcmp(filename, "-") == 0) {
11796 f = fopen(filename, "rb");
11798 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11799 DisplayError(buf, errno);
11803 if (fseek(f, 0, 0) == -1) {
11804 /* f is not seekable; probably a pipe */
11807 if (useList && n == 0) {
11808 int error = GameListBuild(f);
11810 DisplayError(_("Cannot build game list"), error);
11811 } else if (!ListEmpty(&gameList) &&
11812 ((ListGame *) gameList.tailPred)->number > 1) {
11813 GameListPopUp(f, title);
11820 return LoadGame(f, n, title, FALSE);
11825 MakeRegisteredMove ()
11827 int fromX, fromY, toX, toY;
11829 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11830 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11833 if (appData.debugMode)
11834 fprintf(debugFP, "Restoring %s for game %d\n",
11835 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11837 thinkOutput[0] = NULLCHAR;
11838 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11839 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11840 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11841 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11842 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11843 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11844 MakeMove(fromX, fromY, toX, toY, promoChar);
11845 ShowMove(fromX, fromY, toX, toY);
11847 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11854 if (WhiteOnMove(currentMove)) {
11855 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11857 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11862 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11869 if (WhiteOnMove(currentMove)) {
11870 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11872 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11877 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11888 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11890 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11894 if (gameNumber > nCmailGames) {
11895 DisplayError(_("No more games in this message"), 0);
11898 if (f == lastLoadGameFP) {
11899 int offset = gameNumber - lastLoadGameNumber;
11901 cmailMsg[0] = NULLCHAR;
11902 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11903 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11904 nCmailMovesRegistered--;
11906 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11907 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11908 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11911 if (! RegisterMove()) return FALSE;
11915 retVal = LoadGame(f, gameNumber, title, useList);
11917 /* Make move registered during previous look at this game, if any */
11918 MakeRegisteredMove();
11920 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11921 commentList[currentMove]
11922 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11923 DisplayComment(currentMove - 1, commentList[currentMove]);
11929 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11931 ReloadGame (int offset)
11933 int gameNumber = lastLoadGameNumber + offset;
11934 if (lastLoadGameFP == NULL) {
11935 DisplayError(_("No game has been loaded yet"), 0);
11938 if (gameNumber <= 0) {
11939 DisplayError(_("Can't back up any further"), 0);
11942 if (cmailMsgLoaded) {
11943 return CmailLoadGame(lastLoadGameFP, gameNumber,
11944 lastLoadGameTitle, lastLoadGameUseList);
11946 return LoadGame(lastLoadGameFP, gameNumber,
11947 lastLoadGameTitle, lastLoadGameUseList);
11951 int keys[EmptySquare+1];
11954 PositionMatches (Board b1, Board b2)
11957 switch(appData.searchMode) {
11958 case 1: return CompareWithRights(b1, b2);
11960 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11961 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11965 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11966 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11967 sum += keys[b1[r][f]] - keys[b2[r][f]];
11971 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11972 sum += keys[b1[r][f]] - keys[b2[r][f]];
11984 int pieceList[256], quickBoard[256];
11985 ChessSquare pieceType[256] = { EmptySquare };
11986 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11987 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11988 int soughtTotal, turn;
11989 Boolean epOK, flipSearch;
11992 unsigned char piece, to;
11995 #define DSIZE (250000)
11997 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11998 Move *moveDatabase = initialSpace;
11999 unsigned int movePtr, dataSize = DSIZE;
12002 MakePieceList (Board board, int *counts)
12004 int r, f, n=Q_PROMO, total=0;
12005 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12006 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12007 int sq = f + (r<<4);
12008 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12009 quickBoard[sq] = ++n;
12011 pieceType[n] = board[r][f];
12012 counts[board[r][f]]++;
12013 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12014 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12018 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12023 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12025 int sq = fromX + (fromY<<4);
12026 int piece = quickBoard[sq];
12027 quickBoard[sq] = 0;
12028 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12029 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12030 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12031 moveDatabase[movePtr++].piece = Q_WCASTL;
12032 quickBoard[sq] = piece;
12033 piece = quickBoard[from]; quickBoard[from] = 0;
12034 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12036 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12037 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12038 moveDatabase[movePtr++].piece = Q_BCASTL;
12039 quickBoard[sq] = piece;
12040 piece = quickBoard[from]; quickBoard[from] = 0;
12041 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12043 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12044 quickBoard[(fromY<<4)+toX] = 0;
12045 moveDatabase[movePtr].piece = Q_EP;
12046 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12047 moveDatabase[movePtr].to = sq;
12049 if(promoPiece != pieceType[piece]) {
12050 moveDatabase[movePtr++].piece = Q_PROMO;
12051 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12053 moveDatabase[movePtr].piece = piece;
12054 quickBoard[sq] = piece;
12059 PackGame (Board board)
12061 Move *newSpace = NULL;
12062 moveDatabase[movePtr].piece = 0; // terminate previous game
12063 if(movePtr > dataSize) {
12064 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12065 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12066 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12069 Move *p = moveDatabase, *q = newSpace;
12070 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12071 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12072 moveDatabase = newSpace;
12073 } else { // calloc failed, we must be out of memory. Too bad...
12074 dataSize = 0; // prevent calloc events for all subsequent games
12075 return 0; // and signal this one isn't cached
12079 MakePieceList(board, counts);
12084 QuickCompare (Board board, int *minCounts, int *maxCounts)
12085 { // compare according to search mode
12087 switch(appData.searchMode)
12089 case 1: // exact position match
12090 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12091 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12092 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12095 case 2: // can have extra material on empty squares
12096 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12097 if(board[r][f] == EmptySquare) continue;
12098 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12101 case 3: // material with exact Pawn structure
12102 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12103 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12104 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12105 } // fall through to material comparison
12106 case 4: // exact material
12107 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12109 case 6: // material range with given imbalance
12110 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12111 // fall through to range comparison
12112 case 5: // material range
12113 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12119 QuickScan (Board board, Move *move)
12120 { // reconstruct game,and compare all positions in it
12121 int cnt=0, stretch=0, total = MakePieceList(board, counts);
12123 int piece = move->piece;
12124 int to = move->to, from = pieceList[piece];
12125 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12126 if(!piece) return -1;
12127 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12128 piece = (++move)->piece;
12129 from = pieceList[piece];
12130 counts[pieceType[piece]]--;
12131 pieceType[piece] = (ChessSquare) move->to;
12132 counts[move->to]++;
12133 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12134 counts[pieceType[quickBoard[to]]]--;
12135 quickBoard[to] = 0; total--;
12138 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12139 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12140 from = pieceList[piece]; // so this must be King
12141 quickBoard[from] = 0;
12142 pieceList[piece] = to;
12143 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12144 quickBoard[from] = 0; // rook
12145 quickBoard[to] = piece;
12146 to = move->to; piece = move->piece;
12150 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12151 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12152 quickBoard[from] = 0;
12154 quickBoard[to] = piece;
12155 pieceList[piece] = to;
12157 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12158 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12159 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12160 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12162 static int lastCounts[EmptySquare+1];
12164 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12165 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12166 } else stretch = 0;
12167 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12176 flipSearch = FALSE;
12177 CopyBoard(soughtBoard, boards[currentMove]);
12178 soughtTotal = MakePieceList(soughtBoard, maxSought);
12179 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12180 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12181 CopyBoard(reverseBoard, boards[currentMove]);
12182 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12183 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12184 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12185 reverseBoard[r][f] = piece;
12187 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12188 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12189 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12190 || (boards[currentMove][CASTLING][2] == NoRights ||
12191 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12192 && (boards[currentMove][CASTLING][5] == NoRights ||
12193 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12196 CopyBoard(flipBoard, soughtBoard);
12197 CopyBoard(rotateBoard, reverseBoard);
12198 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12199 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12200 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12203 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12204 if(appData.searchMode >= 5) {
12205 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12206 MakePieceList(soughtBoard, minSought);
12207 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12209 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12210 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12213 GameInfo dummyInfo;
12214 static int creatingBook;
12217 GameContainsPosition (FILE *f, ListGame *lg)
12219 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12220 int fromX, fromY, toX, toY;
12222 static int initDone=FALSE;
12224 // weed out games based on numerical tag comparison
12225 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12226 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12227 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12228 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12230 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12233 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12234 else CopyBoard(boards[scratch], initialPosition); // default start position
12237 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12238 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12241 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12242 fseek(f, lg->offset, 0);
12245 yyboardindex = scratch;
12246 quickFlag = plyNr+1;
12251 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12257 if(plyNr) return -1; // after we have seen moves, this is for new game
12260 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12261 case ImpossibleMove:
12262 case WhiteWins: // game ends here with these four
12265 case GameUnfinished:
12269 if(appData.testLegality) return -1;
12270 case WhiteCapturesEnPassant:
12271 case BlackCapturesEnPassant:
12272 case WhitePromotion:
12273 case BlackPromotion:
12274 case WhiteNonPromotion:
12275 case BlackNonPromotion:
12278 case WhiteKingSideCastle:
12279 case WhiteQueenSideCastle:
12280 case BlackKingSideCastle:
12281 case BlackQueenSideCastle:
12282 case WhiteKingSideCastleWild:
12283 case WhiteQueenSideCastleWild:
12284 case BlackKingSideCastleWild:
12285 case BlackQueenSideCastleWild:
12286 case WhiteHSideCastleFR:
12287 case WhiteASideCastleFR:
12288 case BlackHSideCastleFR:
12289 case BlackASideCastleFR:
12290 fromX = currentMoveString[0] - AAA;
12291 fromY = currentMoveString[1] - ONE;
12292 toX = currentMoveString[2] - AAA;
12293 toY = currentMoveString[3] - ONE;
12294 promoChar = currentMoveString[4];
12298 fromX = next == WhiteDrop ?
12299 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12300 (int) CharToPiece(ToLower(currentMoveString[0]));
12302 toX = currentMoveString[2] - AAA;
12303 toY = currentMoveString[3] - ONE;
12307 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12309 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12310 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12311 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12312 if(appData.findMirror) {
12313 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12314 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12319 /* Load the nth game from open file f */
12321 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12325 int gn = gameNumber;
12326 ListGame *lg = NULL;
12327 int numPGNTags = 0;
12329 GameMode oldGameMode;
12330 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12332 if (appData.debugMode)
12333 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12335 if (gameMode == Training )
12336 SetTrainingModeOff();
12338 oldGameMode = gameMode;
12339 if (gameMode != BeginningOfGame) {
12340 Reset(FALSE, TRUE);
12342 killX = killY = -1; // [HGM] lion: in case we did not Reset
12345 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12346 fclose(lastLoadGameFP);
12350 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12353 fseek(f, lg->offset, 0);
12354 GameListHighlight(gameNumber);
12355 pos = lg->position;
12359 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12360 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12362 DisplayError(_("Game number out of range"), 0);
12367 if (fseek(f, 0, 0) == -1) {
12368 if (f == lastLoadGameFP ?
12369 gameNumber == lastLoadGameNumber + 1 :
12373 DisplayError(_("Can't seek on game file"), 0);
12378 lastLoadGameFP = f;
12379 lastLoadGameNumber = gameNumber;
12380 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12381 lastLoadGameUseList = useList;
12385 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12386 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12387 lg->gameInfo.black);
12389 } else if (*title != NULLCHAR) {
12390 if (gameNumber > 1) {
12391 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12394 DisplayTitle(title);
12398 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12399 gameMode = PlayFromGameFile;
12403 currentMove = forwardMostMove = backwardMostMove = 0;
12404 CopyBoard(boards[0], initialPosition);
12408 * Skip the first gn-1 games in the file.
12409 * Also skip over anything that precedes an identifiable
12410 * start of game marker, to avoid being confused by
12411 * garbage at the start of the file. Currently
12412 * recognized start of game markers are the move number "1",
12413 * the pattern "gnuchess .* game", the pattern
12414 * "^[#;%] [^ ]* game file", and a PGN tag block.
12415 * A game that starts with one of the latter two patterns
12416 * will also have a move number 1, possibly
12417 * following a position diagram.
12418 * 5-4-02: Let's try being more lenient and allowing a game to
12419 * start with an unnumbered move. Does that break anything?
12421 cm = lastLoadGameStart = EndOfFile;
12423 yyboardindex = forwardMostMove;
12424 cm = (ChessMove) Myylex();
12427 if (cmailMsgLoaded) {
12428 nCmailGames = CMAIL_MAX_GAMES - gn;
12431 DisplayError(_("Game not found in file"), 0);
12438 lastLoadGameStart = cm;
12441 case MoveNumberOne:
12442 switch (lastLoadGameStart) {
12447 case MoveNumberOne:
12449 gn--; /* count this game */
12450 lastLoadGameStart = cm;
12459 switch (lastLoadGameStart) {
12462 case MoveNumberOne:
12464 gn--; /* count this game */
12465 lastLoadGameStart = cm;
12468 lastLoadGameStart = cm; /* game counted already */
12476 yyboardindex = forwardMostMove;
12477 cm = (ChessMove) Myylex();
12478 } while (cm == PGNTag || cm == Comment);
12485 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12486 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12487 != CMAIL_OLD_RESULT) {
12489 cmailResult[ CMAIL_MAX_GAMES
12490 - gn - 1] = CMAIL_OLD_RESULT;
12497 /* Only a NormalMove can be at the start of a game
12498 * without a position diagram. */
12499 if (lastLoadGameStart == EndOfFile ) {
12501 lastLoadGameStart = MoveNumberOne;
12510 if (appData.debugMode)
12511 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12513 if (cm == XBoardGame) {
12514 /* Skip any header junk before position diagram and/or move 1 */
12516 yyboardindex = forwardMostMove;
12517 cm = (ChessMove) Myylex();
12519 if (cm == EndOfFile ||
12520 cm == GNUChessGame || cm == XBoardGame) {
12521 /* Empty game; pretend end-of-file and handle later */
12526 if (cm == MoveNumberOne || cm == PositionDiagram ||
12527 cm == PGNTag || cm == Comment)
12530 } else if (cm == GNUChessGame) {
12531 if (gameInfo.event != NULL) {
12532 free(gameInfo.event);
12534 gameInfo.event = StrSave(yy_text);
12537 startedFromSetupPosition = FALSE;
12538 while (cm == PGNTag) {
12539 if (appData.debugMode)
12540 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12541 err = ParsePGNTag(yy_text, &gameInfo);
12542 if (!err) numPGNTags++;
12544 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12545 if(gameInfo.variant != oldVariant) {
12546 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12547 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12548 InitPosition(TRUE);
12549 oldVariant = gameInfo.variant;
12550 if (appData.debugMode)
12551 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12555 if (gameInfo.fen != NULL) {
12556 Board initial_position;
12557 startedFromSetupPosition = TRUE;
12558 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12560 DisplayError(_("Bad FEN position in file"), 0);
12563 CopyBoard(boards[0], initial_position);
12564 if (blackPlaysFirst) {
12565 currentMove = forwardMostMove = backwardMostMove = 1;
12566 CopyBoard(boards[1], initial_position);
12567 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12568 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12569 timeRemaining[0][1] = whiteTimeRemaining;
12570 timeRemaining[1][1] = blackTimeRemaining;
12571 if (commentList[0] != NULL) {
12572 commentList[1] = commentList[0];
12573 commentList[0] = NULL;
12576 currentMove = forwardMostMove = backwardMostMove = 0;
12578 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12580 initialRulePlies = FENrulePlies;
12581 for( i=0; i< nrCastlingRights; i++ )
12582 initialRights[i] = initial_position[CASTLING][i];
12584 yyboardindex = forwardMostMove;
12585 free(gameInfo.fen);
12586 gameInfo.fen = NULL;
12589 yyboardindex = forwardMostMove;
12590 cm = (ChessMove) Myylex();
12592 /* Handle comments interspersed among the tags */
12593 while (cm == Comment) {
12595 if (appData.debugMode)
12596 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12598 AppendComment(currentMove, p, FALSE);
12599 yyboardindex = forwardMostMove;
12600 cm = (ChessMove) Myylex();
12604 /* don't rely on existence of Event tag since if game was
12605 * pasted from clipboard the Event tag may not exist
12607 if (numPGNTags > 0){
12609 if (gameInfo.variant == VariantNormal) {
12610 VariantClass v = StringToVariant(gameInfo.event);
12611 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12612 if(v < VariantShogi) gameInfo.variant = v;
12615 if( appData.autoDisplayTags ) {
12616 tags = PGNTags(&gameInfo);
12617 TagsPopUp(tags, CmailMsg());
12622 /* Make something up, but don't display it now */
12627 if (cm == PositionDiagram) {
12630 Board initial_position;
12632 if (appData.debugMode)
12633 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12635 if (!startedFromSetupPosition) {
12637 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12638 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12649 initial_position[i][j++] = CharToPiece(*p);
12652 while (*p == ' ' || *p == '\t' ||
12653 *p == '\n' || *p == '\r') p++;
12655 if (strncmp(p, "black", strlen("black"))==0)
12656 blackPlaysFirst = TRUE;
12658 blackPlaysFirst = FALSE;
12659 startedFromSetupPosition = TRUE;
12661 CopyBoard(boards[0], initial_position);
12662 if (blackPlaysFirst) {
12663 currentMove = forwardMostMove = backwardMostMove = 1;
12664 CopyBoard(boards[1], initial_position);
12665 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12666 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12667 timeRemaining[0][1] = whiteTimeRemaining;
12668 timeRemaining[1][1] = blackTimeRemaining;
12669 if (commentList[0] != NULL) {
12670 commentList[1] = commentList[0];
12671 commentList[0] = NULL;
12674 currentMove = forwardMostMove = backwardMostMove = 0;
12677 yyboardindex = forwardMostMove;
12678 cm = (ChessMove) Myylex();
12681 if(!creatingBook) {
12682 if (first.pr == NoProc) {
12683 StartChessProgram(&first);
12685 InitChessProgram(&first, FALSE);
12686 SendToProgram("force\n", &first);
12687 if (startedFromSetupPosition) {
12688 SendBoard(&first, forwardMostMove);
12689 if (appData.debugMode) {
12690 fprintf(debugFP, "Load Game\n");
12692 DisplayBothClocks();
12696 /* [HGM] server: flag to write setup moves in broadcast file as one */
12697 loadFlag = appData.suppressLoadMoves;
12699 while (cm == Comment) {
12701 if (appData.debugMode)
12702 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12704 AppendComment(currentMove, p, FALSE);
12705 yyboardindex = forwardMostMove;
12706 cm = (ChessMove) Myylex();
12709 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12710 cm == WhiteWins || cm == BlackWins ||
12711 cm == GameIsDrawn || cm == GameUnfinished) {
12712 DisplayMessage("", _("No moves in game"));
12713 if (cmailMsgLoaded) {
12714 if (appData.debugMode)
12715 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12719 DrawPosition(FALSE, boards[currentMove]);
12720 DisplayBothClocks();
12721 gameMode = EditGame;
12728 // [HGM] PV info: routine tests if comment empty
12729 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12730 DisplayComment(currentMove - 1, commentList[currentMove]);
12732 if (!matchMode && appData.timeDelay != 0)
12733 DrawPosition(FALSE, boards[currentMove]);
12735 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12736 programStats.ok_to_send = 1;
12739 /* if the first token after the PGN tags is a move
12740 * and not move number 1, retrieve it from the parser
12742 if (cm != MoveNumberOne)
12743 LoadGameOneMove(cm);
12745 /* load the remaining moves from the file */
12746 while (LoadGameOneMove(EndOfFile)) {
12747 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12748 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12751 /* rewind to the start of the game */
12752 currentMove = backwardMostMove;
12754 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12756 if (oldGameMode == AnalyzeFile) {
12757 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12758 AnalyzeFileEvent();
12760 if (oldGameMode == AnalyzeMode) {
12761 AnalyzeFileEvent();
12764 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12765 long int w, b; // [HGM] adjourn: restore saved clock times
12766 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12767 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12768 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12769 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12773 if(creatingBook) return TRUE;
12774 if (!matchMode && pos > 0) {
12775 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12777 if (matchMode || appData.timeDelay == 0) {
12779 } else if (appData.timeDelay > 0) {
12780 AutoPlayGameLoop();
12783 if (appData.debugMode)
12784 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12786 loadFlag = 0; /* [HGM] true game starts */
12790 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12792 ReloadPosition (int offset)
12794 int positionNumber = lastLoadPositionNumber + offset;
12795 if (lastLoadPositionFP == NULL) {
12796 DisplayError(_("No position has been loaded yet"), 0);
12799 if (positionNumber <= 0) {
12800 DisplayError(_("Can't back up any further"), 0);
12803 return LoadPosition(lastLoadPositionFP, positionNumber,
12804 lastLoadPositionTitle);
12807 /* Load the nth position from the given file */
12809 LoadPositionFromFile (char *filename, int n, char *title)
12814 if (strcmp(filename, "-") == 0) {
12815 return LoadPosition(stdin, n, "stdin");
12817 f = fopen(filename, "rb");
12819 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12820 DisplayError(buf, errno);
12823 return LoadPosition(f, n, title);
12828 /* Load the nth position from the given open file, and close it */
12830 LoadPosition (FILE *f, int positionNumber, char *title)
12832 char *p, line[MSG_SIZ];
12833 Board initial_position;
12834 int i, j, fenMode, pn;
12836 if (gameMode == Training )
12837 SetTrainingModeOff();
12839 if (gameMode != BeginningOfGame) {
12840 Reset(FALSE, TRUE);
12842 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12843 fclose(lastLoadPositionFP);
12845 if (positionNumber == 0) positionNumber = 1;
12846 lastLoadPositionFP = f;
12847 lastLoadPositionNumber = positionNumber;
12848 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12849 if (first.pr == NoProc && !appData.noChessProgram) {
12850 StartChessProgram(&first);
12851 InitChessProgram(&first, FALSE);
12853 pn = positionNumber;
12854 if (positionNumber < 0) {
12855 /* Negative position number means to seek to that byte offset */
12856 if (fseek(f, -positionNumber, 0) == -1) {
12857 DisplayError(_("Can't seek on position file"), 0);
12862 if (fseek(f, 0, 0) == -1) {
12863 if (f == lastLoadPositionFP ?
12864 positionNumber == lastLoadPositionNumber + 1 :
12865 positionNumber == 1) {
12868 DisplayError(_("Can't seek on position file"), 0);
12873 /* See if this file is FEN or old-style xboard */
12874 if (fgets(line, MSG_SIZ, f) == NULL) {
12875 DisplayError(_("Position not found in file"), 0);
12878 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12879 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12882 if (fenMode || line[0] == '#') pn--;
12884 /* skip positions before number pn */
12885 if (fgets(line, MSG_SIZ, f) == NULL) {
12887 DisplayError(_("Position not found in file"), 0);
12890 if (fenMode || line[0] == '#') pn--;
12895 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12896 DisplayError(_("Bad FEN position in file"), 0);
12900 (void) fgets(line, MSG_SIZ, f);
12901 (void) fgets(line, MSG_SIZ, f);
12903 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12904 (void) fgets(line, MSG_SIZ, f);
12905 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12908 initial_position[i][j++] = CharToPiece(*p);
12912 blackPlaysFirst = FALSE;
12914 (void) fgets(line, MSG_SIZ, f);
12915 if (strncmp(line, "black", strlen("black"))==0)
12916 blackPlaysFirst = TRUE;
12919 startedFromSetupPosition = TRUE;
12921 CopyBoard(boards[0], initial_position);
12922 if (blackPlaysFirst) {
12923 currentMove = forwardMostMove = backwardMostMove = 1;
12924 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12925 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12926 CopyBoard(boards[1], initial_position);
12927 DisplayMessage("", _("Black to play"));
12929 currentMove = forwardMostMove = backwardMostMove = 0;
12930 DisplayMessage("", _("White to play"));
12932 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12933 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12934 SendToProgram("force\n", &first);
12935 SendBoard(&first, forwardMostMove);
12937 if (appData.debugMode) {
12939 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12940 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12941 fprintf(debugFP, "Load Position\n");
12944 if (positionNumber > 1) {
12945 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12946 DisplayTitle(line);
12948 DisplayTitle(title);
12950 gameMode = EditGame;
12953 timeRemaining[0][1] = whiteTimeRemaining;
12954 timeRemaining[1][1] = blackTimeRemaining;
12955 DrawPosition(FALSE, boards[currentMove]);
12962 CopyPlayerNameIntoFileName (char **dest, char *src)
12964 while (*src != NULLCHAR && *src != ',') {
12969 *(*dest)++ = *src++;
12975 DefaultFileName (char *ext)
12977 static char def[MSG_SIZ];
12980 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12982 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12984 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12986 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12993 /* Save the current game to the given file */
12995 SaveGameToFile (char *filename, int append)
12999 int result, i, t,tot=0;
13001 if (strcmp(filename, "-") == 0) {
13002 return SaveGame(stdout, 0, NULL);
13004 for(i=0; i<10; i++) { // upto 10 tries
13005 f = fopen(filename, append ? "a" : "w");
13006 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13007 if(f || errno != 13) break;
13008 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13012 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13013 DisplayError(buf, errno);
13016 safeStrCpy(buf, lastMsg, MSG_SIZ);
13017 DisplayMessage(_("Waiting for access to save file"), "");
13018 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13019 DisplayMessage(_("Saving game"), "");
13020 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13021 result = SaveGame(f, 0, NULL);
13022 DisplayMessage(buf, "");
13029 SavePart (char *str)
13031 static char buf[MSG_SIZ];
13034 p = strchr(str, ' ');
13035 if (p == NULL) return str;
13036 strncpy(buf, str, p - str);
13037 buf[p - str] = NULLCHAR;
13041 #define PGN_MAX_LINE 75
13043 #define PGN_SIDE_WHITE 0
13044 #define PGN_SIDE_BLACK 1
13047 FindFirstMoveOutOfBook (int side)
13051 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13052 int index = backwardMostMove;
13053 int has_book_hit = 0;
13055 if( (index % 2) != side ) {
13059 while( index < forwardMostMove ) {
13060 /* Check to see if engine is in book */
13061 int depth = pvInfoList[index].depth;
13062 int score = pvInfoList[index].score;
13068 else if( score == 0 && depth == 63 ) {
13069 in_book = 1; /* Zappa */
13071 else if( score == 2 && depth == 99 ) {
13072 in_book = 1; /* Abrok */
13075 has_book_hit += in_book;
13091 GetOutOfBookInfo (char * buf)
13095 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13097 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13098 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13102 if( oob[0] >= 0 || oob[1] >= 0 ) {
13103 for( i=0; i<2; i++ ) {
13107 if( i > 0 && oob[0] >= 0 ) {
13108 strcat( buf, " " );
13111 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13112 sprintf( buf+strlen(buf), "%s%.2f",
13113 pvInfoList[idx].score >= 0 ? "+" : "",
13114 pvInfoList[idx].score / 100.0 );
13120 /* Save game in PGN style and close the file */
13122 SaveGamePGN (FILE *f)
13124 int i, offset, linelen, newblock;
13127 int movelen, numlen, blank;
13128 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13130 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13132 PrintPGNTags(f, &gameInfo);
13134 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13136 if (backwardMostMove > 0 || startedFromSetupPosition) {
13137 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13138 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13139 fprintf(f, "\n{--------------\n");
13140 PrintPosition(f, backwardMostMove);
13141 fprintf(f, "--------------}\n");
13145 /* [AS] Out of book annotation */
13146 if( appData.saveOutOfBookInfo ) {
13149 GetOutOfBookInfo( buf );
13151 if( buf[0] != '\0' ) {
13152 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13159 i = backwardMostMove;
13163 while (i < forwardMostMove) {
13164 /* Print comments preceding this move */
13165 if (commentList[i] != NULL) {
13166 if (linelen > 0) fprintf(f, "\n");
13167 fprintf(f, "%s", commentList[i]);
13172 /* Format move number */
13174 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13177 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13179 numtext[0] = NULLCHAR;
13181 numlen = strlen(numtext);
13184 /* Print move number */
13185 blank = linelen > 0 && numlen > 0;
13186 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13195 fprintf(f, "%s", numtext);
13199 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13200 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13203 blank = linelen > 0 && movelen > 0;
13204 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13213 fprintf(f, "%s", move_buffer);
13214 linelen += movelen;
13216 /* [AS] Add PV info if present */
13217 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13218 /* [HGM] add time */
13219 char buf[MSG_SIZ]; int seconds;
13221 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13227 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13230 seconds = (seconds + 4)/10; // round to full seconds
13232 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13234 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13237 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13238 pvInfoList[i].score >= 0 ? "+" : "",
13239 pvInfoList[i].score / 100.0,
13240 pvInfoList[i].depth,
13243 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13245 /* Print score/depth */
13246 blank = linelen > 0 && movelen > 0;
13247 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13256 fprintf(f, "%s", move_buffer);
13257 linelen += movelen;
13263 /* Start a new line */
13264 if (linelen > 0) fprintf(f, "\n");
13266 /* Print comments after last move */
13267 if (commentList[i] != NULL) {
13268 fprintf(f, "%s\n", commentList[i]);
13272 if (gameInfo.resultDetails != NULL &&
13273 gameInfo.resultDetails[0] != NULLCHAR) {
13274 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13275 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13276 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13277 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13278 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13280 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13284 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13288 /* Save game in old style and close the file */
13290 SaveGameOldStyle (FILE *f)
13295 tm = time((time_t *) NULL);
13297 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13300 if (backwardMostMove > 0 || startedFromSetupPosition) {
13301 fprintf(f, "\n[--------------\n");
13302 PrintPosition(f, backwardMostMove);
13303 fprintf(f, "--------------]\n");
13308 i = backwardMostMove;
13309 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13311 while (i < forwardMostMove) {
13312 if (commentList[i] != NULL) {
13313 fprintf(f, "[%s]\n", commentList[i]);
13316 if ((i % 2) == 1) {
13317 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13320 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13322 if (commentList[i] != NULL) {
13326 if (i >= forwardMostMove) {
13330 fprintf(f, "%s\n", parseList[i]);
13335 if (commentList[i] != NULL) {
13336 fprintf(f, "[%s]\n", commentList[i]);
13339 /* This isn't really the old style, but it's close enough */
13340 if (gameInfo.resultDetails != NULL &&
13341 gameInfo.resultDetails[0] != NULLCHAR) {
13342 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13343 gameInfo.resultDetails);
13345 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13352 /* Save the current game to open file f and close the file */
13354 SaveGame (FILE *f, int dummy, char *dummy2)
13356 if (gameMode == EditPosition) EditPositionDone(TRUE);
13357 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13358 if (appData.oldSaveStyle)
13359 return SaveGameOldStyle(f);
13361 return SaveGamePGN(f);
13364 /* Save the current position to the given file */
13366 SavePositionToFile (char *filename)
13371 if (strcmp(filename, "-") == 0) {
13372 return SavePosition(stdout, 0, NULL);
13374 f = fopen(filename, "a");
13376 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13377 DisplayError(buf, errno);
13380 safeStrCpy(buf, lastMsg, MSG_SIZ);
13381 DisplayMessage(_("Waiting for access to save file"), "");
13382 flock(fileno(f), LOCK_EX); // [HGM] lock
13383 DisplayMessage(_("Saving position"), "");
13384 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13385 SavePosition(f, 0, NULL);
13386 DisplayMessage(buf, "");
13392 /* Save the current position to the given open file and close the file */
13394 SavePosition (FILE *f, int dummy, char *dummy2)
13399 if (gameMode == EditPosition) EditPositionDone(TRUE);
13400 if (appData.oldSaveStyle) {
13401 tm = time((time_t *) NULL);
13403 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13405 fprintf(f, "[--------------\n");
13406 PrintPosition(f, currentMove);
13407 fprintf(f, "--------------]\n");
13409 fen = PositionToFEN(currentMove, NULL, 1);
13410 fprintf(f, "%s\n", fen);
13418 ReloadCmailMsgEvent (int unregister)
13421 static char *inFilename = NULL;
13422 static char *outFilename;
13424 struct stat inbuf, outbuf;
13427 /* Any registered moves are unregistered if unregister is set, */
13428 /* i.e. invoked by the signal handler */
13430 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13431 cmailMoveRegistered[i] = FALSE;
13432 if (cmailCommentList[i] != NULL) {
13433 free(cmailCommentList[i]);
13434 cmailCommentList[i] = NULL;
13437 nCmailMovesRegistered = 0;
13440 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13441 cmailResult[i] = CMAIL_NOT_RESULT;
13445 if (inFilename == NULL) {
13446 /* Because the filenames are static they only get malloced once */
13447 /* and they never get freed */
13448 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13449 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13451 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13452 sprintf(outFilename, "%s.out", appData.cmailGameName);
13455 status = stat(outFilename, &outbuf);
13457 cmailMailedMove = FALSE;
13459 status = stat(inFilename, &inbuf);
13460 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13463 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13464 counts the games, notes how each one terminated, etc.
13466 It would be nice to remove this kludge and instead gather all
13467 the information while building the game list. (And to keep it
13468 in the game list nodes instead of having a bunch of fixed-size
13469 parallel arrays.) Note this will require getting each game's
13470 termination from the PGN tags, as the game list builder does
13471 not process the game moves. --mann
13473 cmailMsgLoaded = TRUE;
13474 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13476 /* Load first game in the file or popup game menu */
13477 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13479 #endif /* !WIN32 */
13487 char string[MSG_SIZ];
13489 if ( cmailMailedMove
13490 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13491 return TRUE; /* Allow free viewing */
13494 /* Unregister move to ensure that we don't leave RegisterMove */
13495 /* with the move registered when the conditions for registering no */
13497 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13498 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13499 nCmailMovesRegistered --;
13501 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13503 free(cmailCommentList[lastLoadGameNumber - 1]);
13504 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13508 if (cmailOldMove == -1) {
13509 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13513 if (currentMove > cmailOldMove + 1) {
13514 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13518 if (currentMove < cmailOldMove) {
13519 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13523 if (forwardMostMove > currentMove) {
13524 /* Silently truncate extra moves */
13528 if ( (currentMove == cmailOldMove + 1)
13529 || ( (currentMove == cmailOldMove)
13530 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13531 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13532 if (gameInfo.result != GameUnfinished) {
13533 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13536 if (commentList[currentMove] != NULL) {
13537 cmailCommentList[lastLoadGameNumber - 1]
13538 = StrSave(commentList[currentMove]);
13540 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13542 if (appData.debugMode)
13543 fprintf(debugFP, "Saving %s for game %d\n",
13544 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13546 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13548 f = fopen(string, "w");
13549 if (appData.oldSaveStyle) {
13550 SaveGameOldStyle(f); /* also closes the file */
13552 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13553 f = fopen(string, "w");
13554 SavePosition(f, 0, NULL); /* also closes the file */
13556 fprintf(f, "{--------------\n");
13557 PrintPosition(f, currentMove);
13558 fprintf(f, "--------------}\n\n");
13560 SaveGame(f, 0, NULL); /* also closes the file*/
13563 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13564 nCmailMovesRegistered ++;
13565 } else if (nCmailGames == 1) {
13566 DisplayError(_("You have not made a move yet"), 0);
13577 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13578 FILE *commandOutput;
13579 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13580 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13586 if (! cmailMsgLoaded) {
13587 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13591 if (nCmailGames == nCmailResults) {
13592 DisplayError(_("No unfinished games"), 0);
13596 #if CMAIL_PROHIBIT_REMAIL
13597 if (cmailMailedMove) {
13598 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);
13599 DisplayError(msg, 0);
13604 if (! (cmailMailedMove || RegisterMove())) return;
13606 if ( cmailMailedMove
13607 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13608 snprintf(string, MSG_SIZ, partCommandString,
13609 appData.debugMode ? " -v" : "", appData.cmailGameName);
13610 commandOutput = popen(string, "r");
13612 if (commandOutput == NULL) {
13613 DisplayError(_("Failed to invoke cmail"), 0);
13615 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13616 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13618 if (nBuffers > 1) {
13619 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13620 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13621 nBytes = MSG_SIZ - 1;
13623 (void) memcpy(msg, buffer, nBytes);
13625 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13627 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13628 cmailMailedMove = TRUE; /* Prevent >1 moves */
13631 for (i = 0; i < nCmailGames; i ++) {
13632 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13637 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13639 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13641 appData.cmailGameName,
13643 LoadGameFromFile(buffer, 1, buffer, FALSE);
13644 cmailMsgLoaded = FALSE;
13648 DisplayInformation(msg);
13649 pclose(commandOutput);
13652 if ((*cmailMsg) != '\0') {
13653 DisplayInformation(cmailMsg);
13658 #endif /* !WIN32 */
13667 int prependComma = 0;
13669 char string[MSG_SIZ]; /* Space for game-list */
13672 if (!cmailMsgLoaded) return "";
13674 if (cmailMailedMove) {
13675 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13677 /* Create a list of games left */
13678 snprintf(string, MSG_SIZ, "[");
13679 for (i = 0; i < nCmailGames; i ++) {
13680 if (! ( cmailMoveRegistered[i]
13681 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13682 if (prependComma) {
13683 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13685 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13689 strcat(string, number);
13692 strcat(string, "]");
13694 if (nCmailMovesRegistered + nCmailResults == 0) {
13695 switch (nCmailGames) {
13697 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13701 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13705 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13710 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13712 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13717 if (nCmailResults == nCmailGames) {
13718 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13720 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13725 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13737 if (gameMode == Training)
13738 SetTrainingModeOff();
13741 cmailMsgLoaded = FALSE;
13742 if (appData.icsActive) {
13743 SendToICS(ics_prefix);
13744 SendToICS("refresh\n");
13749 ExitEvent (int status)
13753 /* Give up on clean exit */
13757 /* Keep trying for clean exit */
13761 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13763 if (telnetISR != NULL) {
13764 RemoveInputSource(telnetISR);
13766 if (icsPR != NoProc) {
13767 DestroyChildProcess(icsPR, TRUE);
13770 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13771 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13773 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13774 /* make sure this other one finishes before killing it! */
13775 if(endingGame) { int count = 0;
13776 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13777 while(endingGame && count++ < 10) DoSleep(1);
13778 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13781 /* Kill off chess programs */
13782 if (first.pr != NoProc) {
13785 DoSleep( appData.delayBeforeQuit );
13786 SendToProgram("quit\n", &first);
13787 DoSleep( appData.delayAfterQuit );
13788 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13790 if (second.pr != NoProc) {
13791 DoSleep( appData.delayBeforeQuit );
13792 SendToProgram("quit\n", &second);
13793 DoSleep( appData.delayAfterQuit );
13794 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13796 if (first.isr != NULL) {
13797 RemoveInputSource(first.isr);
13799 if (second.isr != NULL) {
13800 RemoveInputSource(second.isr);
13803 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13804 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13806 ShutDownFrontEnd();
13811 PauseEngine (ChessProgramState *cps)
13813 SendToProgram("pause\n", cps);
13818 UnPauseEngine (ChessProgramState *cps)
13820 SendToProgram("resume\n", cps);
13827 if (appData.debugMode)
13828 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13832 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13834 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13835 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13836 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13838 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13839 HandleMachineMove(stashedInputMove, stalledEngine);
13840 stalledEngine = NULL;
13843 if (gameMode == MachinePlaysWhite ||
13844 gameMode == TwoMachinesPlay ||
13845 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13846 if(first.pause) UnPauseEngine(&first);
13847 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13848 if(second.pause) UnPauseEngine(&second);
13849 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13852 DisplayBothClocks();
13854 if (gameMode == PlayFromGameFile) {
13855 if (appData.timeDelay >= 0)
13856 AutoPlayGameLoop();
13857 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13858 Reset(FALSE, TRUE);
13859 SendToICS(ics_prefix);
13860 SendToICS("refresh\n");
13861 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13862 ForwardInner(forwardMostMove);
13864 pauseExamInvalid = FALSE;
13866 switch (gameMode) {
13870 pauseExamForwardMostMove = forwardMostMove;
13871 pauseExamInvalid = FALSE;
13874 case IcsPlayingWhite:
13875 case IcsPlayingBlack:
13879 case PlayFromGameFile:
13880 (void) StopLoadGameTimer();
13884 case BeginningOfGame:
13885 if (appData.icsActive) return;
13886 /* else fall through */
13887 case MachinePlaysWhite:
13888 case MachinePlaysBlack:
13889 case TwoMachinesPlay:
13890 if (forwardMostMove == 0)
13891 return; /* don't pause if no one has moved */
13892 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13893 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13894 if(onMove->pause) { // thinking engine can be paused
13895 PauseEngine(onMove); // do it
13896 if(onMove->other->pause) // pondering opponent can always be paused immediately
13897 PauseEngine(onMove->other);
13899 SendToProgram("easy\n", onMove->other);
13901 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13902 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13904 PauseEngine(&first);
13906 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13907 } else { // human on move, pause pondering by either method
13909 PauseEngine(&first);
13910 else if(appData.ponderNextMove)
13911 SendToProgram("easy\n", &first);
13914 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13924 EditCommentEvent ()
13926 char title[MSG_SIZ];
13928 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13929 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13931 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13932 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13933 parseList[currentMove - 1]);
13936 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13943 char *tags = PGNTags(&gameInfo);
13945 EditTagsPopUp(tags, NULL);
13952 if(second.analyzing) {
13953 SendToProgram("exit\n", &second);
13954 second.analyzing = FALSE;
13956 if (second.pr == NoProc) StartChessProgram(&second);
13957 InitChessProgram(&second, FALSE);
13958 FeedMovesToProgram(&second, currentMove);
13960 SendToProgram("analyze\n", &second);
13961 second.analyzing = TRUE;
13965 /* Toggle ShowThinking */
13967 ToggleShowThinking()
13969 appData.showThinking = !appData.showThinking;
13970 ShowThinkingEvent();
13974 AnalyzeModeEvent ()
13978 if (!first.analysisSupport) {
13979 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13980 DisplayError(buf, 0);
13983 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13984 if (appData.icsActive) {
13985 if (gameMode != IcsObserving) {
13986 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13987 DisplayError(buf, 0);
13989 if (appData.icsEngineAnalyze) {
13990 if (appData.debugMode)
13991 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13997 /* if enable, user wants to disable icsEngineAnalyze */
13998 if (appData.icsEngineAnalyze) {
14003 appData.icsEngineAnalyze = TRUE;
14004 if (appData.debugMode)
14005 fprintf(debugFP, "ICS engine analyze starting... \n");
14008 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14009 if (appData.noChessProgram || gameMode == AnalyzeMode)
14012 if (gameMode != AnalyzeFile) {
14013 if (!appData.icsEngineAnalyze) {
14015 if (gameMode != EditGame) return 0;
14017 if (!appData.showThinking) ToggleShowThinking();
14018 ResurrectChessProgram();
14019 SendToProgram("analyze\n", &first);
14020 first.analyzing = TRUE;
14021 /*first.maybeThinking = TRUE;*/
14022 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14023 EngineOutputPopUp();
14025 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14030 StartAnalysisClock();
14031 GetTimeMark(&lastNodeCountTime);
14037 AnalyzeFileEvent ()
14039 if (appData.noChessProgram || gameMode == AnalyzeFile)
14042 if (!first.analysisSupport) {
14044 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14045 DisplayError(buf, 0);
14049 if (gameMode != AnalyzeMode) {
14050 keepInfo = 1; // mere annotating should not alter PGN tags
14053 if (gameMode != EditGame) return;
14054 if (!appData.showThinking) ToggleShowThinking();
14055 ResurrectChessProgram();
14056 SendToProgram("analyze\n", &first);
14057 first.analyzing = TRUE;
14058 /*first.maybeThinking = TRUE;*/
14059 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14060 EngineOutputPopUp();
14062 gameMode = AnalyzeFile;
14066 StartAnalysisClock();
14067 GetTimeMark(&lastNodeCountTime);
14069 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14070 AnalysisPeriodicEvent(1);
14074 MachineWhiteEvent ()
14077 char *bookHit = NULL;
14079 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14083 if (gameMode == PlayFromGameFile ||
14084 gameMode == TwoMachinesPlay ||
14085 gameMode == Training ||
14086 gameMode == AnalyzeMode ||
14087 gameMode == EndOfGame)
14090 if (gameMode == EditPosition)
14091 EditPositionDone(TRUE);
14093 if (!WhiteOnMove(currentMove)) {
14094 DisplayError(_("It is not White's turn"), 0);
14098 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14101 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14102 gameMode == AnalyzeFile)
14105 ResurrectChessProgram(); /* in case it isn't running */
14106 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14107 gameMode = MachinePlaysWhite;
14110 gameMode = MachinePlaysWhite;
14114 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14116 if (first.sendName) {
14117 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14118 SendToProgram(buf, &first);
14120 if (first.sendTime) {
14121 if (first.useColors) {
14122 SendToProgram("black\n", &first); /*gnu kludge*/
14124 SendTimeRemaining(&first, TRUE);
14126 if (first.useColors) {
14127 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14129 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14130 SetMachineThinkingEnables();
14131 first.maybeThinking = TRUE;
14135 if (appData.autoFlipView && !flipView) {
14136 flipView = !flipView;
14137 DrawPosition(FALSE, NULL);
14138 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14141 if(bookHit) { // [HGM] book: simulate book reply
14142 static char bookMove[MSG_SIZ]; // a bit generous?
14144 programStats.nodes = programStats.depth = programStats.time =
14145 programStats.score = programStats.got_only_move = 0;
14146 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14148 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14149 strcat(bookMove, bookHit);
14150 HandleMachineMove(bookMove, &first);
14155 MachineBlackEvent ()
14158 char *bookHit = NULL;
14160 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14164 if (gameMode == PlayFromGameFile ||
14165 gameMode == TwoMachinesPlay ||
14166 gameMode == Training ||
14167 gameMode == AnalyzeMode ||
14168 gameMode == EndOfGame)
14171 if (gameMode == EditPosition)
14172 EditPositionDone(TRUE);
14174 if (WhiteOnMove(currentMove)) {
14175 DisplayError(_("It is not Black's turn"), 0);
14179 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14182 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14183 gameMode == AnalyzeFile)
14186 ResurrectChessProgram(); /* in case it isn't running */
14187 gameMode = MachinePlaysBlack;
14191 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14193 if (first.sendName) {
14194 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14195 SendToProgram(buf, &first);
14197 if (first.sendTime) {
14198 if (first.useColors) {
14199 SendToProgram("white\n", &first); /*gnu kludge*/
14201 SendTimeRemaining(&first, FALSE);
14203 if (first.useColors) {
14204 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14206 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14207 SetMachineThinkingEnables();
14208 first.maybeThinking = TRUE;
14211 if (appData.autoFlipView && flipView) {
14212 flipView = !flipView;
14213 DrawPosition(FALSE, NULL);
14214 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14216 if(bookHit) { // [HGM] book: simulate book reply
14217 static char bookMove[MSG_SIZ]; // a bit generous?
14219 programStats.nodes = programStats.depth = programStats.time =
14220 programStats.score = programStats.got_only_move = 0;
14221 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14223 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14224 strcat(bookMove, bookHit);
14225 HandleMachineMove(bookMove, &first);
14231 DisplayTwoMachinesTitle ()
14234 if (appData.matchGames > 0) {
14235 if(appData.tourneyFile[0]) {
14236 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14237 gameInfo.white, _("vs."), gameInfo.black,
14238 nextGame+1, appData.matchGames+1,
14239 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14241 if (first.twoMachinesColor[0] == 'w') {
14242 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14243 gameInfo.white, _("vs."), gameInfo.black,
14244 first.matchWins, second.matchWins,
14245 matchGame - 1 - (first.matchWins + second.matchWins));
14247 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14248 gameInfo.white, _("vs."), gameInfo.black,
14249 second.matchWins, first.matchWins,
14250 matchGame - 1 - (first.matchWins + second.matchWins));
14253 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14259 SettingsMenuIfReady ()
14261 if (second.lastPing != second.lastPong) {
14262 DisplayMessage("", _("Waiting for second chess program"));
14263 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14267 DisplayMessage("", "");
14268 SettingsPopUp(&second);
14272 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14275 if (cps->pr == NoProc) {
14276 StartChessProgram(cps);
14277 if (cps->protocolVersion == 1) {
14279 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14281 /* kludge: allow timeout for initial "feature" command */
14282 if(retry != TwoMachinesEventIfReady) FreezeUI();
14283 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14284 DisplayMessage("", buf);
14285 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14293 TwoMachinesEvent P((void))
14297 ChessProgramState *onmove;
14298 char *bookHit = NULL;
14299 static int stalling = 0;
14303 if (appData.noChessProgram) return;
14305 switch (gameMode) {
14306 case TwoMachinesPlay:
14308 case MachinePlaysWhite:
14309 case MachinePlaysBlack:
14310 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14311 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14315 case BeginningOfGame:
14316 case PlayFromGameFile:
14319 if (gameMode != EditGame) return;
14322 EditPositionDone(TRUE);
14333 // forwardMostMove = currentMove;
14334 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14335 startingEngine = TRUE;
14337 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14339 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14340 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14341 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14344 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14346 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14347 startingEngine = FALSE;
14348 DisplayError("second engine does not play this", 0);
14353 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14354 SendToProgram("force\n", &second);
14356 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14359 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14360 if(appData.matchPause>10000 || appData.matchPause<10)
14361 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14362 wait = SubtractTimeMarks(&now, &pauseStart);
14363 if(wait < appData.matchPause) {
14364 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14367 // we are now committed to starting the game
14369 DisplayMessage("", "");
14370 if (startedFromSetupPosition) {
14371 SendBoard(&second, backwardMostMove);
14372 if (appData.debugMode) {
14373 fprintf(debugFP, "Two Machines\n");
14376 for (i = backwardMostMove; i < forwardMostMove; i++) {
14377 SendMoveToProgram(i, &second);
14380 gameMode = TwoMachinesPlay;
14381 pausing = startingEngine = FALSE;
14382 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14384 DisplayTwoMachinesTitle();
14386 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14391 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14392 SendToProgram(first.computerString, &first);
14393 if (first.sendName) {
14394 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14395 SendToProgram(buf, &first);
14397 SendToProgram(second.computerString, &second);
14398 if (second.sendName) {
14399 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14400 SendToProgram(buf, &second);
14404 if (!first.sendTime || !second.sendTime) {
14405 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14406 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14408 if (onmove->sendTime) {
14409 if (onmove->useColors) {
14410 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14412 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14414 if (onmove->useColors) {
14415 SendToProgram(onmove->twoMachinesColor, onmove);
14417 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14418 // SendToProgram("go\n", onmove);
14419 onmove->maybeThinking = TRUE;
14420 SetMachineThinkingEnables();
14424 if(bookHit) { // [HGM] book: simulate book reply
14425 static char bookMove[MSG_SIZ]; // a bit generous?
14427 programStats.nodes = programStats.depth = programStats.time =
14428 programStats.score = programStats.got_only_move = 0;
14429 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14431 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14432 strcat(bookMove, bookHit);
14433 savedMessage = bookMove; // args for deferred call
14434 savedState = onmove;
14435 ScheduleDelayedEvent(DeferredBookMove, 1);
14442 if (gameMode == Training) {
14443 SetTrainingModeOff();
14444 gameMode = PlayFromGameFile;
14445 DisplayMessage("", _("Training mode off"));
14447 gameMode = Training;
14448 animateTraining = appData.animate;
14450 /* make sure we are not already at the end of the game */
14451 if (currentMove < forwardMostMove) {
14452 SetTrainingModeOn();
14453 DisplayMessage("", _("Training mode on"));
14455 gameMode = PlayFromGameFile;
14456 DisplayError(_("Already at end of game"), 0);
14465 if (!appData.icsActive) return;
14466 switch (gameMode) {
14467 case IcsPlayingWhite:
14468 case IcsPlayingBlack:
14471 case BeginningOfGame:
14479 EditPositionDone(TRUE);
14492 gameMode = IcsIdle;
14502 switch (gameMode) {
14504 SetTrainingModeOff();
14506 case MachinePlaysWhite:
14507 case MachinePlaysBlack:
14508 case BeginningOfGame:
14509 SendToProgram("force\n", &first);
14510 SetUserThinkingEnables();
14512 case PlayFromGameFile:
14513 (void) StopLoadGameTimer();
14514 if (gameFileFP != NULL) {
14519 EditPositionDone(TRUE);
14524 SendToProgram("force\n", &first);
14526 case TwoMachinesPlay:
14527 GameEnds(EndOfFile, NULL, GE_PLAYER);
14528 ResurrectChessProgram();
14529 SetUserThinkingEnables();
14532 ResurrectChessProgram();
14534 case IcsPlayingBlack:
14535 case IcsPlayingWhite:
14536 DisplayError(_("Warning: You are still playing a game"), 0);
14539 DisplayError(_("Warning: You are still observing a game"), 0);
14542 DisplayError(_("Warning: You are still examining a game"), 0);
14553 first.offeredDraw = second.offeredDraw = 0;
14555 if (gameMode == PlayFromGameFile) {
14556 whiteTimeRemaining = timeRemaining[0][currentMove];
14557 blackTimeRemaining = timeRemaining[1][currentMove];
14561 if (gameMode == MachinePlaysWhite ||
14562 gameMode == MachinePlaysBlack ||
14563 gameMode == TwoMachinesPlay ||
14564 gameMode == EndOfGame) {
14565 i = forwardMostMove;
14566 while (i > currentMove) {
14567 SendToProgram("undo\n", &first);
14570 if(!adjustedClock) {
14571 whiteTimeRemaining = timeRemaining[0][currentMove];
14572 blackTimeRemaining = timeRemaining[1][currentMove];
14573 DisplayBothClocks();
14575 if (whiteFlag || blackFlag) {
14576 whiteFlag = blackFlag = 0;
14581 gameMode = EditGame;
14588 EditPositionEvent ()
14590 if (gameMode == EditPosition) {
14596 if (gameMode != EditGame) return;
14598 gameMode = EditPosition;
14601 if (currentMove > 0)
14602 CopyBoard(boards[0], boards[currentMove]);
14604 blackPlaysFirst = !WhiteOnMove(currentMove);
14606 currentMove = forwardMostMove = backwardMostMove = 0;
14607 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14609 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14615 /* [DM] icsEngineAnalyze - possible call from other functions */
14616 if (appData.icsEngineAnalyze) {
14617 appData.icsEngineAnalyze = FALSE;
14619 DisplayMessage("",_("Close ICS engine analyze..."));
14621 if (first.analysisSupport && first.analyzing) {
14622 SendToBoth("exit\n");
14623 first.analyzing = second.analyzing = FALSE;
14625 thinkOutput[0] = NULLCHAR;
14629 EditPositionDone (Boolean fakeRights)
14631 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14633 startedFromSetupPosition = TRUE;
14634 InitChessProgram(&first, FALSE);
14635 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14636 boards[0][EP_STATUS] = EP_NONE;
14637 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14638 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14639 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14640 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14641 } else boards[0][CASTLING][2] = NoRights;
14642 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14643 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14644 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14645 } else boards[0][CASTLING][5] = NoRights;
14646 if(gameInfo.variant == VariantSChess) {
14648 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14649 boards[0][VIRGIN][i] = 0;
14650 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14651 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14655 SendToProgram("force\n", &first);
14656 if (blackPlaysFirst) {
14657 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14658 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14659 currentMove = forwardMostMove = backwardMostMove = 1;
14660 CopyBoard(boards[1], boards[0]);
14662 currentMove = forwardMostMove = backwardMostMove = 0;
14664 SendBoard(&first, forwardMostMove);
14665 if (appData.debugMode) {
14666 fprintf(debugFP, "EditPosDone\n");
14669 DisplayMessage("", "");
14670 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14671 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14672 gameMode = EditGame;
14674 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14675 ClearHighlights(); /* [AS] */
14678 /* Pause for `ms' milliseconds */
14679 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14681 TimeDelay (long ms)
14688 } while (SubtractTimeMarks(&m2, &m1) < ms);
14691 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14693 SendMultiLineToICS (char *buf)
14695 char temp[MSG_SIZ+1], *p;
14702 strncpy(temp, buf, len);
14707 if (*p == '\n' || *p == '\r')
14712 strcat(temp, "\n");
14714 SendToPlayer(temp, strlen(temp));
14718 SetWhiteToPlayEvent ()
14720 if (gameMode == EditPosition) {
14721 blackPlaysFirst = FALSE;
14722 DisplayBothClocks(); /* works because currentMove is 0 */
14723 } else if (gameMode == IcsExamining) {
14724 SendToICS(ics_prefix);
14725 SendToICS("tomove white\n");
14730 SetBlackToPlayEvent ()
14732 if (gameMode == EditPosition) {
14733 blackPlaysFirst = TRUE;
14734 currentMove = 1; /* kludge */
14735 DisplayBothClocks();
14737 } else if (gameMode == IcsExamining) {
14738 SendToICS(ics_prefix);
14739 SendToICS("tomove black\n");
14744 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14747 ChessSquare piece = boards[0][y][x];
14748 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14749 static int lastVariant;
14751 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14753 switch (selection) {
14755 CopyBoard(currentBoard, boards[0]);
14756 CopyBoard(menuBoard, initialPosition);
14757 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14758 SendToICS(ics_prefix);
14759 SendToICS("bsetup clear\n");
14760 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14761 SendToICS(ics_prefix);
14762 SendToICS("clearboard\n");
14765 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14766 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14767 for (y = 0; y < BOARD_HEIGHT; y++) {
14768 if (gameMode == IcsExamining) {
14769 if (boards[currentMove][y][x] != EmptySquare) {
14770 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14775 if(boards[0][y][x] != p) nonEmpty++;
14776 boards[0][y][x] = p;
14779 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14781 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14782 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
14783 ChessSquare p = menuBoard[0][x];
14784 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14785 p = menuBoard[BOARD_HEIGHT-1][x];
14786 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14788 DisplayMessage("Clicking clock again restores position", "");
14789 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14790 if(!nonEmpty) { // asked to clear an empty board
14791 CopyBoard(boards[0], menuBoard);
14793 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14794 CopyBoard(boards[0], initialPosition);
14796 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14797 && !CompareBoards(nullBoard, erasedBoard)) {
14798 CopyBoard(boards[0], erasedBoard);
14800 CopyBoard(erasedBoard, currentBoard);
14804 if (gameMode == EditPosition) {
14805 DrawPosition(FALSE, boards[0]);
14810 SetWhiteToPlayEvent();
14814 SetBlackToPlayEvent();
14818 if (gameMode == IcsExamining) {
14819 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14820 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14823 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14824 if(x == BOARD_LEFT-2) {
14825 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14826 boards[0][y][1] = 0;
14828 if(x == BOARD_RGHT+1) {
14829 if(y >= gameInfo.holdingsSize) break;
14830 boards[0][y][BOARD_WIDTH-2] = 0;
14833 boards[0][y][x] = EmptySquare;
14834 DrawPosition(FALSE, boards[0]);
14839 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14840 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14841 selection = (ChessSquare) (PROMOTED piece);
14842 } else if(piece == EmptySquare) selection = WhiteSilver;
14843 else selection = (ChessSquare)((int)piece - 1);
14847 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14848 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14849 selection = (ChessSquare) (DEMOTED piece);
14850 } else if(piece == EmptySquare) selection = BlackSilver;
14851 else selection = (ChessSquare)((int)piece + 1);
14856 if(gameInfo.variant == VariantShatranj ||
14857 gameInfo.variant == VariantXiangqi ||
14858 gameInfo.variant == VariantCourier ||
14859 gameInfo.variant == VariantASEAN ||
14860 gameInfo.variant == VariantMakruk )
14861 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14866 if(gameInfo.variant == VariantXiangqi)
14867 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14868 if(gameInfo.variant == VariantKnightmate)
14869 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14872 if (gameMode == IcsExamining) {
14873 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14874 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14875 PieceToChar(selection), AAA + x, ONE + y);
14878 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14880 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14881 n = PieceToNumber(selection - BlackPawn);
14882 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14883 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14884 boards[0][BOARD_HEIGHT-1-n][1]++;
14886 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14887 n = PieceToNumber(selection);
14888 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14889 boards[0][n][BOARD_WIDTH-1] = selection;
14890 boards[0][n][BOARD_WIDTH-2]++;
14893 boards[0][y][x] = selection;
14894 DrawPosition(TRUE, boards[0]);
14896 fromX = fromY = -1;
14904 DropMenuEvent (ChessSquare selection, int x, int y)
14906 ChessMove moveType;
14908 switch (gameMode) {
14909 case IcsPlayingWhite:
14910 case MachinePlaysBlack:
14911 if (!WhiteOnMove(currentMove)) {
14912 DisplayMoveError(_("It is Black's turn"));
14915 moveType = WhiteDrop;
14917 case IcsPlayingBlack:
14918 case MachinePlaysWhite:
14919 if (WhiteOnMove(currentMove)) {
14920 DisplayMoveError(_("It is White's turn"));
14923 moveType = BlackDrop;
14926 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14932 if (moveType == BlackDrop && selection < BlackPawn) {
14933 selection = (ChessSquare) ((int) selection
14934 + (int) BlackPawn - (int) WhitePawn);
14936 if (boards[currentMove][y][x] != EmptySquare) {
14937 DisplayMoveError(_("That square is occupied"));
14941 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14947 /* Accept a pending offer of any kind from opponent */
14949 if (appData.icsActive) {
14950 SendToICS(ics_prefix);
14951 SendToICS("accept\n");
14952 } else if (cmailMsgLoaded) {
14953 if (currentMove == cmailOldMove &&
14954 commentList[cmailOldMove] != NULL &&
14955 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14956 "Black offers a draw" : "White offers a draw")) {
14958 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14959 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14961 DisplayError(_("There is no pending offer on this move"), 0);
14962 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14965 /* Not used for offers from chess program */
14972 /* Decline a pending offer of any kind from opponent */
14974 if (appData.icsActive) {
14975 SendToICS(ics_prefix);
14976 SendToICS("decline\n");
14977 } else if (cmailMsgLoaded) {
14978 if (currentMove == cmailOldMove &&
14979 commentList[cmailOldMove] != NULL &&
14980 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14981 "Black offers a draw" : "White offers a draw")) {
14983 AppendComment(cmailOldMove, "Draw declined", TRUE);
14984 DisplayComment(cmailOldMove - 1, "Draw declined");
14987 DisplayError(_("There is no pending offer on this move"), 0);
14990 /* Not used for offers from chess program */
14997 /* Issue ICS rematch command */
14998 if (appData.icsActive) {
14999 SendToICS(ics_prefix);
15000 SendToICS("rematch\n");
15007 /* Call your opponent's flag (claim a win on time) */
15008 if (appData.icsActive) {
15009 SendToICS(ics_prefix);
15010 SendToICS("flag\n");
15012 switch (gameMode) {
15015 case MachinePlaysWhite:
15018 GameEnds(GameIsDrawn, "Both players ran out of time",
15021 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15023 DisplayError(_("Your opponent is not out of time"), 0);
15026 case MachinePlaysBlack:
15029 GameEnds(GameIsDrawn, "Both players ran out of time",
15032 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15034 DisplayError(_("Your opponent is not out of time"), 0);
15042 ClockClick (int which)
15043 { // [HGM] code moved to back-end from winboard.c
15044 if(which) { // black clock
15045 if (gameMode == EditPosition || gameMode == IcsExamining) {
15046 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15047 SetBlackToPlayEvent();
15048 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15049 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15050 } else if (shiftKey) {
15051 AdjustClock(which, -1);
15052 } else if (gameMode == IcsPlayingWhite ||
15053 gameMode == MachinePlaysBlack) {
15056 } else { // white clock
15057 if (gameMode == EditPosition || gameMode == IcsExamining) {
15058 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15059 SetWhiteToPlayEvent();
15060 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15061 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15062 } else if (shiftKey) {
15063 AdjustClock(which, -1);
15064 } else if (gameMode == IcsPlayingBlack ||
15065 gameMode == MachinePlaysWhite) {
15074 /* Offer draw or accept pending draw offer from opponent */
15076 if (appData.icsActive) {
15077 /* Note: tournament rules require draw offers to be
15078 made after you make your move but before you punch
15079 your clock. Currently ICS doesn't let you do that;
15080 instead, you immediately punch your clock after making
15081 a move, but you can offer a draw at any time. */
15083 SendToICS(ics_prefix);
15084 SendToICS("draw\n");
15085 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15086 } else if (cmailMsgLoaded) {
15087 if (currentMove == cmailOldMove &&
15088 commentList[cmailOldMove] != NULL &&
15089 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15090 "Black offers a draw" : "White offers a draw")) {
15091 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15092 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15093 } else if (currentMove == cmailOldMove + 1) {
15094 char *offer = WhiteOnMove(cmailOldMove) ?
15095 "White offers a draw" : "Black offers a draw";
15096 AppendComment(currentMove, offer, TRUE);
15097 DisplayComment(currentMove - 1, offer);
15098 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15100 DisplayError(_("You must make your move before offering a draw"), 0);
15101 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15103 } else if (first.offeredDraw) {
15104 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15106 if (first.sendDrawOffers) {
15107 SendToProgram("draw\n", &first);
15108 userOfferedDraw = TRUE;
15116 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15118 if (appData.icsActive) {
15119 SendToICS(ics_prefix);
15120 SendToICS("adjourn\n");
15122 /* Currently GNU Chess doesn't offer or accept Adjourns */
15130 /* Offer Abort or accept pending Abort offer from opponent */
15132 if (appData.icsActive) {
15133 SendToICS(ics_prefix);
15134 SendToICS("abort\n");
15136 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15143 /* Resign. You can do this even if it's not your turn. */
15145 if (appData.icsActive) {
15146 SendToICS(ics_prefix);
15147 SendToICS("resign\n");
15149 switch (gameMode) {
15150 case MachinePlaysWhite:
15151 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15153 case MachinePlaysBlack:
15154 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15157 if (cmailMsgLoaded) {
15159 if (WhiteOnMove(cmailOldMove)) {
15160 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15162 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15164 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15175 StopObservingEvent ()
15177 /* Stop observing current games */
15178 SendToICS(ics_prefix);
15179 SendToICS("unobserve\n");
15183 StopExaminingEvent ()
15185 /* Stop observing current game */
15186 SendToICS(ics_prefix);
15187 SendToICS("unexamine\n");
15191 ForwardInner (int target)
15193 int limit; int oldSeekGraphUp = seekGraphUp;
15195 if (appData.debugMode)
15196 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15197 target, currentMove, forwardMostMove);
15199 if (gameMode == EditPosition)
15202 seekGraphUp = FALSE;
15203 MarkTargetSquares(1);
15205 if (gameMode == PlayFromGameFile && !pausing)
15208 if (gameMode == IcsExamining && pausing)
15209 limit = pauseExamForwardMostMove;
15211 limit = forwardMostMove;
15213 if (target > limit) target = limit;
15215 if (target > 0 && moveList[target - 1][0]) {
15216 int fromX, fromY, toX, toY;
15217 toX = moveList[target - 1][2] - AAA;
15218 toY = moveList[target - 1][3] - ONE;
15219 if (moveList[target - 1][1] == '@') {
15220 if (appData.highlightLastMove) {
15221 SetHighlights(-1, -1, toX, toY);
15224 fromX = moveList[target - 1][0] - AAA;
15225 fromY = moveList[target - 1][1] - ONE;
15226 if (target == currentMove + 1) {
15227 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15229 if (appData.highlightLastMove) {
15230 SetHighlights(fromX, fromY, toX, toY);
15234 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15235 gameMode == Training || gameMode == PlayFromGameFile ||
15236 gameMode == AnalyzeFile) {
15237 while (currentMove < target) {
15238 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15239 SendMoveToProgram(currentMove++, &first);
15242 currentMove = target;
15245 if (gameMode == EditGame || gameMode == EndOfGame) {
15246 whiteTimeRemaining = timeRemaining[0][currentMove];
15247 blackTimeRemaining = timeRemaining[1][currentMove];
15249 DisplayBothClocks();
15250 DisplayMove(currentMove - 1);
15251 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15252 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15253 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15254 DisplayComment(currentMove - 1, commentList[currentMove]);
15256 ClearMap(); // [HGM] exclude: invalidate map
15263 if (gameMode == IcsExamining && !pausing) {
15264 SendToICS(ics_prefix);
15265 SendToICS("forward\n");
15267 ForwardInner(currentMove + 1);
15274 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15275 /* to optimze, we temporarily turn off analysis mode while we feed
15276 * the remaining moves to the engine. Otherwise we get analysis output
15279 if (first.analysisSupport) {
15280 SendToProgram("exit\nforce\n", &first);
15281 first.analyzing = FALSE;
15285 if (gameMode == IcsExamining && !pausing) {
15286 SendToICS(ics_prefix);
15287 SendToICS("forward 999999\n");
15289 ForwardInner(forwardMostMove);
15292 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15293 /* we have fed all the moves, so reactivate analysis mode */
15294 SendToProgram("analyze\n", &first);
15295 first.analyzing = TRUE;
15296 /*first.maybeThinking = TRUE;*/
15297 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15302 BackwardInner (int target)
15304 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15306 if (appData.debugMode)
15307 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15308 target, currentMove, forwardMostMove);
15310 if (gameMode == EditPosition) return;
15311 seekGraphUp = FALSE;
15312 MarkTargetSquares(1);
15313 if (currentMove <= backwardMostMove) {
15315 DrawPosition(full_redraw, boards[currentMove]);
15318 if (gameMode == PlayFromGameFile && !pausing)
15321 if (moveList[target][0]) {
15322 int fromX, fromY, toX, toY;
15323 toX = moveList[target][2] - AAA;
15324 toY = moveList[target][3] - ONE;
15325 if (moveList[target][1] == '@') {
15326 if (appData.highlightLastMove) {
15327 SetHighlights(-1, -1, toX, toY);
15330 fromX = moveList[target][0] - AAA;
15331 fromY = moveList[target][1] - ONE;
15332 if (target == currentMove - 1) {
15333 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15335 if (appData.highlightLastMove) {
15336 SetHighlights(fromX, fromY, toX, toY);
15340 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15341 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15342 while (currentMove > target) {
15343 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15344 // null move cannot be undone. Reload program with move history before it.
15346 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15347 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15349 SendBoard(&first, i);
15350 if(second.analyzing) SendBoard(&second, i);
15351 for(currentMove=i; currentMove<target; currentMove++) {
15352 SendMoveToProgram(currentMove, &first);
15353 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15357 SendToBoth("undo\n");
15361 currentMove = target;
15364 if (gameMode == EditGame || gameMode == EndOfGame) {
15365 whiteTimeRemaining = timeRemaining[0][currentMove];
15366 blackTimeRemaining = timeRemaining[1][currentMove];
15368 DisplayBothClocks();
15369 DisplayMove(currentMove - 1);
15370 DrawPosition(full_redraw, boards[currentMove]);
15371 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15372 // [HGM] PV info: routine tests if comment empty
15373 DisplayComment(currentMove - 1, commentList[currentMove]);
15374 ClearMap(); // [HGM] exclude: invalidate map
15380 if (gameMode == IcsExamining && !pausing) {
15381 SendToICS(ics_prefix);
15382 SendToICS("backward\n");
15384 BackwardInner(currentMove - 1);
15391 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15392 /* to optimize, we temporarily turn off analysis mode while we undo
15393 * all the moves. Otherwise we get analysis output after each undo.
15395 if (first.analysisSupport) {
15396 SendToProgram("exit\nforce\n", &first);
15397 first.analyzing = FALSE;
15401 if (gameMode == IcsExamining && !pausing) {
15402 SendToICS(ics_prefix);
15403 SendToICS("backward 999999\n");
15405 BackwardInner(backwardMostMove);
15408 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15409 /* we have fed all the moves, so reactivate analysis mode */
15410 SendToProgram("analyze\n", &first);
15411 first.analyzing = TRUE;
15412 /*first.maybeThinking = TRUE;*/
15413 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15420 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15421 if (to >= forwardMostMove) to = forwardMostMove;
15422 if (to <= backwardMostMove) to = backwardMostMove;
15423 if (to < currentMove) {
15431 RevertEvent (Boolean annotate)
15433 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15436 if (gameMode != IcsExamining) {
15437 DisplayError(_("You are not examining a game"), 0);
15441 DisplayError(_("You can't revert while pausing"), 0);
15444 SendToICS(ics_prefix);
15445 SendToICS("revert\n");
15449 RetractMoveEvent ()
15451 switch (gameMode) {
15452 case MachinePlaysWhite:
15453 case MachinePlaysBlack:
15454 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15455 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15458 if (forwardMostMove < 2) return;
15459 currentMove = forwardMostMove = forwardMostMove - 2;
15460 whiteTimeRemaining = timeRemaining[0][currentMove];
15461 blackTimeRemaining = timeRemaining[1][currentMove];
15462 DisplayBothClocks();
15463 DisplayMove(currentMove - 1);
15464 ClearHighlights();/*!! could figure this out*/
15465 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15466 SendToProgram("remove\n", &first);
15467 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15470 case BeginningOfGame:
15474 case IcsPlayingWhite:
15475 case IcsPlayingBlack:
15476 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15477 SendToICS(ics_prefix);
15478 SendToICS("takeback 2\n");
15480 SendToICS(ics_prefix);
15481 SendToICS("takeback 1\n");
15490 ChessProgramState *cps;
15492 switch (gameMode) {
15493 case MachinePlaysWhite:
15494 if (!WhiteOnMove(forwardMostMove)) {
15495 DisplayError(_("It is your turn"), 0);
15500 case MachinePlaysBlack:
15501 if (WhiteOnMove(forwardMostMove)) {
15502 DisplayError(_("It is your turn"), 0);
15507 case TwoMachinesPlay:
15508 if (WhiteOnMove(forwardMostMove) ==
15509 (first.twoMachinesColor[0] == 'w')) {
15515 case BeginningOfGame:
15519 SendToProgram("?\n", cps);
15523 TruncateGameEvent ()
15526 if (gameMode != EditGame) return;
15533 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15534 if (forwardMostMove > currentMove) {
15535 if (gameInfo.resultDetails != NULL) {
15536 free(gameInfo.resultDetails);
15537 gameInfo.resultDetails = NULL;
15538 gameInfo.result = GameUnfinished;
15540 forwardMostMove = currentMove;
15541 HistorySet(parseList, backwardMostMove, forwardMostMove,
15549 if (appData.noChessProgram) return;
15550 switch (gameMode) {
15551 case MachinePlaysWhite:
15552 if (WhiteOnMove(forwardMostMove)) {
15553 DisplayError(_("Wait until your turn."), 0);
15557 case BeginningOfGame:
15558 case MachinePlaysBlack:
15559 if (!WhiteOnMove(forwardMostMove)) {
15560 DisplayError(_("Wait until your turn."), 0);
15565 DisplayError(_("No hint available"), 0);
15568 SendToProgram("hint\n", &first);
15569 hintRequested = TRUE;
15575 ListGame * lg = (ListGame *) gameList.head;
15578 static int secondTime = FALSE;
15580 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15581 DisplayError(_("Game list not loaded or empty"), 0);
15585 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15588 DisplayNote(_("Book file exists! Try again for overwrite."));
15592 creatingBook = TRUE;
15593 secondTime = FALSE;
15595 /* Get list size */
15596 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15597 LoadGame(f, nItem, "", TRUE);
15598 AddGameToBook(TRUE);
15599 lg = (ListGame *) lg->node.succ;
15602 creatingBook = FALSE;
15609 if (appData.noChessProgram) return;
15610 switch (gameMode) {
15611 case MachinePlaysWhite:
15612 if (WhiteOnMove(forwardMostMove)) {
15613 DisplayError(_("Wait until your turn."), 0);
15617 case BeginningOfGame:
15618 case MachinePlaysBlack:
15619 if (!WhiteOnMove(forwardMostMove)) {
15620 DisplayError(_("Wait until your turn."), 0);
15625 EditPositionDone(TRUE);
15627 case TwoMachinesPlay:
15632 SendToProgram("bk\n", &first);
15633 bookOutput[0] = NULLCHAR;
15634 bookRequested = TRUE;
15640 char *tags = PGNTags(&gameInfo);
15641 TagsPopUp(tags, CmailMsg());
15645 /* end button procedures */
15648 PrintPosition (FILE *fp, int move)
15652 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15653 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15654 char c = PieceToChar(boards[move][i][j]);
15655 fputc(c == 'x' ? '.' : c, fp);
15656 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15659 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15660 fprintf(fp, "white to play\n");
15662 fprintf(fp, "black to play\n");
15666 PrintOpponents (FILE *fp)
15668 if (gameInfo.white != NULL) {
15669 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15675 /* Find last component of program's own name, using some heuristics */
15677 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15680 int local = (strcmp(host, "localhost") == 0);
15681 while (!local && (p = strchr(prog, ';')) != NULL) {
15683 while (*p == ' ') p++;
15686 if (*prog == '"' || *prog == '\'') {
15687 q = strchr(prog + 1, *prog);
15689 q = strchr(prog, ' ');
15691 if (q == NULL) q = prog + strlen(prog);
15693 while (p >= prog && *p != '/' && *p != '\\') p--;
15695 if(p == prog && *p == '"') p++;
15697 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15698 memcpy(buf, p, q - p);
15699 buf[q - p] = NULLCHAR;
15707 TimeControlTagValue ()
15710 if (!appData.clockMode) {
15711 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15712 } else if (movesPerSession > 0) {
15713 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15714 } else if (timeIncrement == 0) {
15715 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15717 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15719 return StrSave(buf);
15725 /* This routine is used only for certain modes */
15726 VariantClass v = gameInfo.variant;
15727 ChessMove r = GameUnfinished;
15730 if(keepInfo) return;
15732 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15733 r = gameInfo.result;
15734 p = gameInfo.resultDetails;
15735 gameInfo.resultDetails = NULL;
15737 ClearGameInfo(&gameInfo);
15738 gameInfo.variant = v;
15740 switch (gameMode) {
15741 case MachinePlaysWhite:
15742 gameInfo.event = StrSave( appData.pgnEventHeader );
15743 gameInfo.site = StrSave(HostName());
15744 gameInfo.date = PGNDate();
15745 gameInfo.round = StrSave("-");
15746 gameInfo.white = StrSave(first.tidy);
15747 gameInfo.black = StrSave(UserName());
15748 gameInfo.timeControl = TimeControlTagValue();
15751 case MachinePlaysBlack:
15752 gameInfo.event = StrSave( appData.pgnEventHeader );
15753 gameInfo.site = StrSave(HostName());
15754 gameInfo.date = PGNDate();
15755 gameInfo.round = StrSave("-");
15756 gameInfo.white = StrSave(UserName());
15757 gameInfo.black = StrSave(first.tidy);
15758 gameInfo.timeControl = TimeControlTagValue();
15761 case TwoMachinesPlay:
15762 gameInfo.event = StrSave( appData.pgnEventHeader );
15763 gameInfo.site = StrSave(HostName());
15764 gameInfo.date = PGNDate();
15767 snprintf(buf, MSG_SIZ, "%d", roundNr);
15768 gameInfo.round = StrSave(buf);
15770 gameInfo.round = StrSave("-");
15772 if (first.twoMachinesColor[0] == 'w') {
15773 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15774 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15776 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15777 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15779 gameInfo.timeControl = TimeControlTagValue();
15783 gameInfo.event = StrSave("Edited game");
15784 gameInfo.site = StrSave(HostName());
15785 gameInfo.date = PGNDate();
15786 gameInfo.round = StrSave("-");
15787 gameInfo.white = StrSave("-");
15788 gameInfo.black = StrSave("-");
15789 gameInfo.result = r;
15790 gameInfo.resultDetails = p;
15794 gameInfo.event = StrSave("Edited position");
15795 gameInfo.site = StrSave(HostName());
15796 gameInfo.date = PGNDate();
15797 gameInfo.round = StrSave("-");
15798 gameInfo.white = StrSave("-");
15799 gameInfo.black = StrSave("-");
15802 case IcsPlayingWhite:
15803 case IcsPlayingBlack:
15808 case PlayFromGameFile:
15809 gameInfo.event = StrSave("Game from non-PGN file");
15810 gameInfo.site = StrSave(HostName());
15811 gameInfo.date = PGNDate();
15812 gameInfo.round = StrSave("-");
15813 gameInfo.white = StrSave("?");
15814 gameInfo.black = StrSave("?");
15823 ReplaceComment (int index, char *text)
15829 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15830 pvInfoList[index-1].depth == len &&
15831 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15832 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15833 while (*text == '\n') text++;
15834 len = strlen(text);
15835 while (len > 0 && text[len - 1] == '\n') len--;
15837 if (commentList[index] != NULL)
15838 free(commentList[index]);
15841 commentList[index] = NULL;
15844 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15845 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15846 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15847 commentList[index] = (char *) malloc(len + 2);
15848 strncpy(commentList[index], text, len);
15849 commentList[index][len] = '\n';
15850 commentList[index][len + 1] = NULLCHAR;
15852 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15854 commentList[index] = (char *) malloc(len + 7);
15855 safeStrCpy(commentList[index], "{\n", 3);
15856 safeStrCpy(commentList[index]+2, text, len+1);
15857 commentList[index][len+2] = NULLCHAR;
15858 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15859 strcat(commentList[index], "\n}\n");
15864 CrushCRs (char *text)
15872 if (ch == '\r') continue;
15874 } while (ch != '\0');
15878 AppendComment (int index, char *text, Boolean addBraces)
15879 /* addBraces tells if we should add {} */
15884 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15885 if(addBraces == 3) addBraces = 0; else // force appending literally
15886 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15889 while (*text == '\n') text++;
15890 len = strlen(text);
15891 while (len > 0 && text[len - 1] == '\n') len--;
15892 text[len] = NULLCHAR;
15894 if (len == 0) return;
15896 if (commentList[index] != NULL) {
15897 Boolean addClosingBrace = addBraces;
15898 old = commentList[index];
15899 oldlen = strlen(old);
15900 while(commentList[index][oldlen-1] == '\n')
15901 commentList[index][--oldlen] = NULLCHAR;
15902 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15903 safeStrCpy(commentList[index], old, oldlen + len + 6);
15905 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15906 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15907 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15908 while (*text == '\n') { text++; len--; }
15909 commentList[index][--oldlen] = NULLCHAR;
15911 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15912 else strcat(commentList[index], "\n");
15913 strcat(commentList[index], text);
15914 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15915 else strcat(commentList[index], "\n");
15917 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15919 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15920 else commentList[index][0] = NULLCHAR;
15921 strcat(commentList[index], text);
15922 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15923 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15928 FindStr (char * text, char * sub_text)
15930 char * result = strstr( text, sub_text );
15932 if( result != NULL ) {
15933 result += strlen( sub_text );
15939 /* [AS] Try to extract PV info from PGN comment */
15940 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15942 GetInfoFromComment (int index, char * text)
15944 char * sep = text, *p;
15946 if( text != NULL && index > 0 ) {
15949 int time = -1, sec = 0, deci;
15950 char * s_eval = FindStr( text, "[%eval " );
15951 char * s_emt = FindStr( text, "[%emt " );
15953 if( s_eval != NULL || s_emt != NULL ) {
15955 if(0) { // [HGM] this code is not finished, and could actually be detrimental
15960 if( s_eval != NULL ) {
15961 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15965 if( delim != ']' ) {
15970 if( s_emt != NULL ) {
15975 /* We expect something like: [+|-]nnn.nn/dd */
15978 if(*text != '{') return text; // [HGM] braces: must be normal comment
15980 sep = strchr( text, '/' );
15981 if( sep == NULL || sep < (text+4) ) {
15986 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15987 if(p[1] == '(') { // comment starts with PV
15988 p = strchr(p, ')'); // locate end of PV
15989 if(p == NULL || sep < p+5) return text;
15990 // at this point we have something like "{(.*) +0.23/6 ..."
15991 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15992 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15993 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15995 time = -1; sec = -1; deci = -1;
15996 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15997 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15998 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15999 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16003 if( score_lo < 0 || score_lo >= 100 ) {
16007 if(sec >= 0) time = 600*time + 10*sec; else
16008 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16010 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16012 /* [HGM] PV time: now locate end of PV info */
16013 while( *++sep >= '0' && *sep <= '9'); // strip depth
16015 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16017 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16019 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16020 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16031 pvInfoList[index-1].depth = depth;
16032 pvInfoList[index-1].score = score;
16033 pvInfoList[index-1].time = 10*time; // centi-sec
16034 if(*sep == '}') *sep = 0; else *--sep = '{';
16035 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16041 SendToProgram (char *message, ChessProgramState *cps)
16043 int count, outCount, error;
16046 if (cps->pr == NoProc) return;
16049 if (appData.debugMode) {
16052 fprintf(debugFP, "%ld >%-6s: %s",
16053 SubtractTimeMarks(&now, &programStartTime),
16054 cps->which, message);
16056 fprintf(serverFP, "%ld >%-6s: %s",
16057 SubtractTimeMarks(&now, &programStartTime),
16058 cps->which, message), fflush(serverFP);
16061 count = strlen(message);
16062 outCount = OutputToProcess(cps->pr, message, count, &error);
16063 if (outCount < count && !exiting
16064 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16065 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16066 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16067 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16068 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16069 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16070 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16071 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16073 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16074 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16075 gameInfo.result = res;
16077 gameInfo.resultDetails = StrSave(buf);
16079 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16080 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16085 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16089 ChessProgramState *cps = (ChessProgramState *)closure;
16091 if (isr != cps->isr) return; /* Killed intentionally */
16094 RemoveInputSource(cps->isr);
16095 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16096 _(cps->which), cps->program);
16097 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16098 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16099 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16100 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16101 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16102 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16104 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16105 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16106 gameInfo.result = res;
16108 gameInfo.resultDetails = StrSave(buf);
16110 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16111 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16113 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16114 _(cps->which), cps->program);
16115 RemoveInputSource(cps->isr);
16117 /* [AS] Program is misbehaving badly... kill it */
16118 if( count == -2 ) {
16119 DestroyChildProcess( cps->pr, 9 );
16123 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16128 if ((end_str = strchr(message, '\r')) != NULL)
16129 *end_str = NULLCHAR;
16130 if ((end_str = strchr(message, '\n')) != NULL)
16131 *end_str = NULLCHAR;
16133 if (appData.debugMode) {
16134 TimeMark now; int print = 1;
16135 char *quote = ""; char c; int i;
16137 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16138 char start = message[0];
16139 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16140 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16141 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16142 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16143 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16144 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16145 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16146 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16147 sscanf(message, "hint: %c", &c)!=1 &&
16148 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16149 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16150 print = (appData.engineComments >= 2);
16152 message[0] = start; // restore original message
16156 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16157 SubtractTimeMarks(&now, &programStartTime), cps->which,
16161 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16162 SubtractTimeMarks(&now, &programStartTime), cps->which,
16164 message), fflush(serverFP);
16168 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16169 if (appData.icsEngineAnalyze) {
16170 if (strstr(message, "whisper") != NULL ||
16171 strstr(message, "kibitz") != NULL ||
16172 strstr(message, "tellics") != NULL) return;
16175 HandleMachineMove(message, cps);
16180 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16185 if( timeControl_2 > 0 ) {
16186 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16187 tc = timeControl_2;
16190 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16191 inc /= cps->timeOdds;
16192 st /= cps->timeOdds;
16194 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16197 /* Set exact time per move, normally using st command */
16198 if (cps->stKludge) {
16199 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16201 if (seconds == 0) {
16202 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16204 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16207 snprintf(buf, MSG_SIZ, "st %d\n", st);
16210 /* Set conventional or incremental time control, using level command */
16211 if (seconds == 0) {
16212 /* Note old gnuchess bug -- minutes:seconds used to not work.
16213 Fixed in later versions, but still avoid :seconds
16214 when seconds is 0. */
16215 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16217 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16218 seconds, inc/1000.);
16221 SendToProgram(buf, cps);
16223 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16224 /* Orthogonally, limit search to given depth */
16226 if (cps->sdKludge) {
16227 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16229 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16231 SendToProgram(buf, cps);
16234 if(cps->nps >= 0) { /* [HGM] nps */
16235 if(cps->supportsNPS == FALSE)
16236 cps->nps = -1; // don't use if engine explicitly says not supported!
16238 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16239 SendToProgram(buf, cps);
16244 ChessProgramState *
16246 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16248 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16249 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16255 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16257 char message[MSG_SIZ];
16260 /* Note: this routine must be called when the clocks are stopped
16261 or when they have *just* been set or switched; otherwise
16262 it will be off by the time since the current tick started.
16264 if (machineWhite) {
16265 time = whiteTimeRemaining / 10;
16266 otime = blackTimeRemaining / 10;
16268 time = blackTimeRemaining / 10;
16269 otime = whiteTimeRemaining / 10;
16271 /* [HGM] translate opponent's time by time-odds factor */
16272 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16274 if (time <= 0) time = 1;
16275 if (otime <= 0) otime = 1;
16277 snprintf(message, MSG_SIZ, "time %ld\n", time);
16278 SendToProgram(message, cps);
16280 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16281 SendToProgram(message, cps);
16285 EngineDefinedVariant (ChessProgramState *cps, int n)
16286 { // return name of n-th unknown variant that engine supports
16287 static char buf[MSG_SIZ];
16288 char *p, *s = cps->variants;
16289 if(!s) return NULL;
16290 do { // parse string from variants feature
16292 p = strchr(s, ',');
16293 if(p) *p = NULLCHAR;
16294 v = StringToVariant(s);
16295 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16296 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16297 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16300 if(n < 0) return buf;
16306 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16309 int len = strlen(name);
16312 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16314 sscanf(*p, "%d", &val);
16316 while (**p && **p != ' ')
16318 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16319 SendToProgram(buf, cps);
16326 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16329 int len = strlen(name);
16330 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16332 sscanf(*p, "%d", loc);
16333 while (**p && **p != ' ') (*p)++;
16334 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16335 SendToProgram(buf, cps);
16342 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16345 int len = strlen(name);
16346 if (strncmp((*p), name, len) == 0
16347 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16349 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16350 sscanf(*p, "%[^\"]", *loc);
16351 while (**p && **p != '\"') (*p)++;
16352 if (**p == '\"') (*p)++;
16353 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16354 SendToProgram(buf, cps);
16361 ParseOption (Option *opt, ChessProgramState *cps)
16362 // [HGM] options: process the string that defines an engine option, and determine
16363 // name, type, default value, and allowed value range
16365 char *p, *q, buf[MSG_SIZ];
16366 int n, min = (-1)<<31, max = 1<<31, def;
16368 if(p = strstr(opt->name, " -spin ")) {
16369 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16370 if(max < min) max = min; // enforce consistency
16371 if(def < min) def = min;
16372 if(def > max) def = max;
16377 } else if((p = strstr(opt->name, " -slider "))) {
16378 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16379 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16380 if(max < min) max = min; // enforce consistency
16381 if(def < min) def = min;
16382 if(def > max) def = max;
16386 opt->type = Spin; // Slider;
16387 } else if((p = strstr(opt->name, " -string "))) {
16388 opt->textValue = p+9;
16389 opt->type = TextBox;
16390 } else if((p = strstr(opt->name, " -file "))) {
16391 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16392 opt->textValue = p+7;
16393 opt->type = FileName; // FileName;
16394 } else if((p = strstr(opt->name, " -path "))) {
16395 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16396 opt->textValue = p+7;
16397 opt->type = PathName; // PathName;
16398 } else if(p = strstr(opt->name, " -check ")) {
16399 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16400 opt->value = (def != 0);
16401 opt->type = CheckBox;
16402 } else if(p = strstr(opt->name, " -combo ")) {
16403 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16404 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16405 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16406 opt->value = n = 0;
16407 while(q = StrStr(q, " /// ")) {
16408 n++; *q = 0; // count choices, and null-terminate each of them
16410 if(*q == '*') { // remember default, which is marked with * prefix
16414 cps->comboList[cps->comboCnt++] = q;
16416 cps->comboList[cps->comboCnt++] = NULL;
16418 opt->type = ComboBox;
16419 } else if(p = strstr(opt->name, " -button")) {
16420 opt->type = Button;
16421 } else if(p = strstr(opt->name, " -save")) {
16422 opt->type = SaveButton;
16423 } else return FALSE;
16424 *p = 0; // terminate option name
16425 // now look if the command-line options define a setting for this engine option.
16426 if(cps->optionSettings && cps->optionSettings[0])
16427 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16428 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16429 snprintf(buf, MSG_SIZ, "option %s", p);
16430 if(p = strstr(buf, ",")) *p = 0;
16431 if(q = strchr(buf, '=')) switch(opt->type) {
16433 for(n=0; n<opt->max; n++)
16434 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16437 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16441 opt->value = atoi(q+1);
16446 SendToProgram(buf, cps);
16452 FeatureDone (ChessProgramState *cps, int val)
16454 DelayedEventCallback cb = GetDelayedEvent();
16455 if ((cb == InitBackEnd3 && cps == &first) ||
16456 (cb == SettingsMenuIfReady && cps == &second) ||
16457 (cb == LoadEngine) ||
16458 (cb == TwoMachinesEventIfReady)) {
16459 CancelDelayedEvent();
16460 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16462 cps->initDone = val;
16463 if(val) cps->reload = FALSE;
16466 /* Parse feature command from engine */
16468 ParseFeatures (char *args, ChessProgramState *cps)
16476 while (*p == ' ') p++;
16477 if (*p == NULLCHAR) return;
16479 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16480 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16481 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16482 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16483 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16484 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16485 if (BoolFeature(&p, "reuse", &val, cps)) {
16486 /* Engine can disable reuse, but can't enable it if user said no */
16487 if (!val) cps->reuse = FALSE;
16490 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16491 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16492 if (gameMode == TwoMachinesPlay) {
16493 DisplayTwoMachinesTitle();
16499 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16500 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16501 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16502 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16503 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16504 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16505 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16506 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16507 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16508 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16509 if (IntFeature(&p, "done", &val, cps)) {
16510 FeatureDone(cps, val);
16513 /* Added by Tord: */
16514 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16515 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16516 /* End of additions by Tord */
16518 /* [HGM] added features: */
16519 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16520 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16521 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16522 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16523 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16524 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16525 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16526 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16527 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16528 FREE(cps->option[cps->nrOptions].name);
16529 cps->option[cps->nrOptions].name = q; q = NULL;
16530 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16531 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16532 SendToProgram(buf, cps);
16535 if(cps->nrOptions >= MAX_OPTIONS) {
16537 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16538 DisplayError(buf, 0);
16542 /* End of additions by HGM */
16544 /* unknown feature: complain and skip */
16546 while (*q && *q != '=') q++;
16547 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16548 SendToProgram(buf, cps);
16554 while (*p && *p != '\"') p++;
16555 if (*p == '\"') p++;
16557 while (*p && *p != ' ') p++;
16565 PeriodicUpdatesEvent (int newState)
16567 if (newState == appData.periodicUpdates)
16570 appData.periodicUpdates=newState;
16572 /* Display type changes, so update it now */
16573 // DisplayAnalysis();
16575 /* Get the ball rolling again... */
16577 AnalysisPeriodicEvent(1);
16578 StartAnalysisClock();
16583 PonderNextMoveEvent (int newState)
16585 if (newState == appData.ponderNextMove) return;
16586 if (gameMode == EditPosition) EditPositionDone(TRUE);
16588 SendToProgram("hard\n", &first);
16589 if (gameMode == TwoMachinesPlay) {
16590 SendToProgram("hard\n", &second);
16593 SendToProgram("easy\n", &first);
16594 thinkOutput[0] = NULLCHAR;
16595 if (gameMode == TwoMachinesPlay) {
16596 SendToProgram("easy\n", &second);
16599 appData.ponderNextMove = newState;
16603 NewSettingEvent (int option, int *feature, char *command, int value)
16607 if (gameMode == EditPosition) EditPositionDone(TRUE);
16608 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16609 if(feature == NULL || *feature) SendToProgram(buf, &first);
16610 if (gameMode == TwoMachinesPlay) {
16611 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16616 ShowThinkingEvent ()
16617 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16619 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16620 int newState = appData.showThinking
16621 // [HGM] thinking: other features now need thinking output as well
16622 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16624 if (oldState == newState) return;
16625 oldState = newState;
16626 if (gameMode == EditPosition) EditPositionDone(TRUE);
16628 SendToProgram("post\n", &first);
16629 if (gameMode == TwoMachinesPlay) {
16630 SendToProgram("post\n", &second);
16633 SendToProgram("nopost\n", &first);
16634 thinkOutput[0] = NULLCHAR;
16635 if (gameMode == TwoMachinesPlay) {
16636 SendToProgram("nopost\n", &second);
16639 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16643 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16645 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16646 if (pr == NoProc) return;
16647 AskQuestion(title, question, replyPrefix, pr);
16651 TypeInEvent (char firstChar)
16653 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16654 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16655 gameMode == AnalyzeMode || gameMode == EditGame ||
16656 gameMode == EditPosition || gameMode == IcsExamining ||
16657 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16658 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16659 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16660 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16661 gameMode == Training) PopUpMoveDialog(firstChar);
16665 TypeInDoneEvent (char *move)
16668 int n, fromX, fromY, toX, toY;
16670 ChessMove moveType;
16673 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16674 EditPositionPasteFEN(move);
16677 // [HGM] movenum: allow move number to be typed in any mode
16678 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16682 // undocumented kludge: allow command-line option to be typed in!
16683 // (potentially fatal, and does not implement the effect of the option.)
16684 // should only be used for options that are values on which future decisions will be made,
16685 // and definitely not on options that would be used during initialization.
16686 if(strstr(move, "!!! -") == move) {
16687 ParseArgsFromString(move+4);
16691 if (gameMode != EditGame && currentMove != forwardMostMove &&
16692 gameMode != Training) {
16693 DisplayMoveError(_("Displayed move is not current"));
16695 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16696 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16697 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16698 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16699 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16700 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16702 DisplayMoveError(_("Could not parse move"));
16708 DisplayMove (int moveNumber)
16710 char message[MSG_SIZ];
16712 char cpThinkOutput[MSG_SIZ];
16714 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16716 if (moveNumber == forwardMostMove - 1 ||
16717 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16719 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16721 if (strchr(cpThinkOutput, '\n')) {
16722 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16725 *cpThinkOutput = NULLCHAR;
16728 /* [AS] Hide thinking from human user */
16729 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16730 *cpThinkOutput = NULLCHAR;
16731 if( thinkOutput[0] != NULLCHAR ) {
16734 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16735 cpThinkOutput[i] = '.';
16737 cpThinkOutput[i] = NULLCHAR;
16738 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16742 if (moveNumber == forwardMostMove - 1 &&
16743 gameInfo.resultDetails != NULL) {
16744 if (gameInfo.resultDetails[0] == NULLCHAR) {
16745 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16747 snprintf(res, MSG_SIZ, " {%s} %s",
16748 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16754 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16755 DisplayMessage(res, cpThinkOutput);
16757 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16758 WhiteOnMove(moveNumber) ? " " : ".. ",
16759 parseList[moveNumber], res);
16760 DisplayMessage(message, cpThinkOutput);
16765 DisplayComment (int moveNumber, char *text)
16767 char title[MSG_SIZ];
16769 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16770 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16772 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16773 WhiteOnMove(moveNumber) ? " " : ".. ",
16774 parseList[moveNumber]);
16776 if (text != NULL && (appData.autoDisplayComment || commentUp))
16777 CommentPopUp(title, text);
16780 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16781 * might be busy thinking or pondering. It can be omitted if your
16782 * gnuchess is configured to stop thinking immediately on any user
16783 * input. However, that gnuchess feature depends on the FIONREAD
16784 * ioctl, which does not work properly on some flavors of Unix.
16787 Attention (ChessProgramState *cps)
16790 if (!cps->useSigint) return;
16791 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16792 switch (gameMode) {
16793 case MachinePlaysWhite:
16794 case MachinePlaysBlack:
16795 case TwoMachinesPlay:
16796 case IcsPlayingWhite:
16797 case IcsPlayingBlack:
16800 /* Skip if we know it isn't thinking */
16801 if (!cps->maybeThinking) return;
16802 if (appData.debugMode)
16803 fprintf(debugFP, "Interrupting %s\n", cps->which);
16804 InterruptChildProcess(cps->pr);
16805 cps->maybeThinking = FALSE;
16810 #endif /*ATTENTION*/
16816 if (whiteTimeRemaining <= 0) {
16819 if (appData.icsActive) {
16820 if (appData.autoCallFlag &&
16821 gameMode == IcsPlayingBlack && !blackFlag) {
16822 SendToICS(ics_prefix);
16823 SendToICS("flag\n");
16827 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16829 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16830 if (appData.autoCallFlag) {
16831 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16838 if (blackTimeRemaining <= 0) {
16841 if (appData.icsActive) {
16842 if (appData.autoCallFlag &&
16843 gameMode == IcsPlayingWhite && !whiteFlag) {
16844 SendToICS(ics_prefix);
16845 SendToICS("flag\n");
16849 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16851 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16852 if (appData.autoCallFlag) {
16853 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16864 CheckTimeControl ()
16866 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16867 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16870 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16872 if ( !WhiteOnMove(forwardMostMove) ) {
16873 /* White made time control */
16874 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16875 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16876 /* [HGM] time odds: correct new time quota for time odds! */
16877 / WhitePlayer()->timeOdds;
16878 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16880 lastBlack -= blackTimeRemaining;
16881 /* Black made time control */
16882 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16883 / WhitePlayer()->other->timeOdds;
16884 lastWhite = whiteTimeRemaining;
16889 DisplayBothClocks ()
16891 int wom = gameMode == EditPosition ?
16892 !blackPlaysFirst : WhiteOnMove(currentMove);
16893 DisplayWhiteClock(whiteTimeRemaining, wom);
16894 DisplayBlackClock(blackTimeRemaining, !wom);
16898 /* Timekeeping seems to be a portability nightmare. I think everyone
16899 has ftime(), but I'm really not sure, so I'm including some ifdefs
16900 to use other calls if you don't. Clocks will be less accurate if
16901 you have neither ftime nor gettimeofday.
16904 /* VS 2008 requires the #include outside of the function */
16905 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16906 #include <sys/timeb.h>
16909 /* Get the current time as a TimeMark */
16911 GetTimeMark (TimeMark *tm)
16913 #if HAVE_GETTIMEOFDAY
16915 struct timeval timeVal;
16916 struct timezone timeZone;
16918 gettimeofday(&timeVal, &timeZone);
16919 tm->sec = (long) timeVal.tv_sec;
16920 tm->ms = (int) (timeVal.tv_usec / 1000L);
16922 #else /*!HAVE_GETTIMEOFDAY*/
16925 // include <sys/timeb.h> / moved to just above start of function
16926 struct timeb timeB;
16929 tm->sec = (long) timeB.time;
16930 tm->ms = (int) timeB.millitm;
16932 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16933 tm->sec = (long) time(NULL);
16939 /* Return the difference in milliseconds between two
16940 time marks. We assume the difference will fit in a long!
16943 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16945 return 1000L*(tm2->sec - tm1->sec) +
16946 (long) (tm2->ms - tm1->ms);
16951 * Code to manage the game clocks.
16953 * In tournament play, black starts the clock and then white makes a move.
16954 * We give the human user a slight advantage if he is playing white---the
16955 * clocks don't run until he makes his first move, so it takes zero time.
16956 * Also, we don't account for network lag, so we could get out of sync
16957 * with GNU Chess's clock -- but then, referees are always right.
16960 static TimeMark tickStartTM;
16961 static long intendedTickLength;
16964 NextTickLength (long timeRemaining)
16966 long nominalTickLength, nextTickLength;
16968 if (timeRemaining > 0L && timeRemaining <= 10000L)
16969 nominalTickLength = 100L;
16971 nominalTickLength = 1000L;
16972 nextTickLength = timeRemaining % nominalTickLength;
16973 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16975 return nextTickLength;
16978 /* Adjust clock one minute up or down */
16980 AdjustClock (Boolean which, int dir)
16982 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16983 if(which) blackTimeRemaining += 60000*dir;
16984 else whiteTimeRemaining += 60000*dir;
16985 DisplayBothClocks();
16986 adjustedClock = TRUE;
16989 /* Stop clocks and reset to a fresh time control */
16993 (void) StopClockTimer();
16994 if (appData.icsActive) {
16995 whiteTimeRemaining = blackTimeRemaining = 0;
16996 } else if (searchTime) {
16997 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16998 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16999 } else { /* [HGM] correct new time quote for time odds */
17000 whiteTC = blackTC = fullTimeControlString;
17001 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17002 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17004 if (whiteFlag || blackFlag) {
17006 whiteFlag = blackFlag = FALSE;
17008 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17009 DisplayBothClocks();
17010 adjustedClock = FALSE;
17013 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17015 /* Decrement running clock by amount of time that has passed */
17019 long timeRemaining;
17020 long lastTickLength, fudge;
17023 if (!appData.clockMode) return;
17024 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17028 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17030 /* Fudge if we woke up a little too soon */
17031 fudge = intendedTickLength - lastTickLength;
17032 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17034 if (WhiteOnMove(forwardMostMove)) {
17035 if(whiteNPS >= 0) lastTickLength = 0;
17036 timeRemaining = whiteTimeRemaining -= lastTickLength;
17037 if(timeRemaining < 0 && !appData.icsActive) {
17038 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17039 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17040 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17041 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17044 DisplayWhiteClock(whiteTimeRemaining - fudge,
17045 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17047 if(blackNPS >= 0) lastTickLength = 0;
17048 timeRemaining = blackTimeRemaining -= lastTickLength;
17049 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17050 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17052 blackStartMove = forwardMostMove;
17053 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17056 DisplayBlackClock(blackTimeRemaining - fudge,
17057 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17059 if (CheckFlags()) return;
17061 if(twoBoards) { // count down secondary board's clocks as well
17062 activePartnerTime -= lastTickLength;
17064 if(activePartner == 'W')
17065 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17067 DisplayBlackClock(activePartnerTime, TRUE);
17072 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17073 StartClockTimer(intendedTickLength);
17075 /* if the time remaining has fallen below the alarm threshold, sound the
17076 * alarm. if the alarm has sounded and (due to a takeback or time control
17077 * with increment) the time remaining has increased to a level above the
17078 * threshold, reset the alarm so it can sound again.
17081 if (appData.icsActive && appData.icsAlarm) {
17083 /* make sure we are dealing with the user's clock */
17084 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17085 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17088 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17089 alarmSounded = FALSE;
17090 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17092 alarmSounded = TRUE;
17098 /* A player has just moved, so stop the previously running
17099 clock and (if in clock mode) start the other one.
17100 We redisplay both clocks in case we're in ICS mode, because
17101 ICS gives us an update to both clocks after every move.
17102 Note that this routine is called *after* forwardMostMove
17103 is updated, so the last fractional tick must be subtracted
17104 from the color that is *not* on move now.
17107 SwitchClocks (int newMoveNr)
17109 long lastTickLength;
17111 int flagged = FALSE;
17115 if (StopClockTimer() && appData.clockMode) {
17116 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17117 if (!WhiteOnMove(forwardMostMove)) {
17118 if(blackNPS >= 0) lastTickLength = 0;
17119 blackTimeRemaining -= lastTickLength;
17120 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17121 // if(pvInfoList[forwardMostMove].time == -1)
17122 pvInfoList[forwardMostMove].time = // use GUI time
17123 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17125 if(whiteNPS >= 0) lastTickLength = 0;
17126 whiteTimeRemaining -= lastTickLength;
17127 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17128 // if(pvInfoList[forwardMostMove].time == -1)
17129 pvInfoList[forwardMostMove].time =
17130 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17132 flagged = CheckFlags();
17134 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17135 CheckTimeControl();
17137 if (flagged || !appData.clockMode) return;
17139 switch (gameMode) {
17140 case MachinePlaysBlack:
17141 case MachinePlaysWhite:
17142 case BeginningOfGame:
17143 if (pausing) return;
17147 case PlayFromGameFile:
17155 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17156 if(WhiteOnMove(forwardMostMove))
17157 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17158 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17162 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17163 whiteTimeRemaining : blackTimeRemaining);
17164 StartClockTimer(intendedTickLength);
17168 /* Stop both clocks */
17172 long lastTickLength;
17175 if (!StopClockTimer()) return;
17176 if (!appData.clockMode) return;
17180 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17181 if (WhiteOnMove(forwardMostMove)) {
17182 if(whiteNPS >= 0) lastTickLength = 0;
17183 whiteTimeRemaining -= lastTickLength;
17184 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17186 if(blackNPS >= 0) lastTickLength = 0;
17187 blackTimeRemaining -= lastTickLength;
17188 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17193 /* Start clock of player on move. Time may have been reset, so
17194 if clock is already running, stop and restart it. */
17198 (void) StopClockTimer(); /* in case it was running already */
17199 DisplayBothClocks();
17200 if (CheckFlags()) return;
17202 if (!appData.clockMode) return;
17203 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17205 GetTimeMark(&tickStartTM);
17206 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17207 whiteTimeRemaining : blackTimeRemaining);
17209 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17210 whiteNPS = blackNPS = -1;
17211 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17212 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17213 whiteNPS = first.nps;
17214 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17215 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17216 blackNPS = first.nps;
17217 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17218 whiteNPS = second.nps;
17219 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17220 blackNPS = second.nps;
17221 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17223 StartClockTimer(intendedTickLength);
17227 TimeString (long ms)
17229 long second, minute, hour, day;
17231 static char buf[32];
17233 if (ms > 0 && ms <= 9900) {
17234 /* convert milliseconds to tenths, rounding up */
17235 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17237 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17241 /* convert milliseconds to seconds, rounding up */
17242 /* use floating point to avoid strangeness of integer division
17243 with negative dividends on many machines */
17244 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17251 day = second / (60 * 60 * 24);
17252 second = second % (60 * 60 * 24);
17253 hour = second / (60 * 60);
17254 second = second % (60 * 60);
17255 minute = second / 60;
17256 second = second % 60;
17259 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17260 sign, day, hour, minute, second);
17262 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17264 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17271 * This is necessary because some C libraries aren't ANSI C compliant yet.
17274 StrStr (char *string, char *match)
17278 length = strlen(match);
17280 for (i = strlen(string) - length; i >= 0; i--, string++)
17281 if (!strncmp(match, string, length))
17288 StrCaseStr (char *string, char *match)
17292 length = strlen(match);
17294 for (i = strlen(string) - length; i >= 0; i--, string++) {
17295 for (j = 0; j < length; j++) {
17296 if (ToLower(match[j]) != ToLower(string[j]))
17299 if (j == length) return string;
17307 StrCaseCmp (char *s1, char *s2)
17312 c1 = ToLower(*s1++);
17313 c2 = ToLower(*s2++);
17314 if (c1 > c2) return 1;
17315 if (c1 < c2) return -1;
17316 if (c1 == NULLCHAR) return 0;
17324 return isupper(c) ? tolower(c) : c;
17331 return islower(c) ? toupper(c) : c;
17333 #endif /* !_amigados */
17340 if ((ret = (char *) malloc(strlen(s) + 1)))
17342 safeStrCpy(ret, s, strlen(s)+1);
17348 StrSavePtr (char *s, char **savePtr)
17353 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17354 safeStrCpy(*savePtr, s, strlen(s)+1);
17366 clock = time((time_t *)NULL);
17367 tm = localtime(&clock);
17368 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17369 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17370 return StrSave(buf);
17375 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17377 int i, j, fromX, fromY, toX, toY;
17384 whiteToPlay = (gameMode == EditPosition) ?
17385 !blackPlaysFirst : (move % 2 == 0);
17388 /* Piece placement data */
17389 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17390 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17392 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17393 if (boards[move][i][j] == EmptySquare) {
17395 } else { ChessSquare piece = boards[move][i][j];
17396 if (emptycount > 0) {
17397 if(emptycount<10) /* [HGM] can be >= 10 */
17398 *p++ = '0' + emptycount;
17399 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17402 if(PieceToChar(piece) == '+') {
17403 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17405 piece = (ChessSquare)(DEMOTED piece);
17407 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17409 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17410 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17415 if (emptycount > 0) {
17416 if(emptycount<10) /* [HGM] can be >= 10 */
17417 *p++ = '0' + emptycount;
17418 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17425 /* [HGM] print Crazyhouse or Shogi holdings */
17426 if( gameInfo.holdingsWidth ) {
17427 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17429 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17430 piece = boards[move][i][BOARD_WIDTH-1];
17431 if( piece != EmptySquare )
17432 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17433 *p++ = PieceToChar(piece);
17435 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17436 piece = boards[move][BOARD_HEIGHT-i-1][0];
17437 if( piece != EmptySquare )
17438 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17439 *p++ = PieceToChar(piece);
17442 if( q == p ) *p++ = '-';
17448 *p++ = whiteToPlay ? 'w' : 'b';
17451 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17452 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17454 if(nrCastlingRights) {
17456 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17457 /* [HGM] write directly from rights */
17458 if(boards[move][CASTLING][2] != NoRights &&
17459 boards[move][CASTLING][0] != NoRights )
17460 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17461 if(boards[move][CASTLING][2] != NoRights &&
17462 boards[move][CASTLING][1] != NoRights )
17463 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17464 if(boards[move][CASTLING][5] != NoRights &&
17465 boards[move][CASTLING][3] != NoRights )
17466 *p++ = boards[move][CASTLING][3] + AAA;
17467 if(boards[move][CASTLING][5] != NoRights &&
17468 boards[move][CASTLING][4] != NoRights )
17469 *p++ = boards[move][CASTLING][4] + AAA;
17472 /* [HGM] write true castling rights */
17473 if( nrCastlingRights == 6 ) {
17475 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17476 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17477 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17478 boards[move][CASTLING][2] != NoRights );
17479 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17480 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17481 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17482 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17483 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17487 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17488 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17489 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17490 boards[move][CASTLING][5] != NoRights );
17491 if(gameInfo.variant == VariantSChess) {
17492 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17493 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17494 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17495 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17500 if (q == p) *p++ = '-'; /* No castling rights */
17504 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17505 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17506 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17507 /* En passant target square */
17508 if (move > backwardMostMove) {
17509 fromX = moveList[move - 1][0] - AAA;
17510 fromY = moveList[move - 1][1] - ONE;
17511 toX = moveList[move - 1][2] - AAA;
17512 toY = moveList[move - 1][3] - ONE;
17513 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17514 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17515 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17517 /* 2-square pawn move just happened */
17519 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17523 } else if(move == backwardMostMove) {
17524 // [HGM] perhaps we should always do it like this, and forget the above?
17525 if((signed char)boards[move][EP_STATUS] >= 0) {
17526 *p++ = boards[move][EP_STATUS] + AAA;
17527 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17539 { int i = 0, j=move;
17541 /* [HGM] find reversible plies */
17542 if (appData.debugMode) { int k;
17543 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17544 for(k=backwardMostMove; k<=forwardMostMove; k++)
17545 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17549 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17550 if( j == backwardMostMove ) i += initialRulePlies;
17551 sprintf(p, "%d ", i);
17552 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17554 /* Fullmove number */
17555 sprintf(p, "%d", (move / 2) + 1);
17556 } else *--p = NULLCHAR;
17558 return StrSave(buf);
17562 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17566 int emptycount, virgin[BOARD_FILES];
17571 /* Piece placement data */
17572 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17575 if (*p == '/' || *p == ' ' || *p == '[' ) {
17577 emptycount = gameInfo.boardWidth - j;
17578 while (emptycount--)
17579 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17580 if (*p == '/') p++;
17581 else if(autoSize) { // we stumbled unexpectedly into end of board
17582 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17583 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17585 appData.NrRanks = gameInfo.boardHeight - i; i=0;
17588 #if(BOARD_FILES >= 10)
17589 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17590 p++; emptycount=10;
17591 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17592 while (emptycount--)
17593 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17595 } else if (*p == '*') {
17596 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17597 } else if (isdigit(*p)) {
17598 emptycount = *p++ - '0';
17599 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17600 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17601 while (emptycount--)
17602 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17603 } else if (*p == '+' || isalpha(*p)) {
17604 if (j >= gameInfo.boardWidth) return FALSE;
17606 piece = CharToPiece(*++p);
17607 if(piece == EmptySquare) return FALSE; /* unknown piece */
17608 piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17609 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17610 } else piece = CharToPiece(*p++);
17612 if(piece==EmptySquare) return FALSE; /* unknown piece */
17613 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17614 piece = (ChessSquare) (PROMOTED piece);
17615 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17618 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17624 while (*p == '/' || *p == ' ') p++;
17626 if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17628 /* [HGM] by default clear Crazyhouse holdings, if present */
17629 if(gameInfo.holdingsWidth) {
17630 for(i=0; i<BOARD_HEIGHT; i++) {
17631 board[i][0] = EmptySquare; /* black holdings */
17632 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17633 board[i][1] = (ChessSquare) 0; /* black counts */
17634 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17638 /* [HGM] look for Crazyhouse holdings here */
17639 while(*p==' ') p++;
17640 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17642 if(*p == '-' ) p++; /* empty holdings */ else {
17643 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17644 /* if we would allow FEN reading to set board size, we would */
17645 /* have to add holdings and shift the board read so far here */
17646 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17648 if((int) piece >= (int) BlackPawn ) {
17649 i = (int)piece - (int)BlackPawn;
17650 i = PieceToNumber((ChessSquare)i);
17651 if( i >= gameInfo.holdingsSize ) return FALSE;
17652 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17653 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17655 i = (int)piece - (int)WhitePawn;
17656 i = PieceToNumber((ChessSquare)i);
17657 if( i >= gameInfo.holdingsSize ) return FALSE;
17658 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17659 board[i][BOARD_WIDTH-2]++; /* black holdings */
17666 while(*p == ' ') p++;
17670 if(appData.colorNickNames) {
17671 if( c == appData.colorNickNames[0] ) c = 'w'; else
17672 if( c == appData.colorNickNames[1] ) c = 'b';
17676 *blackPlaysFirst = FALSE;
17679 *blackPlaysFirst = TRUE;
17685 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17686 /* return the extra info in global variiables */
17688 /* set defaults in case FEN is incomplete */
17689 board[EP_STATUS] = EP_UNKNOWN;
17690 for(i=0; i<nrCastlingRights; i++ ) {
17691 board[CASTLING][i] =
17692 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17693 } /* assume possible unless obviously impossible */
17694 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17695 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17696 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17697 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17698 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17699 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17700 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17701 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17704 while(*p==' ') p++;
17705 if(nrCastlingRights) {
17706 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17707 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17708 /* castling indicator present, so default becomes no castlings */
17709 for(i=0; i<nrCastlingRights; i++ ) {
17710 board[CASTLING][i] = NoRights;
17713 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17714 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17715 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17716 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17717 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17719 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17720 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17721 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17723 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17724 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17725 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17726 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17727 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17728 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17731 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17732 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17733 board[CASTLING][2] = whiteKingFile;
17734 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17735 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17738 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17739 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17740 board[CASTLING][2] = whiteKingFile;
17741 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17742 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17745 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17746 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17747 board[CASTLING][5] = blackKingFile;
17748 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17749 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17752 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17753 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17754 board[CASTLING][5] = blackKingFile;
17755 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17756 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17759 default: /* FRC castlings */
17760 if(c >= 'a') { /* black rights */
17761 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17762 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17763 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17764 if(i == BOARD_RGHT) break;
17765 board[CASTLING][5] = i;
17767 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17768 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17770 board[CASTLING][3] = c;
17772 board[CASTLING][4] = c;
17773 } else { /* white rights */
17774 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17775 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17776 if(board[0][i] == WhiteKing) break;
17777 if(i == BOARD_RGHT) break;
17778 board[CASTLING][2] = i;
17779 c -= AAA - 'a' + 'A';
17780 if(board[0][c] >= WhiteKing) break;
17782 board[CASTLING][0] = c;
17784 board[CASTLING][1] = c;
17788 for(i=0; i<nrCastlingRights; i++)
17789 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17790 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17791 if (appData.debugMode) {
17792 fprintf(debugFP, "FEN castling rights:");
17793 for(i=0; i<nrCastlingRights; i++)
17794 fprintf(debugFP, " %d", board[CASTLING][i]);
17795 fprintf(debugFP, "\n");
17798 while(*p==' ') p++;
17801 /* read e.p. field in games that know e.p. capture */
17802 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17803 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17804 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17806 p++; board[EP_STATUS] = EP_NONE;
17808 char c = *p++ - AAA;
17810 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17811 if(*p >= '0' && *p <='9') p++;
17812 board[EP_STATUS] = c;
17817 if(sscanf(p, "%d", &i) == 1) {
17818 FENrulePlies = i; /* 50-move ply counter */
17819 /* (The move number is still ignored) */
17826 EditPositionPasteFEN (char *fen)
17829 Board initial_position;
17831 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17832 DisplayError(_("Bad FEN position in clipboard"), 0);
17835 int savedBlackPlaysFirst = blackPlaysFirst;
17836 EditPositionEvent();
17837 blackPlaysFirst = savedBlackPlaysFirst;
17838 CopyBoard(boards[0], initial_position);
17839 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17840 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17841 DisplayBothClocks();
17842 DrawPosition(FALSE, boards[currentMove]);
17847 static char cseq[12] = "\\ ";
17850 set_cont_sequence (char *new_seq)
17855 // handle bad attempts to set the sequence
17857 return 0; // acceptable error - no debug
17859 len = strlen(new_seq);
17860 ret = (len > 0) && (len < sizeof(cseq));
17862 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17863 else if (appData.debugMode)
17864 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17869 reformat a source message so words don't cross the width boundary. internal
17870 newlines are not removed. returns the wrapped size (no null character unless
17871 included in source message). If dest is NULL, only calculate the size required
17872 for the dest buffer. lp argument indicats line position upon entry, and it's
17873 passed back upon exit.
17876 wrap (char *dest, char *src, int count, int width, int *lp)
17878 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17880 cseq_len = strlen(cseq);
17881 old_line = line = *lp;
17882 ansi = len = clen = 0;
17884 for (i=0; i < count; i++)
17886 if (src[i] == '\033')
17889 // if we hit the width, back up
17890 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17892 // store i & len in case the word is too long
17893 old_i = i, old_len = len;
17895 // find the end of the last word
17896 while (i && src[i] != ' ' && src[i] != '\n')
17902 // word too long? restore i & len before splitting it
17903 if ((old_i-i+clen) >= width)
17910 if (i && src[i-1] == ' ')
17913 if (src[i] != ' ' && src[i] != '\n')
17920 // now append the newline and continuation sequence
17925 strncpy(dest+len, cseq, cseq_len);
17933 dest[len] = src[i];
17937 if (src[i] == '\n')
17942 if (dest && appData.debugMode)
17944 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17945 count, width, line, len, *lp);
17946 show_bytes(debugFP, src, count);
17947 fprintf(debugFP, "\ndest: ");
17948 show_bytes(debugFP, dest, len);
17949 fprintf(debugFP, "\n");
17951 *lp = dest ? line : old_line;
17956 // [HGM] vari: routines for shelving variations
17957 Boolean modeRestore = FALSE;
17960 PushInner (int firstMove, int lastMove)
17962 int i, j, nrMoves = lastMove - firstMove;
17964 // push current tail of game on stack
17965 savedResult[storedGames] = gameInfo.result;
17966 savedDetails[storedGames] = gameInfo.resultDetails;
17967 gameInfo.resultDetails = NULL;
17968 savedFirst[storedGames] = firstMove;
17969 savedLast [storedGames] = lastMove;
17970 savedFramePtr[storedGames] = framePtr;
17971 framePtr -= nrMoves; // reserve space for the boards
17972 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17973 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17974 for(j=0; j<MOVE_LEN; j++)
17975 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17976 for(j=0; j<2*MOVE_LEN; j++)
17977 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17978 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17979 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17980 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17981 pvInfoList[firstMove+i-1].depth = 0;
17982 commentList[framePtr+i] = commentList[firstMove+i];
17983 commentList[firstMove+i] = NULL;
17987 forwardMostMove = firstMove; // truncate game so we can start variation
17991 PushTail (int firstMove, int lastMove)
17993 if(appData.icsActive) { // only in local mode
17994 forwardMostMove = currentMove; // mimic old ICS behavior
17997 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17999 PushInner(firstMove, lastMove);
18000 if(storedGames == 1) GreyRevert(FALSE);
18001 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18005 PopInner (Boolean annotate)
18008 char buf[8000], moveBuf[20];
18010 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18011 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18012 nrMoves = savedLast[storedGames] - currentMove;
18015 if(!WhiteOnMove(currentMove))
18016 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18017 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18018 for(i=currentMove; i<forwardMostMove; i++) {
18020 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18021 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18022 strcat(buf, moveBuf);
18023 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18024 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18028 for(i=1; i<=nrMoves; i++) { // copy last variation back
18029 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18030 for(j=0; j<MOVE_LEN; j++)
18031 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18032 for(j=0; j<2*MOVE_LEN; j++)
18033 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18034 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18035 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18036 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18037 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18038 commentList[currentMove+i] = commentList[framePtr+i];
18039 commentList[framePtr+i] = NULL;
18041 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18042 framePtr = savedFramePtr[storedGames];
18043 gameInfo.result = savedResult[storedGames];
18044 if(gameInfo.resultDetails != NULL) {
18045 free(gameInfo.resultDetails);
18047 gameInfo.resultDetails = savedDetails[storedGames];
18048 forwardMostMove = currentMove + nrMoves;
18052 PopTail (Boolean annotate)
18054 if(appData.icsActive) return FALSE; // only in local mode
18055 if(!storedGames) return FALSE; // sanity
18056 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18058 PopInner(annotate);
18059 if(currentMove < forwardMostMove) ForwardEvent(); else
18060 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18062 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18068 { // remove all shelved variations
18070 for(i=0; i<storedGames; i++) {
18071 if(savedDetails[i])
18072 free(savedDetails[i]);
18073 savedDetails[i] = NULL;
18075 for(i=framePtr; i<MAX_MOVES; i++) {
18076 if(commentList[i]) free(commentList[i]);
18077 commentList[i] = NULL;
18079 framePtr = MAX_MOVES-1;
18084 LoadVariation (int index, char *text)
18085 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18086 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18087 int level = 0, move;
18089 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18090 // first find outermost bracketing variation
18091 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18092 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18093 if(*p == '{') wait = '}'; else
18094 if(*p == '[') wait = ']'; else
18095 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18096 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18098 if(*p == wait) wait = NULLCHAR; // closing ]} found
18101 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18102 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18103 end[1] = NULLCHAR; // clip off comment beyond variation
18104 ToNrEvent(currentMove-1);
18105 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18106 // kludge: use ParsePV() to append variation to game
18107 move = currentMove;
18108 ParsePV(start, TRUE, TRUE);
18109 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18110 ClearPremoveHighlights();
18112 ToNrEvent(currentMove+1);
18118 char *p, *q, buf[MSG_SIZ];
18119 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18120 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18121 ParseArgsFromString(buf);
18122 ActivateTheme(TRUE); // also redo colors
18126 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18129 q = appData.themeNames;
18130 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18131 if(appData.useBitmaps) {
18132 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18133 appData.liteBackTextureFile, appData.darkBackTextureFile,
18134 appData.liteBackTextureMode,
18135 appData.darkBackTextureMode );
18137 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18138 Col2Text(2), // lightSquareColor
18139 Col2Text(3) ); // darkSquareColor
18141 if(appData.useBorder) {
18142 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18145 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18147 if(appData.useFont) {
18148 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18149 appData.renderPiecesWithFont,
18150 appData.fontToPieceTable,
18151 Col2Text(9), // appData.fontBackColorWhite
18152 Col2Text(10) ); // appData.fontForeColorBlack
18154 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18155 appData.pieceDirectory);
18156 if(!appData.pieceDirectory[0])
18157 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18158 Col2Text(0), // whitePieceColor
18159 Col2Text(1) ); // blackPieceColor
18161 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18162 Col2Text(4), // highlightSquareColor
18163 Col2Text(5) ); // premoveHighlightColor
18164 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18165 if(insert != q) insert[-1] = NULLCHAR;
18166 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18169 ActivateTheme(FALSE);