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 // null move in variant where engine does not understand it (for analysis purposes)
5020 SendBoard(cps, moveNum + 1); // send position after move in stead.
5023 if (cps->useUsermove) {
5024 SendToProgram("usermove ", cps);
5028 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5029 int len = space - parseList[moveNum];
5030 memcpy(buf, parseList[moveNum], len);
5032 buf[len] = NULLCHAR;
5034 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5036 SendToProgram(buf, cps);
5038 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5039 AlphaRank(moveList[moveNum], 4);
5040 SendToProgram(moveList[moveNum], cps);
5041 AlphaRank(moveList[moveNum], 4); // and back
5043 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5044 * the engine. It would be nice to have a better way to identify castle
5046 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5047 && cps->useOOCastle) {
5048 int fromX = moveList[moveNum][0] - AAA;
5049 int fromY = moveList[moveNum][1] - ONE;
5050 int toX = moveList[moveNum][2] - AAA;
5051 int toY = moveList[moveNum][3] - ONE;
5052 if((boards[moveNum][fromY][fromX] == WhiteKing
5053 && boards[moveNum][toY][toX] == WhiteRook)
5054 || (boards[moveNum][fromY][fromX] == BlackKing
5055 && boards[moveNum][toY][toX] == BlackRook)) {
5056 if(toX > fromX) SendToProgram("O-O\n", cps);
5057 else SendToProgram("O-O-O\n", cps);
5059 else SendToProgram(moveList[moveNum], cps);
5061 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5062 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5063 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5064 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5065 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5066 } else 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');
5072 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5073 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5074 SendToProgram(buf, cps);
5076 else SendToProgram(moveList[moveNum], cps);
5077 /* End of additions by Tord */
5080 /* [HGM] setting up the opening has brought engine in force mode! */
5081 /* Send 'go' if we are in a mode where machine should play. */
5082 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5083 (gameMode == TwoMachinesPlay ||
5085 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5087 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5088 SendToProgram("go\n", cps);
5089 if (appData.debugMode) {
5090 fprintf(debugFP, "(extra)\n");
5093 setboardSpoiledMachineBlack = 0;
5097 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5099 char user_move[MSG_SIZ];
5102 if(gameInfo.variant == VariantSChess && promoChar) {
5103 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5104 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5105 } else suffix[0] = NULLCHAR;
5109 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5110 (int)moveType, fromX, fromY, toX, toY);
5111 DisplayError(user_move + strlen("say "), 0);
5113 case WhiteKingSideCastle:
5114 case BlackKingSideCastle:
5115 case WhiteQueenSideCastleWild:
5116 case BlackQueenSideCastleWild:
5118 case WhiteHSideCastleFR:
5119 case BlackHSideCastleFR:
5121 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5123 case WhiteQueenSideCastle:
5124 case BlackQueenSideCastle:
5125 case WhiteKingSideCastleWild:
5126 case BlackKingSideCastleWild:
5128 case WhiteASideCastleFR:
5129 case BlackASideCastleFR:
5131 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5133 case WhiteNonPromotion:
5134 case BlackNonPromotion:
5135 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5137 case WhitePromotion:
5138 case BlackPromotion:
5139 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5140 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5141 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5142 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5143 PieceToChar(WhiteFerz));
5144 else if(gameInfo.variant == VariantGreat)
5145 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5146 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5147 PieceToChar(WhiteMan));
5149 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5150 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5156 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5157 ToUpper(PieceToChar((ChessSquare) fromX)),
5158 AAA + toX, ONE + toY);
5160 case IllegalMove: /* could be a variant we don't quite understand */
5161 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5163 case WhiteCapturesEnPassant:
5164 case BlackCapturesEnPassant:
5165 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5166 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5169 SendToICS(user_move);
5170 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5171 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5176 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5177 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5178 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5179 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5180 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5183 if(gameMode != IcsExamining) { // is this ever not the case?
5184 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5186 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5187 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5188 } else { // on FICS we must first go to general examine mode
5189 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5191 if(gameInfo.variant != VariantNormal) {
5192 // try figure out wild number, as xboard names are not always valid on ICS
5193 for(i=1; i<=36; i++) {
5194 snprintf(buf, MSG_SIZ, "wild/%d", i);
5195 if(StringToVariant(buf) == gameInfo.variant) break;
5197 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5198 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5199 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5200 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5201 SendToICS(ics_prefix);
5203 if(startedFromSetupPosition || backwardMostMove != 0) {
5204 fen = PositionToFEN(backwardMostMove, NULL, 1);
5205 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5206 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5208 } else { // FICS: everything has to set by separate bsetup commands
5209 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5210 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5212 if(!WhiteOnMove(backwardMostMove)) {
5213 SendToICS("bsetup tomove black\n");
5215 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5216 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5218 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5219 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5221 i = boards[backwardMostMove][EP_STATUS];
5222 if(i >= 0) { // set e.p.
5223 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5229 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5230 SendToICS("bsetup done\n"); // switch to normal examining.
5232 for(i = backwardMostMove; i<last; i++) {
5234 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5235 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5236 int len = strlen(moveList[i]);
5237 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5238 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5242 SendToICS(ics_prefix);
5243 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5246 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5249 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5251 if (rf == DROP_RANK) {
5252 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5253 sprintf(move, "%c@%c%c\n",
5254 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5256 if (promoChar == 'x' || promoChar == NULLCHAR) {
5257 sprintf(move, "%c%c%c%c\n",
5258 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5259 if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5261 sprintf(move, "%c%c%c%c%c\n",
5262 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5268 ProcessICSInitScript (FILE *f)
5272 while (fgets(buf, MSG_SIZ, f)) {
5273 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5280 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5281 static ClickType lastClickType;
5286 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5287 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5288 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5289 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5290 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5291 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5294 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5295 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5296 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5297 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5298 if(!step) step = -1;
5299 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5300 appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion) ||
5301 IS_SHOGI(gameInfo.variant) && promoSweep != CHUPROMOTED last && last != CHUPROMOTED promoSweep && last != promoSweep);
5303 int victim = boards[currentMove][toY][toX];
5304 boards[currentMove][toY][toX] = promoSweep;
5305 DrawPosition(FALSE, boards[currentMove]);
5306 boards[currentMove][toY][toX] = victim;
5308 ChangeDragPiece(promoSweep);
5312 PromoScroll (int x, int y)
5316 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5317 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5318 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5319 if(!step) return FALSE;
5320 lastX = x; lastY = y;
5321 if((promoSweep < BlackPawn) == flipView) step = -step;
5322 if(step > 0) selectFlag = 1;
5323 if(!selectFlag) Sweep(step);
5328 NextPiece (int step)
5330 ChessSquare piece = boards[currentMove][toY][toX];
5333 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5334 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5335 if(!step) step = -1;
5336 } while(PieceToChar(pieceSweep) == '.');
5337 boards[currentMove][toY][toX] = pieceSweep;
5338 DrawPosition(FALSE, boards[currentMove]);
5339 boards[currentMove][toY][toX] = piece;
5341 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5343 AlphaRank (char *move, int n)
5345 // char *p = move, c; int x, y;
5347 if (appData.debugMode) {
5348 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5352 move[2]>='0' && move[2]<='9' &&
5353 move[3]>='a' && move[3]<='x' ) {
5355 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5356 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5358 if(move[0]>='0' && move[0]<='9' &&
5359 move[1]>='a' && move[1]<='x' &&
5360 move[2]>='0' && move[2]<='9' &&
5361 move[3]>='a' && move[3]<='x' ) {
5362 /* input move, Shogi -> normal */
5363 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5364 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5365 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5366 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5369 move[3]>='0' && move[3]<='9' &&
5370 move[2]>='a' && move[2]<='x' ) {
5372 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5373 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5376 move[0]>='a' && move[0]<='x' &&
5377 move[3]>='0' && move[3]<='9' &&
5378 move[2]>='a' && move[2]<='x' ) {
5379 /* output move, normal -> Shogi */
5380 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5381 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5382 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5383 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5384 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5386 if (appData.debugMode) {
5387 fprintf(debugFP, " out = '%s'\n", move);
5391 char yy_textstr[8000];
5393 /* Parser for moves from gnuchess, ICS, or user typein box */
5395 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5397 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5399 switch (*moveType) {
5400 case WhitePromotion:
5401 case BlackPromotion:
5402 case WhiteNonPromotion:
5403 case BlackNonPromotion:
5406 case WhiteCapturesEnPassant:
5407 case BlackCapturesEnPassant:
5408 case WhiteKingSideCastle:
5409 case WhiteQueenSideCastle:
5410 case BlackKingSideCastle:
5411 case BlackQueenSideCastle:
5412 case WhiteKingSideCastleWild:
5413 case WhiteQueenSideCastleWild:
5414 case BlackKingSideCastleWild:
5415 case BlackQueenSideCastleWild:
5416 /* Code added by Tord: */
5417 case WhiteHSideCastleFR:
5418 case WhiteASideCastleFR:
5419 case BlackHSideCastleFR:
5420 case BlackASideCastleFR:
5421 /* End of code added by Tord */
5422 case IllegalMove: /* bug or odd chess variant */
5423 *fromX = currentMoveString[0] - AAA;
5424 *fromY = currentMoveString[1] - ONE;
5425 *toX = currentMoveString[2] - AAA;
5426 *toY = currentMoveString[3] - ONE;
5427 *promoChar = currentMoveString[4];
5428 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5429 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5430 if (appData.debugMode) {
5431 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5433 *fromX = *fromY = *toX = *toY = 0;
5436 if (appData.testLegality) {
5437 return (*moveType != IllegalMove);
5439 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5440 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5445 *fromX = *moveType == WhiteDrop ?
5446 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5447 (int) CharToPiece(ToLower(currentMoveString[0]));
5449 *toX = currentMoveString[2] - AAA;
5450 *toY = currentMoveString[3] - ONE;
5451 *promoChar = NULLCHAR;
5455 case ImpossibleMove:
5465 if (appData.debugMode) {
5466 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5469 *fromX = *fromY = *toX = *toY = 0;
5470 *promoChar = NULLCHAR;
5475 Boolean pushed = FALSE;
5476 char *lastParseAttempt;
5479 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5480 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5481 int fromX, fromY, toX, toY; char promoChar;
5486 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5487 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5488 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5491 endPV = forwardMostMove;
5493 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5494 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5495 lastParseAttempt = pv;
5496 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5497 if(!valid && nr == 0 &&
5498 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5499 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5500 // Hande case where played move is different from leading PV move
5501 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5502 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5503 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5504 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5505 endPV += 2; // if position different, keep this
5506 moveList[endPV-1][0] = fromX + AAA;
5507 moveList[endPV-1][1] = fromY + ONE;
5508 moveList[endPV-1][2] = toX + AAA;
5509 moveList[endPV-1][3] = toY + ONE;
5510 parseList[endPV-1][0] = NULLCHAR;
5511 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5514 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5515 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5516 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5517 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5518 valid++; // allow comments in PV
5522 if(endPV+1 > framePtr) break; // no space, truncate
5525 CopyBoard(boards[endPV], boards[endPV-1]);
5526 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5527 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5528 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5529 CoordsToAlgebraic(boards[endPV - 1],
5530 PosFlags(endPV - 1),
5531 fromY, fromX, toY, toX, promoChar,
5532 parseList[endPV - 1]);
5534 if(atEnd == 2) return; // used hidden, for PV conversion
5535 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5536 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5537 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5538 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5539 DrawPosition(TRUE, boards[currentMove]);
5543 MultiPV (ChessProgramState *cps)
5544 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5546 for(i=0; i<cps->nrOptions; i++)
5547 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5552 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5555 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5557 int startPV, multi, lineStart, origIndex = index;
5558 char *p, buf2[MSG_SIZ];
5559 ChessProgramState *cps = (pane ? &second : &first);
5561 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5562 lastX = x; lastY = y;
5563 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5564 lineStart = startPV = index;
5565 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5566 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5568 do{ while(buf[index] && buf[index] != '\n') index++;
5569 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5571 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5572 int n = cps->option[multi].value;
5573 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5574 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5575 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5576 cps->option[multi].value = n;
5579 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5580 ExcludeClick(origIndex - lineStart);
5583 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5584 *start = startPV; *end = index-1;
5585 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5592 static char buf[10*MSG_SIZ];
5593 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5595 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5596 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5597 for(i = forwardMostMove; i<endPV; i++){
5598 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5599 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5602 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5603 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5604 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5610 LoadPV (int x, int y)
5611 { // called on right mouse click to load PV
5612 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5613 lastX = x; lastY = y;
5614 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5622 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5623 if(endPV < 0) return;
5624 if(appData.autoCopyPV) CopyFENToClipboard();
5626 if(extendGame && currentMove > forwardMostMove) {
5627 Boolean saveAnimate = appData.animate;
5629 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5630 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5631 } else storedGames--; // abandon shelved tail of original game
5634 forwardMostMove = currentMove;
5635 currentMove = oldFMM;
5636 appData.animate = FALSE;
5637 ToNrEvent(forwardMostMove);
5638 appData.animate = saveAnimate;
5640 currentMove = forwardMostMove;
5641 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5642 ClearPremoveHighlights();
5643 DrawPosition(TRUE, boards[currentMove]);
5647 MovePV (int x, int y, int h)
5648 { // step through PV based on mouse coordinates (called on mouse move)
5649 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5651 // we must somehow check if right button is still down (might be released off board!)
5652 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5653 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5654 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5656 lastX = x; lastY = y;
5658 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5659 if(endPV < 0) return;
5660 if(y < margin) step = 1; else
5661 if(y > h - margin) step = -1;
5662 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5663 currentMove += step;
5664 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5665 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5666 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5667 DrawPosition(FALSE, boards[currentMove]);
5671 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5672 // All positions will have equal probability, but the current method will not provide a unique
5673 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5679 int piecesLeft[(int)BlackPawn];
5680 int seed, nrOfShuffles;
5683 GetPositionNumber ()
5684 { // sets global variable seed
5687 seed = appData.defaultFrcPosition;
5688 if(seed < 0) { // randomize based on time for negative FRC position numbers
5689 for(i=0; i<50; i++) seed += random();
5690 seed = random() ^ random() >> 8 ^ random() << 8;
5691 if(seed<0) seed = -seed;
5696 put (Board board, int pieceType, int rank, int n, int shade)
5697 // put the piece on the (n-1)-th empty squares of the given shade
5701 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5702 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5703 board[rank][i] = (ChessSquare) pieceType;
5704 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5706 piecesLeft[pieceType]--;
5715 AddOnePiece (Board board, int pieceType, int rank, int shade)
5716 // calculate where the next piece goes, (any empty square), and put it there
5720 i = seed % squaresLeft[shade];
5721 nrOfShuffles *= squaresLeft[shade];
5722 seed /= squaresLeft[shade];
5723 put(board, pieceType, rank, i, shade);
5727 AddTwoPieces (Board board, int pieceType, int rank)
5728 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5730 int i, n=squaresLeft[ANY], j=n-1, k;
5732 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5733 i = seed % k; // pick one
5736 while(i >= j) i -= j--;
5737 j = n - 1 - j; i += j;
5738 put(board, pieceType, rank, j, ANY);
5739 put(board, pieceType, rank, i, ANY);
5743 SetUpShuffle (Board board, int number)
5747 GetPositionNumber(); nrOfShuffles = 1;
5749 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5750 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5751 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5753 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5755 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5756 p = (int) board[0][i];
5757 if(p < (int) BlackPawn) piecesLeft[p] ++;
5758 board[0][i] = EmptySquare;
5761 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5762 // shuffles restricted to allow normal castling put KRR first
5763 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5764 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5765 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5766 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5767 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5768 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5769 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5770 put(board, WhiteRook, 0, 0, ANY);
5771 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5774 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5775 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5776 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5777 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5778 while(piecesLeft[p] >= 2) {
5779 AddOnePiece(board, p, 0, LITE);
5780 AddOnePiece(board, p, 0, DARK);
5782 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5785 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5786 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5787 // but we leave King and Rooks for last, to possibly obey FRC restriction
5788 if(p == (int)WhiteRook) continue;
5789 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5790 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5793 // now everything is placed, except perhaps King (Unicorn) and Rooks
5795 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5796 // Last King gets castling rights
5797 while(piecesLeft[(int)WhiteUnicorn]) {
5798 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5799 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5802 while(piecesLeft[(int)WhiteKing]) {
5803 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5804 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5809 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5810 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5813 // Only Rooks can be left; simply place them all
5814 while(piecesLeft[(int)WhiteRook]) {
5815 i = put(board, WhiteRook, 0, 0, ANY);
5816 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5819 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5821 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5824 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5825 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5828 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5832 SetCharTable (char *table, const char * map)
5833 /* [HGM] moved here from winboard.c because of its general usefulness */
5834 /* Basically a safe strcpy that uses the last character as King */
5836 int result = FALSE; int NrPieces;
5838 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5839 && NrPieces >= 12 && !(NrPieces&1)) {
5840 int i; /* [HGM] Accept even length from 12 to 34 */
5842 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5843 for( i=0; i<NrPieces/2-1; i++ ) {
5845 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5847 table[(int) WhiteKing] = map[NrPieces/2-1];
5848 table[(int) BlackKing] = map[NrPieces-1];
5857 Prelude (Board board)
5858 { // [HGM] superchess: random selection of exo-pieces
5859 int i, j, k; ChessSquare p;
5860 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5862 GetPositionNumber(); // use FRC position number
5864 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5865 SetCharTable(pieceToChar, appData.pieceToCharTable);
5866 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5867 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5870 j = seed%4; seed /= 4;
5871 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5872 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5873 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5874 j = seed%3 + (seed%3 >= j); seed /= 3;
5875 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5876 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5877 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5878 j = seed%3; seed /= 3;
5879 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%2 + (seed%2 >= j); seed /= 2;
5883 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5887 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5888 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5889 put(board, exoPieces[0], 0, 0, ANY);
5890 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5894 InitPosition (int redraw)
5896 ChessSquare (* pieces)[BOARD_FILES];
5897 int i, j, pawnRow=1, pieceRows=1, overrule,
5898 oldx = gameInfo.boardWidth,
5899 oldy = gameInfo.boardHeight,
5900 oldh = gameInfo.holdingsWidth;
5903 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5905 /* [AS] Initialize pv info list [HGM] and game status */
5907 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5908 pvInfoList[i].depth = 0;
5909 boards[i][EP_STATUS] = EP_NONE;
5910 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5913 initialRulePlies = 0; /* 50-move counter start */
5915 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5916 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5920 /* [HGM] logic here is completely changed. In stead of full positions */
5921 /* the initialized data only consist of the two backranks. The switch */
5922 /* selects which one we will use, which is than copied to the Board */
5923 /* initialPosition, which for the rest is initialized by Pawns and */
5924 /* empty squares. This initial position is then copied to boards[0], */
5925 /* possibly after shuffling, so that it remains available. */
5927 gameInfo.holdingsWidth = 0; /* default board sizes */
5928 gameInfo.boardWidth = 8;
5929 gameInfo.boardHeight = 8;
5930 gameInfo.holdingsSize = 0;
5931 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5932 for(i=0; i<BOARD_FILES-2; i++)
5933 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5934 initialPosition[EP_STATUS] = EP_NONE;
5935 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5936 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5937 SetCharTable(pieceNickName, appData.pieceNickNames);
5938 else SetCharTable(pieceNickName, "............");
5941 switch (gameInfo.variant) {
5942 case VariantFischeRandom:
5943 shuffleOpenings = TRUE;
5946 case VariantShatranj:
5947 pieces = ShatranjArray;
5948 nrCastlingRights = 0;
5949 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5952 pieces = makrukArray;
5953 nrCastlingRights = 0;
5954 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5957 pieces = aseanArray;
5958 nrCastlingRights = 0;
5959 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5961 case VariantTwoKings:
5962 pieces = twoKingsArray;
5965 pieces = GrandArray;
5966 nrCastlingRights = 0;
5967 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5968 gameInfo.boardWidth = 10;
5969 gameInfo.boardHeight = 10;
5970 gameInfo.holdingsSize = 7;
5972 case VariantCapaRandom:
5973 shuffleOpenings = TRUE;
5974 case VariantCapablanca:
5975 pieces = CapablancaArray;
5976 gameInfo.boardWidth = 10;
5977 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5980 pieces = GothicArray;
5981 gameInfo.boardWidth = 10;
5982 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5985 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5986 gameInfo.holdingsSize = 7;
5987 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5990 pieces = JanusArray;
5991 gameInfo.boardWidth = 10;
5992 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5993 nrCastlingRights = 6;
5994 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5995 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5996 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5997 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5998 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5999 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6002 pieces = FalconArray;
6003 gameInfo.boardWidth = 10;
6004 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6006 case VariantXiangqi:
6007 pieces = XiangqiArray;
6008 gameInfo.boardWidth = 9;
6009 gameInfo.boardHeight = 10;
6010 nrCastlingRights = 0;
6011 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6014 pieces = ShogiArray;
6015 gameInfo.boardWidth = 9;
6016 gameInfo.boardHeight = 9;
6017 gameInfo.holdingsSize = 7;
6018 nrCastlingRights = 0;
6019 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6022 pieces = ChuArray; pieceRows = 3;
6023 gameInfo.boardWidth = 12;
6024 gameInfo.boardHeight = 12;
6025 nrCastlingRights = 0;
6026 SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6027 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6029 case VariantCourier:
6030 pieces = CourierArray;
6031 gameInfo.boardWidth = 12;
6032 nrCastlingRights = 0;
6033 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6035 case VariantKnightmate:
6036 pieces = KnightmateArray;
6037 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6039 case VariantSpartan:
6040 pieces = SpartanArray;
6041 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6045 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6048 pieces = fairyArray;
6049 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6052 pieces = GreatArray;
6053 gameInfo.boardWidth = 10;
6054 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6055 gameInfo.holdingsSize = 8;
6059 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6060 gameInfo.holdingsSize = 8;
6061 startedFromSetupPosition = TRUE;
6063 case VariantCrazyhouse:
6064 case VariantBughouse:
6066 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6067 gameInfo.holdingsSize = 5;
6069 case VariantWildCastle:
6071 /* !!?shuffle with kings guaranteed to be on d or e file */
6072 shuffleOpenings = 1;
6074 case VariantNoCastle:
6076 nrCastlingRights = 0;
6077 /* !!?unconstrained back-rank shuffle */
6078 shuffleOpenings = 1;
6083 if(appData.NrFiles >= 0) {
6084 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6085 gameInfo.boardWidth = appData.NrFiles;
6087 if(appData.NrRanks >= 0) {
6088 gameInfo.boardHeight = appData.NrRanks;
6090 if(appData.holdingsSize >= 0) {
6091 i = appData.holdingsSize;
6092 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6093 gameInfo.holdingsSize = i;
6095 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6096 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6097 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6099 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6100 if(pawnRow < 1) pawnRow = 1;
6101 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6102 if(gameInfo.variant == VariantChu) pawnRow = 3;
6104 /* User pieceToChar list overrules defaults */
6105 if(appData.pieceToCharTable != NULL)
6106 SetCharTable(pieceToChar, appData.pieceToCharTable);
6108 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6110 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6111 s = (ChessSquare) 0; /* account holding counts in guard band */
6112 for( i=0; i<BOARD_HEIGHT; i++ )
6113 initialPosition[i][j] = s;
6115 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6116 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6117 initialPosition[pawnRow][j] = WhitePawn;
6118 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6119 if(gameInfo.variant == VariantXiangqi) {
6121 initialPosition[pawnRow][j] =
6122 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6123 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6124 initialPosition[2][j] = WhiteCannon;
6125 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6129 if(gameInfo.variant == VariantChu) {
6130 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6131 initialPosition[pawnRow+1][j] = WhiteCobra,
6132 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6133 for(i=1; i<pieceRows; i++) {
6134 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6135 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6138 if(gameInfo.variant == VariantGrand) {
6139 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6140 initialPosition[0][j] = WhiteRook;
6141 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6144 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
6146 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6149 initialPosition[1][j] = WhiteBishop;
6150 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6152 initialPosition[1][j] = WhiteRook;
6153 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6156 if( nrCastlingRights == -1) {
6157 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6158 /* This sets default castling rights from none to normal corners */
6159 /* Variants with other castling rights must set them themselves above */
6160 nrCastlingRights = 6;
6162 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6163 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6164 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6165 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6166 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6167 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6170 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6171 if(gameInfo.variant == VariantGreat) { // promotion commoners
6172 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6173 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6174 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6175 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6177 if( gameInfo.variant == VariantSChess ) {
6178 initialPosition[1][0] = BlackMarshall;
6179 initialPosition[2][0] = BlackAngel;
6180 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6181 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6182 initialPosition[1][1] = initialPosition[2][1] =
6183 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6185 if (appData.debugMode) {
6186 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6188 if(shuffleOpenings) {
6189 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6190 startedFromSetupPosition = TRUE;
6192 if(startedFromPositionFile) {
6193 /* [HGM] loadPos: use PositionFile for every new game */
6194 CopyBoard(initialPosition, filePosition);
6195 for(i=0; i<nrCastlingRights; i++)
6196 initialRights[i] = filePosition[CASTLING][i];
6197 startedFromSetupPosition = TRUE;
6200 CopyBoard(boards[0], initialPosition);
6202 if(oldx != gameInfo.boardWidth ||
6203 oldy != gameInfo.boardHeight ||
6204 oldv != gameInfo.variant ||
6205 oldh != gameInfo.holdingsWidth
6207 InitDrawingSizes(-2 ,0);
6209 oldv = gameInfo.variant;
6211 DrawPosition(TRUE, boards[currentMove]);
6215 SendBoard (ChessProgramState *cps, int moveNum)
6217 char message[MSG_SIZ];
6219 if (cps->useSetboard) {
6220 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6221 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6222 SendToProgram(message, cps);
6227 int i, j, left=0, right=BOARD_WIDTH;
6228 /* Kludge to set black to move, avoiding the troublesome and now
6229 * deprecated "black" command.
6231 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6232 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6234 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6236 SendToProgram("edit\n", cps);
6237 SendToProgram("#\n", cps);
6238 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6239 bp = &boards[moveNum][i][left];
6240 for (j = left; j < right; j++, bp++) {
6241 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6242 if ((int) *bp < (int) BlackPawn) {
6243 if(j == BOARD_RGHT+1)
6244 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6245 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6246 if(message[0] == '+' || message[0] == '~') {
6247 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6248 PieceToChar((ChessSquare)(DEMOTED *bp)),
6251 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6252 message[1] = BOARD_RGHT - 1 - j + '1';
6253 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6255 SendToProgram(message, cps);
6260 SendToProgram("c\n", cps);
6261 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6262 bp = &boards[moveNum][i][left];
6263 for (j = left; j < right; j++, bp++) {
6264 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6265 if (((int) *bp != (int) EmptySquare)
6266 && ((int) *bp >= (int) BlackPawn)) {
6267 if(j == BOARD_LEFT-2)
6268 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6269 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6271 if(message[0] == '+' || message[0] == '~') {
6272 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6273 PieceToChar((ChessSquare)(DEMOTED *bp)),
6276 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6277 message[1] = BOARD_RGHT - 1 - j + '1';
6278 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6280 SendToProgram(message, cps);
6285 SendToProgram(".\n", cps);
6287 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6290 char exclusionHeader[MSG_SIZ];
6291 int exCnt, excludePtr;
6292 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6293 static Exclusion excluTab[200];
6294 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6300 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6301 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6307 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6308 excludePtr = 24; exCnt = 0;
6313 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6314 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6315 char buf[2*MOVE_LEN], *p;
6316 Exclusion *e = excluTab;
6318 for(i=0; i<exCnt; i++)
6319 if(e[i].ff == fromX && e[i].fr == fromY &&
6320 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6321 if(i == exCnt) { // was not in exclude list; add it
6322 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6323 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6324 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6327 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6328 excludePtr++; e[i].mark = excludePtr++;
6329 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6332 exclusionHeader[e[i].mark] = state;
6336 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6337 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6341 if((signed char)promoChar == -1) { // kludge to indicate best move
6342 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6343 return 1; // if unparsable, abort
6345 // update exclusion map (resolving toggle by consulting existing state)
6346 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6348 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6349 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6350 excludeMap[k] |= 1<<j;
6351 else excludeMap[k] &= ~(1<<j);
6353 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6355 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6356 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6358 return (state == '+');
6362 ExcludeClick (int index)
6365 Exclusion *e = excluTab;
6366 if(index < 25) { // none, best or tail clicked
6367 if(index < 13) { // none: include all
6368 WriteMap(0); // clear map
6369 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6370 SendToBoth("include all\n"); // and inform engine
6371 } else if(index > 18) { // tail
6372 if(exclusionHeader[19] == '-') { // tail was excluded
6373 SendToBoth("include all\n");
6374 WriteMap(0); // clear map completely
6375 // now re-exclude selected moves
6376 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6377 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6378 } else { // tail was included or in mixed state
6379 SendToBoth("exclude all\n");
6380 WriteMap(0xFF); // fill map completely
6381 // now re-include selected moves
6382 j = 0; // count them
6383 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6384 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6385 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6388 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6391 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6392 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6393 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6400 DefaultPromoChoice (int white)
6403 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6404 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6405 result = WhiteFerz; // no choice
6406 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6407 result= WhiteKing; // in Suicide Q is the last thing we want
6408 else if(gameInfo.variant == VariantSpartan)
6409 result = white ? WhiteQueen : WhiteAngel;
6410 else result = WhiteQueen;
6411 if(!white) result = WHITE_TO_BLACK result;
6415 static int autoQueen; // [HGM] oneclick
6418 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6420 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6421 /* [HGM] add Shogi promotions */
6422 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6427 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6428 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6430 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6431 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6434 piece = boards[currentMove][fromY][fromX];
6435 if(gameInfo.variant == VariantChu) {
6436 int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6437 promotionZoneSize = BOARD_HEIGHT/3;
6438 highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6439 } else if(gameInfo.variant == VariantShogi) {
6440 promotionZoneSize = BOARD_HEIGHT/3;
6441 highestPromotingPiece = (int)WhiteAlfil;
6442 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6443 promotionZoneSize = 3;
6446 // Treat Lance as Pawn when it is not representing Amazon
6447 if(gameInfo.variant != VariantSuper) {
6448 if(piece == WhiteLance) piece = WhitePawn; else
6449 if(piece == BlackLance) piece = BlackPawn;
6452 // next weed out all moves that do not touch the promotion zone at all
6453 if((int)piece >= BlackPawn) {
6454 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6456 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6458 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6459 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6462 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6464 // weed out mandatory Shogi promotions
6465 if(gameInfo.variant == VariantShogi) {
6466 if(piece >= BlackPawn) {
6467 if(toY == 0 && piece == BlackPawn ||
6468 toY == 0 && piece == BlackQueen ||
6469 toY <= 1 && piece == BlackKnight) {
6474 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6475 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6476 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6483 // weed out obviously illegal Pawn moves
6484 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6485 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6486 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6487 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6488 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6489 // note we are not allowed to test for valid (non-)capture, due to premove
6492 // we either have a choice what to promote to, or (in Shogi) whether to promote
6493 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6494 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6495 *promoChoice = PieceToChar(BlackFerz); // no choice
6498 // no sense asking what we must promote to if it is going to explode...
6499 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6500 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6503 // give caller the default choice even if we will not make it
6504 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6505 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6506 if( sweepSelect && gameInfo.variant != VariantGreat
6507 && gameInfo.variant != VariantGrand
6508 && gameInfo.variant != VariantSuper) return FALSE;
6509 if(autoQueen) return FALSE; // predetermined
6511 // suppress promotion popup on illegal moves that are not premoves
6512 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6513 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6514 if(appData.testLegality && !premove) {
6515 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6516 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) ? '+' : NULLCHAR);
6517 if(moveType != WhitePromotion && moveType != BlackPromotion)
6525 InPalace (int row, int column)
6526 { /* [HGM] for Xiangqi */
6527 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6528 column < (BOARD_WIDTH + 4)/2 &&
6529 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6534 PieceForSquare (int x, int y)
6536 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6539 return boards[currentMove][y][x];
6543 OKToStartUserMove (int x, int y)
6545 ChessSquare from_piece;
6548 if (matchMode) return FALSE;
6549 if (gameMode == EditPosition) return TRUE;
6551 if (x >= 0 && y >= 0)
6552 from_piece = boards[currentMove][y][x];
6554 from_piece = EmptySquare;
6556 if (from_piece == EmptySquare) return FALSE;
6558 white_piece = (int)from_piece >= (int)WhitePawn &&
6559 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6563 case TwoMachinesPlay:
6571 case MachinePlaysWhite:
6572 case IcsPlayingBlack:
6573 if (appData.zippyPlay) return FALSE;
6575 DisplayMoveError(_("You are playing Black"));
6580 case MachinePlaysBlack:
6581 case IcsPlayingWhite:
6582 if (appData.zippyPlay) return FALSE;
6584 DisplayMoveError(_("You are playing White"));
6589 case PlayFromGameFile:
6590 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6592 if (!white_piece && WhiteOnMove(currentMove)) {
6593 DisplayMoveError(_("It is White's turn"));
6596 if (white_piece && !WhiteOnMove(currentMove)) {
6597 DisplayMoveError(_("It is Black's turn"));
6600 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6601 /* Editing correspondence game history */
6602 /* Could disallow this or prompt for confirmation */
6607 case BeginningOfGame:
6608 if (appData.icsActive) return FALSE;
6609 if (!appData.noChessProgram) {
6611 DisplayMoveError(_("You are playing White"));
6618 if (!white_piece && WhiteOnMove(currentMove)) {
6619 DisplayMoveError(_("It is White's turn"));
6622 if (white_piece && !WhiteOnMove(currentMove)) {
6623 DisplayMoveError(_("It is Black's turn"));
6632 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6633 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6634 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6635 && gameMode != AnalyzeFile && gameMode != Training) {
6636 DisplayMoveError(_("Displayed position is not current"));
6643 OnlyMove (int *x, int *y, Boolean captures)
6645 DisambiguateClosure cl;
6646 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6648 case MachinePlaysBlack:
6649 case IcsPlayingWhite:
6650 case BeginningOfGame:
6651 if(!WhiteOnMove(currentMove)) return FALSE;
6653 case MachinePlaysWhite:
6654 case IcsPlayingBlack:
6655 if(WhiteOnMove(currentMove)) return FALSE;
6662 cl.pieceIn = EmptySquare;
6667 cl.promoCharIn = NULLCHAR;
6668 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6669 if( cl.kind == NormalMove ||
6670 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6671 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6672 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6679 if(cl.kind != ImpossibleMove) return FALSE;
6680 cl.pieceIn = EmptySquare;
6685 cl.promoCharIn = NULLCHAR;
6686 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6687 if( cl.kind == NormalMove ||
6688 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6689 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6690 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6695 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6701 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6702 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6703 int lastLoadGameUseList = FALSE;
6704 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6705 ChessMove lastLoadGameStart = EndOfFile;
6709 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6713 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6715 /* Check if the user is playing in turn. This is complicated because we
6716 let the user "pick up" a piece before it is his turn. So the piece he
6717 tried to pick up may have been captured by the time he puts it down!
6718 Therefore we use the color the user is supposed to be playing in this
6719 test, not the color of the piece that is currently on the starting
6720 square---except in EditGame mode, where the user is playing both
6721 sides; fortunately there the capture race can't happen. (It can
6722 now happen in IcsExamining mode, but that's just too bad. The user
6723 will get a somewhat confusing message in that case.)
6728 case TwoMachinesPlay:
6732 /* We switched into a game mode where moves are not accepted,
6733 perhaps while the mouse button was down. */
6736 case MachinePlaysWhite:
6737 /* User is moving for Black */
6738 if (WhiteOnMove(currentMove)) {
6739 DisplayMoveError(_("It is White's turn"));
6744 case MachinePlaysBlack:
6745 /* User is moving for White */
6746 if (!WhiteOnMove(currentMove)) {
6747 DisplayMoveError(_("It is Black's turn"));
6752 case PlayFromGameFile:
6753 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6756 case BeginningOfGame:
6759 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6760 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6761 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6762 /* User is moving for Black */
6763 if (WhiteOnMove(currentMove)) {
6764 DisplayMoveError(_("It is White's turn"));
6768 /* User is moving for White */
6769 if (!WhiteOnMove(currentMove)) {
6770 DisplayMoveError(_("It is Black's turn"));
6776 case IcsPlayingBlack:
6777 /* User is moving for Black */
6778 if (WhiteOnMove(currentMove)) {
6779 if (!appData.premove) {
6780 DisplayMoveError(_("It is White's turn"));
6781 } else if (toX >= 0 && toY >= 0) {
6784 premoveFromX = fromX;
6785 premoveFromY = fromY;
6786 premovePromoChar = promoChar;
6788 if (appData.debugMode)
6789 fprintf(debugFP, "Got premove: fromX %d,"
6790 "fromY %d, toX %d, toY %d\n",
6791 fromX, fromY, toX, toY);
6797 case IcsPlayingWhite:
6798 /* User is moving for White */
6799 if (!WhiteOnMove(currentMove)) {
6800 if (!appData.premove) {
6801 DisplayMoveError(_("It is Black's turn"));
6802 } else if (toX >= 0 && toY >= 0) {
6805 premoveFromX = fromX;
6806 premoveFromY = fromY;
6807 premovePromoChar = promoChar;
6809 if (appData.debugMode)
6810 fprintf(debugFP, "Got premove: fromX %d,"
6811 "fromY %d, toX %d, toY %d\n",
6812 fromX, fromY, toX, toY);
6822 /* EditPosition, empty square, or different color piece;
6823 click-click move is possible */
6824 if (toX == -2 || toY == -2) {
6825 boards[0][fromY][fromX] = EmptySquare;
6826 DrawPosition(FALSE, boards[currentMove]);
6828 } else if (toX >= 0 && toY >= 0) {
6829 boards[0][toY][toX] = boards[0][fromY][fromX];
6830 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6831 if(boards[0][fromY][0] != EmptySquare) {
6832 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6833 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6836 if(fromX == BOARD_RGHT+1) {
6837 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6838 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6839 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6842 boards[0][fromY][fromX] = gatingPiece;
6843 DrawPosition(FALSE, boards[currentMove]);
6849 if(toX < 0 || toY < 0) return;
6850 pup = boards[currentMove][toY][toX];
6852 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6853 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6854 if( pup != EmptySquare ) return;
6855 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6856 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6857 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6858 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6859 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6860 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6861 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6865 /* [HGM] always test for legality, to get promotion info */
6866 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6867 fromY, fromX, toY, toX, promoChar);
6869 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6871 /* [HGM] but possibly ignore an IllegalMove result */
6872 if (appData.testLegality) {
6873 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6874 DisplayMoveError(_("Illegal move"));
6879 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6880 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6881 ClearPremoveHighlights(); // was included
6882 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6886 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6889 /* Common tail of UserMoveEvent and DropMenuEvent */
6891 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6895 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6896 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6897 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6898 if(WhiteOnMove(currentMove)) {
6899 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6901 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6905 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6906 move type in caller when we know the move is a legal promotion */
6907 if(moveType == NormalMove && promoChar)
6908 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6910 /* [HGM] <popupFix> The following if has been moved here from
6911 UserMoveEvent(). Because it seemed to belong here (why not allow
6912 piece drops in training games?), and because it can only be
6913 performed after it is known to what we promote. */
6914 if (gameMode == Training) {
6915 /* compare the move played on the board to the next move in the
6916 * game. If they match, display the move and the opponent's response.
6917 * If they don't match, display an error message.
6921 CopyBoard(testBoard, boards[currentMove]);
6922 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6924 if (CompareBoards(testBoard, boards[currentMove+1])) {
6925 ForwardInner(currentMove+1);
6927 /* Autoplay the opponent's response.
6928 * if appData.animate was TRUE when Training mode was entered,
6929 * the response will be animated.
6931 saveAnimate = appData.animate;
6932 appData.animate = animateTraining;
6933 ForwardInner(currentMove+1);
6934 appData.animate = saveAnimate;
6936 /* check for the end of the game */
6937 if (currentMove >= forwardMostMove) {
6938 gameMode = PlayFromGameFile;
6940 SetTrainingModeOff();
6941 DisplayInformation(_("End of game"));
6944 DisplayError(_("Incorrect move"), 0);
6949 /* Ok, now we know that the move is good, so we can kill
6950 the previous line in Analysis Mode */
6951 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6952 && currentMove < forwardMostMove) {
6953 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6954 else forwardMostMove = currentMove;
6959 /* If we need the chess program but it's dead, restart it */
6960 ResurrectChessProgram();
6962 /* A user move restarts a paused game*/
6966 thinkOutput[0] = NULLCHAR;
6968 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6970 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6971 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6975 if (gameMode == BeginningOfGame) {
6976 if (appData.noChessProgram) {
6977 gameMode = EditGame;
6981 gameMode = MachinePlaysBlack;
6984 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6986 if (first.sendName) {
6987 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6988 SendToProgram(buf, &first);
6995 /* Relay move to ICS or chess engine */
6996 if (appData.icsActive) {
6997 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6998 gameMode == IcsExamining) {
6999 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7000 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7002 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7004 // also send plain move, in case ICS does not understand atomic claims
7005 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7009 if (first.sendTime && (gameMode == BeginningOfGame ||
7010 gameMode == MachinePlaysWhite ||
7011 gameMode == MachinePlaysBlack)) {
7012 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7014 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7015 // [HGM] book: if program might be playing, let it use book
7016 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7017 first.maybeThinking = TRUE;
7018 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7019 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7020 SendBoard(&first, currentMove+1);
7021 if(second.analyzing) {
7022 if(!second.useSetboard) SendToProgram("undo\n", &second);
7023 SendBoard(&second, currentMove+1);
7026 SendMoveToProgram(forwardMostMove-1, &first);
7027 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7029 if (currentMove == cmailOldMove + 1) {
7030 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7034 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7038 if(appData.testLegality)
7039 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7045 if (WhiteOnMove(currentMove)) {
7046 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7048 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7052 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7057 case MachinePlaysBlack:
7058 case MachinePlaysWhite:
7059 /* disable certain menu options while machine is thinking */
7060 SetMachineThinkingEnables();
7067 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7068 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7070 if(bookHit) { // [HGM] book: simulate book reply
7071 static char bookMove[MSG_SIZ]; // a bit generous?
7073 programStats.nodes = programStats.depth = programStats.time =
7074 programStats.score = programStats.got_only_move = 0;
7075 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7077 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7078 strcat(bookMove, bookHit);
7079 HandleMachineMove(bookMove, &first);
7085 MarkByFEN(char *fen)
7088 if(!appData.markers || !appData.highlightDragging) return;
7089 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7090 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7094 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7095 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7096 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7097 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7098 if(*fen == 'T') marker[r][f++] = 0; else
7099 if(*fen == 'Y') marker[r][f++] = 1; else
7100 if(*fen == 'G') marker[r][f++] = 3; else
7101 if(*fen == 'B') marker[r][f++] = 4; else
7102 if(*fen == 'C') marker[r][f++] = 5; else
7103 if(*fen == 'M') marker[r][f++] = 6; else
7104 if(*fen == 'W') marker[r][f++] = 7; else
7105 if(*fen == 'D') marker[r][f++] = 8; else
7106 if(*fen == 'R') marker[r][f++] = 2; else {
7107 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7110 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7114 DrawPosition(TRUE, NULL);
7117 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7120 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7122 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7123 Markers *m = (Markers *) closure;
7124 if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7125 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7126 || kind == WhiteCapturesEnPassant
7127 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7128 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7132 MarkTargetSquares (int clear)
7135 if(clear) { // no reason to ever suppress clearing
7136 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;
7137 if(!sum) return; // nothing was cleared,no redraw needed
7140 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7141 !appData.testLegality || gameMode == EditPosition) return;
7142 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7143 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7144 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7146 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7149 DrawPosition(FALSE, NULL);
7153 Explode (Board board, int fromX, int fromY, int toX, int toY)
7155 if(gameInfo.variant == VariantAtomic &&
7156 (board[toY][toX] != EmptySquare || // capture?
7157 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7158 board[fromY][fromX] == BlackPawn )
7160 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7166 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7169 CanPromote (ChessSquare piece, int y)
7171 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7172 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7173 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7174 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7175 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7176 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7177 return (piece == BlackPawn && y == 1 ||
7178 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7179 piece == BlackLance && y == 1 ||
7180 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7184 HoverEvent (int xPix, int yPix, int x, int y)
7186 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7188 if(dragging == 2) DragPieceMove(xPix, yPix); // [HGM] lion: drag without button for second leg
7189 if(!first.highlight) return;
7190 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7191 if(x == oldX && y == oldY) return; // only do something if we enter new square
7192 oldFromX = fromX; oldFromY = fromY;
7193 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7194 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7195 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7196 else if(oldX != x || oldY != y) {
7197 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7198 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7199 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7200 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7202 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7203 SendToProgram(buf, &first);
7206 // SetHighlights(fromX, fromY, x, y);
7210 void ReportClick(char *action, int x, int y)
7212 char buf[MSG_SIZ]; // Inform engine of what user does
7214 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7215 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7216 if(!first.highlight || gameMode == EditPosition) return;
7217 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7218 SendToProgram(buf, &first);
7222 LeftClick (ClickType clickType, int xPix, int yPix)
7225 Boolean saveAnimate;
7226 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7227 char promoChoice = NULLCHAR;
7229 static TimeMark lastClickTime, prevClickTime;
7231 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7233 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7235 if (clickType == Press) ErrorPopDown();
7236 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7238 x = EventToSquare(xPix, BOARD_WIDTH);
7239 y = EventToSquare(yPix, BOARD_HEIGHT);
7240 if (!flipView && y >= 0) {
7241 y = BOARD_HEIGHT - 1 - y;
7243 if (flipView && x >= 0) {
7244 x = BOARD_WIDTH - 1 - x;
7247 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7248 defaultPromoChoice = promoSweep;
7249 promoSweep = EmptySquare; // terminate sweep
7250 promoDefaultAltered = TRUE;
7251 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7254 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7255 if(clickType == Release) return; // ignore upclick of click-click destination
7256 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7257 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7258 if(gameInfo.holdingsWidth &&
7259 (WhiteOnMove(currentMove)
7260 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7261 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7262 // click in right holdings, for determining promotion piece
7263 ChessSquare p = boards[currentMove][y][x];
7264 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7265 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7266 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7267 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7272 DrawPosition(FALSE, boards[currentMove]);
7276 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7277 if(clickType == Press
7278 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7279 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7280 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7283 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7284 // could be static click on premove from-square: abort premove
7286 ClearPremoveHighlights();
7289 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7290 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7292 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7293 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7294 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7295 defaultPromoChoice = DefaultPromoChoice(side);
7298 autoQueen = appData.alwaysPromoteToQueen;
7302 gatingPiece = EmptySquare;
7303 if (clickType != Press) {
7304 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7305 DragPieceEnd(xPix, yPix); dragging = 0;
7306 DrawPosition(FALSE, NULL);
7310 doubleClick = FALSE;
7311 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7312 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7314 fromX = x; fromY = y; toX = toY = killX = killY = -1;
7315 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7316 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7317 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7319 if (OKToStartUserMove(fromX, fromY)) {
7321 ReportClick("lift", x, y);
7322 MarkTargetSquares(0);
7323 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7324 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7325 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7326 promoSweep = defaultPromoChoice;
7327 selectFlag = 0; lastX = xPix; lastY = yPix;
7328 Sweep(0); // Pawn that is going to promote: preview promotion piece
7329 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7331 if (appData.highlightDragging) {
7332 SetHighlights(fromX, fromY, -1, -1);
7336 } else fromX = fromY = -1;
7342 if (clickType == Press && gameMode != EditPosition) {
7347 // ignore off-board to clicks
7348 if(y < 0 || x < 0) return;
7350 /* Check if clicking again on the same color piece */
7351 fromP = boards[currentMove][fromY][fromX];
7352 toP = boards[currentMove][y][x];
7353 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7354 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7355 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7356 WhitePawn <= toP && toP <= WhiteKing &&
7357 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7358 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7359 (BlackPawn <= fromP && fromP <= BlackKing &&
7360 BlackPawn <= toP && toP <= BlackKing &&
7361 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7362 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7363 /* Clicked again on same color piece -- changed his mind */
7364 second = (x == fromX && y == fromY);
7366 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7367 second = FALSE; // first double-click rather than scond click
7368 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7370 promoDefaultAltered = FALSE;
7371 MarkTargetSquares(1);
7372 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7373 if (appData.highlightDragging) {
7374 SetHighlights(x, y, -1, -1);
7378 if (OKToStartUserMove(x, y)) {
7379 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7380 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7381 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7382 gatingPiece = boards[currentMove][fromY][fromX];
7383 else gatingPiece = doubleClick ? fromP : EmptySquare;
7385 fromY = y; dragging = 1;
7386 ReportClick("lift", x, y);
7387 MarkTargetSquares(0);
7388 DragPieceBegin(xPix, yPix, FALSE);
7389 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7390 promoSweep = defaultPromoChoice;
7391 selectFlag = 0; lastX = xPix; lastY = yPix;
7392 Sweep(0); // Pawn that is going to promote: preview promotion piece
7396 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7399 // ignore clicks on holdings
7400 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7403 if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7404 DragPieceEnd(xPix, yPix); dragging = 0;
7406 // a deferred attempt to click-click move an empty square on top of a piece
7407 boards[currentMove][y][x] = EmptySquare;
7409 DrawPosition(FALSE, boards[currentMove]);
7410 fromX = fromY = -1; clearFlag = 0;
7413 if (appData.animateDragging) {
7414 /* Undo animation damage if any */
7415 DrawPosition(FALSE, NULL);
7417 if (second || sweepSelecting) {
7418 /* Second up/down in same square; just abort move */
7419 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7420 second = sweepSelecting = 0;
7422 gatingPiece = EmptySquare;
7423 MarkTargetSquares(1);
7426 ClearPremoveHighlights();
7428 /* First upclick in same square; start click-click mode */
7429 SetHighlights(x, y, -1, -1);
7436 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7437 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7438 DisplayMessage(_("only marked squares are legal"),"");
7439 DrawPosition(TRUE, NULL);
7440 return; // ignore to-click
7443 /* we now have a different from- and (possibly off-board) to-square */
7444 /* Completed move */
7445 if(!sweepSelecting) {
7450 saveAnimate = appData.animate;
7451 if (clickType == Press) {
7452 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7453 // must be Edit Position mode with empty-square selected
7454 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7455 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7458 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7462 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7463 killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7465 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7466 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7467 if(appData.sweepSelect) {
7468 ChessSquare piece = boards[currentMove][fromY][fromX];
7469 promoSweep = defaultPromoChoice;
7470 if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7471 selectFlag = 0; lastX = xPix; lastY = yPix;
7472 Sweep(0); // Pawn that is going to promote: preview promotion piece
7474 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7475 MarkTargetSquares(1);
7477 return; // promo popup appears on up-click
7479 /* Finish clickclick move */
7480 if (appData.animate || appData.highlightLastMove) {
7481 SetHighlights(fromX, fromY, toX, toY);
7485 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7487 if (appData.animate || appData.highlightLastMove) {
7488 SetHighlights(fromX, fromY, toX, toY);
7494 // [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
7495 /* Finish drag move */
7496 if (appData.highlightLastMove) {
7497 SetHighlights(fromX, fromY, toX, toY);
7502 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7503 dragging *= 2; // flag button-less dragging if we are dragging
7504 MarkTargetSquares(1);
7505 if(x == killX && y == killY) killX = killY = -1; else {
7506 killX = x; killY = y; //remeber this square as intermediate
7507 MarkTargetSquares(0);
7508 ReportClick("put", x, y); // and inform engine
7509 ReportClick("lift", x, y);
7513 DragPieceEnd(xPix, yPix); dragging = 0;
7514 /* Don't animate move and drag both */
7515 appData.animate = FALSE;
7518 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7519 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7520 ChessSquare piece = boards[currentMove][fromY][fromX];
7521 if(gameMode == EditPosition && piece != EmptySquare &&
7522 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7525 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7526 n = PieceToNumber(piece - (int)BlackPawn);
7527 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7528 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7529 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7531 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7532 n = PieceToNumber(piece);
7533 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7534 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7535 boards[currentMove][n][BOARD_WIDTH-2]++;
7537 boards[currentMove][fromY][fromX] = EmptySquare;
7541 MarkTargetSquares(1);
7542 DrawPosition(TRUE, boards[currentMove]);
7546 // off-board moves should not be highlighted
7547 if(x < 0 || y < 0) ClearHighlights();
7548 else ReportClick("put", x, y);
7550 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7552 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7553 SetHighlights(fromX, fromY, toX, toY);
7554 MarkTargetSquares(1);
7555 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7556 // [HGM] super: promotion to captured piece selected from holdings
7557 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7558 promotionChoice = TRUE;
7559 // kludge follows to temporarily execute move on display, without promoting yet
7560 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7561 boards[currentMove][toY][toX] = p;
7562 DrawPosition(FALSE, boards[currentMove]);
7563 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7564 boards[currentMove][toY][toX] = q;
7565 DisplayMessage("Click in holdings to choose piece", "");
7570 int oldMove = currentMove;
7571 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7572 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7573 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7574 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7575 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7576 DrawPosition(TRUE, boards[currentMove]);
7577 MarkTargetSquares(1);
7580 appData.animate = saveAnimate;
7581 if (appData.animate || appData.animateDragging) {
7582 /* Undo animation damage if needed */
7583 DrawPosition(FALSE, NULL);
7588 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7589 { // front-end-free part taken out of PieceMenuPopup
7590 int whichMenu; int xSqr, ySqr;
7592 if(seekGraphUp) { // [HGM] seekgraph
7593 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7594 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7598 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7599 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7600 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7601 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7602 if(action == Press) {
7603 originalFlip = flipView;
7604 flipView = !flipView; // temporarily flip board to see game from partners perspective
7605 DrawPosition(TRUE, partnerBoard);
7606 DisplayMessage(partnerStatus, "");
7608 } else if(action == Release) {
7609 flipView = originalFlip;
7610 DrawPosition(TRUE, boards[currentMove]);
7616 xSqr = EventToSquare(x, BOARD_WIDTH);
7617 ySqr = EventToSquare(y, BOARD_HEIGHT);
7618 if (action == Release) {
7619 if(pieceSweep != EmptySquare) {
7620 EditPositionMenuEvent(pieceSweep, toX, toY);
7621 pieceSweep = EmptySquare;
7622 } else UnLoadPV(); // [HGM] pv
7624 if (action != Press) return -2; // return code to be ignored
7627 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7629 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7630 if (xSqr < 0 || ySqr < 0) return -1;
7631 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7632 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7633 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7634 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7638 if(!appData.icsEngineAnalyze) return -1;
7639 case IcsPlayingWhite:
7640 case IcsPlayingBlack:
7641 if(!appData.zippyPlay) goto noZip;
7644 case MachinePlaysWhite:
7645 case MachinePlaysBlack:
7646 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7647 if (!appData.dropMenu) {
7649 return 2; // flag front-end to grab mouse events
7651 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7652 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7655 if (xSqr < 0 || ySqr < 0) return -1;
7656 if (!appData.dropMenu || appData.testLegality &&
7657 gameInfo.variant != VariantBughouse &&
7658 gameInfo.variant != VariantCrazyhouse) return -1;
7659 whichMenu = 1; // drop menu
7665 if (((*fromX = xSqr) < 0) ||
7666 ((*fromY = ySqr) < 0)) {
7667 *fromX = *fromY = -1;
7671 *fromX = BOARD_WIDTH - 1 - *fromX;
7673 *fromY = BOARD_HEIGHT - 1 - *fromY;
7679 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7681 // char * hint = lastHint;
7682 FrontEndProgramStats stats;
7684 stats.which = cps == &first ? 0 : 1;
7685 stats.depth = cpstats->depth;
7686 stats.nodes = cpstats->nodes;
7687 stats.score = cpstats->score;
7688 stats.time = cpstats->time;
7689 stats.pv = cpstats->movelist;
7690 stats.hint = lastHint;
7691 stats.an_move_index = 0;
7692 stats.an_move_count = 0;
7694 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7695 stats.hint = cpstats->move_name;
7696 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7697 stats.an_move_count = cpstats->nr_moves;
7700 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
7702 SetProgramStats( &stats );
7706 ClearEngineOutputPane (int which)
7708 static FrontEndProgramStats dummyStats;
7709 dummyStats.which = which;
7710 dummyStats.pv = "#";
7711 SetProgramStats( &dummyStats );
7714 #define MAXPLAYERS 500
7717 TourneyStandings (int display)
7719 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7720 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7721 char result, *p, *names[MAXPLAYERS];
7723 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7724 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7725 names[0] = p = strdup(appData.participants);
7726 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7728 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7730 while(result = appData.results[nr]) {
7731 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7732 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7733 wScore = bScore = 0;
7735 case '+': wScore = 2; break;
7736 case '-': bScore = 2; break;
7737 case '=': wScore = bScore = 1; break;
7739 case '*': return strdup("busy"); // tourney not finished
7747 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7748 for(w=0; w<nPlayers; w++) {
7750 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7751 ranking[w] = b; points[w] = bScore; score[b] = -2;
7753 p = malloc(nPlayers*34+1);
7754 for(w=0; w<nPlayers && w<display; w++)
7755 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7761 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7762 { // count all piece types
7764 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7765 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7766 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7769 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7770 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7771 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7772 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7773 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7774 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7779 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7781 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7782 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7784 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7785 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7786 if(myPawns == 2 && nMine == 3) // KPP
7787 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7788 if(myPawns == 1 && nMine == 2) // KP
7789 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7790 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7791 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7792 if(myPawns) return FALSE;
7793 if(pCnt[WhiteRook+side])
7794 return pCnt[BlackRook-side] ||
7795 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7796 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7797 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7798 if(pCnt[WhiteCannon+side]) {
7799 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7800 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7802 if(pCnt[WhiteKnight+side])
7803 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7808 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7810 VariantClass v = gameInfo.variant;
7812 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7813 if(v == VariantShatranj) return TRUE; // always winnable through baring
7814 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7815 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7817 if(v == VariantXiangqi) {
7818 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7820 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7821 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7822 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7823 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7824 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7825 if(stale) // we have at least one last-rank P plus perhaps C
7826 return majors // KPKX
7827 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7829 return pCnt[WhiteFerz+side] // KCAK
7830 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7831 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7832 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7834 } else if(v == VariantKnightmate) {
7835 if(nMine == 1) return FALSE;
7836 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7837 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7838 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7840 if(nMine == 1) return FALSE; // bare King
7841 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
7842 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7843 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7844 // by now we have King + 1 piece (or multiple Bishops on the same color)
7845 if(pCnt[WhiteKnight+side])
7846 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7847 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7848 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7850 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7851 if(pCnt[WhiteAlfil+side])
7852 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7853 if(pCnt[WhiteWazir+side])
7854 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7861 CompareWithRights (Board b1, Board b2)
7864 if(!CompareBoards(b1, b2)) return FALSE;
7865 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7866 /* compare castling rights */
7867 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7868 rights++; /* King lost rights, while rook still had them */
7869 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7870 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7871 rights++; /* but at least one rook lost them */
7873 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7875 if( b1[CASTLING][5] != NoRights ) {
7876 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7883 Adjudicate (ChessProgramState *cps)
7884 { // [HGM] some adjudications useful with buggy engines
7885 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7886 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7887 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7888 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7889 int k, drop, count = 0; static int bare = 1;
7890 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7891 Boolean canAdjudicate = !appData.icsActive;
7893 // most tests only when we understand the game, i.e. legality-checking on
7894 if( appData.testLegality )
7895 { /* [HGM] Some more adjudications for obstinate engines */
7896 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7897 static int moveCount = 6;
7899 char *reason = NULL;
7901 /* Count what is on board. */
7902 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7904 /* Some material-based adjudications that have to be made before stalemate test */
7905 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7906 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7907 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7908 if(canAdjudicate && appData.checkMates) {
7910 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7911 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7912 "Xboard adjudication: King destroyed", GE_XBOARD );
7917 /* Bare King in Shatranj (loses) or Losers (wins) */
7918 if( nrW == 1 || nrB == 1) {
7919 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7920 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7921 if(canAdjudicate && appData.checkMates) {
7923 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7924 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7925 "Xboard adjudication: Bare king", GE_XBOARD );
7929 if( gameInfo.variant == VariantShatranj && --bare < 0)
7931 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7932 if(canAdjudicate && appData.checkMates) {
7933 /* but only adjudicate if adjudication enabled */
7935 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7936 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7937 "Xboard adjudication: Bare king", GE_XBOARD );
7944 // don't wait for engine to announce game end if we can judge ourselves
7945 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7947 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7948 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7949 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7950 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7953 reason = "Xboard adjudication: 3rd check";
7954 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7964 reason = "Xboard adjudication: Stalemate";
7965 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7966 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7967 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7968 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7969 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7970 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7971 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7972 EP_CHECKMATE : EP_WINS);
7973 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7974 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7978 reason = "Xboard adjudication: Checkmate";
7979 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7980 if(gameInfo.variant == VariantShogi) {
7981 if(forwardMostMove > backwardMostMove
7982 && moveList[forwardMostMove-1][1] == '@'
7983 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7984 reason = "XBoard adjudication: pawn-drop mate";
7985 boards[forwardMostMove][EP_STATUS] = EP_WINS;
7991 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7993 result = GameIsDrawn; break;
7995 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7997 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8001 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8003 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8004 GameEnds( result, reason, GE_XBOARD );
8008 /* Next absolutely insufficient mating material. */
8009 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8010 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8011 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8013 /* always flag draws, for judging claims */
8014 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8016 if(canAdjudicate && appData.materialDraws) {
8017 /* but only adjudicate them if adjudication enabled */
8018 if(engineOpponent) {
8019 SendToProgram("force\n", engineOpponent); // suppress reply
8020 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8022 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8027 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8028 if(gameInfo.variant == VariantXiangqi ?
8029 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8031 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8032 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8033 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8034 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8036 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8037 { /* if the first 3 moves do not show a tactical win, declare draw */
8038 if(engineOpponent) {
8039 SendToProgram("force\n", engineOpponent); // suppress reply
8040 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8042 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8045 } else moveCount = 6;
8048 // Repetition draws and 50-move rule can be applied independently of legality testing
8050 /* Check for rep-draws */
8052 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8053 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8054 for(k = forwardMostMove-2;
8055 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8056 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8057 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8060 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8061 /* compare castling rights */
8062 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8063 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8064 rights++; /* King lost rights, while rook still had them */
8065 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8066 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8067 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8068 rights++; /* but at least one rook lost them */
8070 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8071 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8073 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8074 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8075 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8078 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8079 && appData.drawRepeats > 1) {
8080 /* adjudicate after user-specified nr of repeats */
8081 int result = GameIsDrawn;
8082 char *details = "XBoard adjudication: repetition draw";
8083 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8084 // [HGM] xiangqi: check for forbidden perpetuals
8085 int m, ourPerpetual = 1, hisPerpetual = 1;
8086 for(m=forwardMostMove; m>k; m-=2) {
8087 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8088 ourPerpetual = 0; // the current mover did not always check
8089 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8090 hisPerpetual = 0; // the opponent did not always check
8092 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8093 ourPerpetual, hisPerpetual);
8094 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8095 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8096 details = "Xboard adjudication: perpetual checking";
8098 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8099 break; // (or we would have caught him before). Abort repetition-checking loop.
8101 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8102 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8104 details = "Xboard adjudication: repetition";
8106 } else // it must be XQ
8107 // Now check for perpetual chases
8108 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8109 hisPerpetual = PerpetualChase(k, forwardMostMove);
8110 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8111 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8112 static char resdet[MSG_SIZ];
8113 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8115 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8117 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8118 break; // Abort repetition-checking loop.
8120 // if neither of us is checking or chasing all the time, or both are, it is draw
8122 if(engineOpponent) {
8123 SendToProgram("force\n", engineOpponent); // suppress reply
8124 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8126 GameEnds( result, details, GE_XBOARD );
8129 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8130 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8134 /* Now we test for 50-move draws. Determine ply count */
8135 count = forwardMostMove;
8136 /* look for last irreversble move */
8137 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8139 /* if we hit starting position, add initial plies */
8140 if( count == backwardMostMove )
8141 count -= initialRulePlies;
8142 count = forwardMostMove - count;
8143 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8144 // adjust reversible move counter for checks in Xiangqi
8145 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8146 if(i < backwardMostMove) i = backwardMostMove;
8147 while(i <= forwardMostMove) {
8148 lastCheck = inCheck; // check evasion does not count
8149 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8150 if(inCheck || lastCheck) count--; // check does not count
8155 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8156 /* this is used to judge if draw claims are legal */
8157 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8158 if(engineOpponent) {
8159 SendToProgram("force\n", engineOpponent); // suppress reply
8160 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8162 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8166 /* if draw offer is pending, treat it as a draw claim
8167 * when draw condition present, to allow engines a way to
8168 * claim draws before making their move to avoid a race
8169 * condition occurring after their move
8171 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8173 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8174 p = "Draw claim: 50-move rule";
8175 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8176 p = "Draw claim: 3-fold repetition";
8177 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8178 p = "Draw claim: insufficient mating material";
8179 if( p != NULL && canAdjudicate) {
8180 if(engineOpponent) {
8181 SendToProgram("force\n", engineOpponent); // suppress reply
8182 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8184 GameEnds( GameIsDrawn, p, GE_XBOARD );
8189 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8190 if(engineOpponent) {
8191 SendToProgram("force\n", engineOpponent); // suppress reply
8192 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8194 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8201 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8202 { // [HGM] book: this routine intercepts moves to simulate book replies
8203 char *bookHit = NULL;
8205 //first determine if the incoming move brings opponent into his book
8206 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8207 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8208 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8209 if(bookHit != NULL && !cps->bookSuspend) {
8210 // make sure opponent is not going to reply after receiving move to book position
8211 SendToProgram("force\n", cps);
8212 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8214 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8215 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8216 // now arrange restart after book miss
8218 // after a book hit we never send 'go', and the code after the call to this routine
8219 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8220 char buf[MSG_SIZ], *move = bookHit;
8222 int fromX, fromY, toX, toY;
8226 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8227 &fromX, &fromY, &toX, &toY, &promoChar)) {
8228 (void) CoordsToAlgebraic(boards[forwardMostMove],
8229 PosFlags(forwardMostMove),
8230 fromY, fromX, toY, toX, promoChar, move);
8232 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8236 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8237 SendToProgram(buf, cps);
8238 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8239 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8240 SendToProgram("go\n", cps);
8241 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8242 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8243 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8244 SendToProgram("go\n", cps);
8245 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8247 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8251 LoadError (char *errmess, ChessProgramState *cps)
8252 { // unloads engine and switches back to -ncp mode if it was first
8253 if(cps->initDone) return FALSE;
8254 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8255 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8258 appData.noChessProgram = TRUE;
8259 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8260 gameMode = BeginningOfGame; ModeHighlight();
8263 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8264 DisplayMessage("", ""); // erase waiting message
8265 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8270 ChessProgramState *savedState;
8272 DeferredBookMove (void)
8274 if(savedState->lastPing != savedState->lastPong)
8275 ScheduleDelayedEvent(DeferredBookMove, 10);
8277 HandleMachineMove(savedMessage, savedState);
8280 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8281 static ChessProgramState *stalledEngine;
8282 static char stashedInputMove[MSG_SIZ];
8285 HandleMachineMove (char *message, ChessProgramState *cps)
8287 static char firstLeg[20];
8288 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8289 char realname[MSG_SIZ];
8290 int fromX, fromY, toX, toY;
8292 char promoChar, roar;
8294 int machineWhite, oldError;
8297 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8298 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8299 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8300 DisplayError(_("Invalid pairing from pairing engine"), 0);
8303 pairingReceived = 1;
8305 return; // Skim the pairing messages here.
8308 oldError = cps->userError; cps->userError = 0;
8310 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8312 * Kludge to ignore BEL characters
8314 while (*message == '\007') message++;
8317 * [HGM] engine debug message: ignore lines starting with '#' character
8319 if(cps->debug && *message == '#') return;
8322 * Look for book output
8324 if (cps == &first && bookRequested) {
8325 if (message[0] == '\t' || message[0] == ' ') {
8326 /* Part of the book output is here; append it */
8327 strcat(bookOutput, message);
8328 strcat(bookOutput, " \n");
8330 } else if (bookOutput[0] != NULLCHAR) {
8331 /* All of book output has arrived; display it */
8332 char *p = bookOutput;
8333 while (*p != NULLCHAR) {
8334 if (*p == '\t') *p = ' ';
8337 DisplayInformation(bookOutput);
8338 bookRequested = FALSE;
8339 /* Fall through to parse the current output */
8344 * Look for machine move.
8346 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8347 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8349 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8350 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8351 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8352 stalledEngine = cps;
8353 if(appData.ponderNextMove) { // bring opponent out of ponder
8354 if(gameMode == TwoMachinesPlay) {
8355 if(cps->other->pause)
8356 PauseEngine(cps->other);
8358 SendToProgram("easy\n", cps->other);
8365 /* This method is only useful on engines that support ping */
8366 if (cps->lastPing != cps->lastPong) {
8367 if (gameMode == BeginningOfGame) {
8368 /* Extra move from before last new; ignore */
8369 if (appData.debugMode) {
8370 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8373 if (appData.debugMode) {
8374 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8375 cps->which, gameMode);
8378 SendToProgram("undo\n", cps);
8384 case BeginningOfGame:
8385 /* Extra move from before last reset; ignore */
8386 if (appData.debugMode) {
8387 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8394 /* Extra move after we tried to stop. The mode test is
8395 not a reliable way of detecting this problem, but it's
8396 the best we can do on engines that don't support ping.
8398 if (appData.debugMode) {
8399 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8400 cps->which, gameMode);
8402 SendToProgram("undo\n", cps);
8405 case MachinePlaysWhite:
8406 case IcsPlayingWhite:
8407 machineWhite = TRUE;
8410 case MachinePlaysBlack:
8411 case IcsPlayingBlack:
8412 machineWhite = FALSE;
8415 case TwoMachinesPlay:
8416 machineWhite = (cps->twoMachinesColor[0] == 'w');
8419 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8420 if (appData.debugMode) {
8422 "Ignoring move out of turn by %s, gameMode %d"
8423 ", forwardMost %d\n",
8424 cps->which, gameMode, forwardMostMove);
8429 if(cps->alphaRank) AlphaRank(machineMove, 4);
8431 // [HGM] lion: (some very limited) support for Alien protocol
8433 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8434 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8436 } else if(firstLeg[0]) { // there was a previous leg;
8437 // only support case where same piece makes two step (and don't even test that!)
8438 char buf[20], *p = machineMove+1, *q = buf+1, f;
8439 safeStrCpy(buf, machineMove, 20);
8440 while(isdigit(*q)) q++; // find start of to-square
8441 safeStrCpy(machineMove, firstLeg, 20);
8442 while(isdigit(*p)) p++;
8443 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8444 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8445 firstLeg[0] = NULLCHAR;
8448 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8449 &fromX, &fromY, &toX, &toY, &promoChar)) {
8450 /* Machine move could not be parsed; ignore it. */
8451 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8452 machineMove, _(cps->which));
8453 DisplayMoveError(buf1);
8454 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8455 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8456 if (gameMode == TwoMachinesPlay) {
8457 GameEnds(machineWhite ? BlackWins : WhiteWins,
8463 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8464 /* So we have to redo legality test with true e.p. status here, */
8465 /* to make sure an illegal e.p. capture does not slip through, */
8466 /* to cause a forfeit on a justified illegal-move complaint */
8467 /* of the opponent. */
8468 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8470 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8471 fromY, fromX, toY, toX, promoChar);
8472 if(moveType == IllegalMove) {
8473 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8474 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8475 GameEnds(machineWhite ? BlackWins : WhiteWins,
8478 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8479 /* [HGM] Kludge to handle engines that send FRC-style castling
8480 when they shouldn't (like TSCP-Gothic) */
8482 case WhiteASideCastleFR:
8483 case BlackASideCastleFR:
8485 currentMoveString[2]++;
8487 case WhiteHSideCastleFR:
8488 case BlackHSideCastleFR:
8490 currentMoveString[2]--;
8492 default: ; // nothing to do, but suppresses warning of pedantic compilers
8495 hintRequested = FALSE;
8496 lastHint[0] = NULLCHAR;
8497 bookRequested = FALSE;
8498 /* Program may be pondering now */
8499 cps->maybeThinking = TRUE;
8500 if (cps->sendTime == 2) cps->sendTime = 1;
8501 if (cps->offeredDraw) cps->offeredDraw--;
8503 /* [AS] Save move info*/
8504 pvInfoList[ forwardMostMove ].score = programStats.score;
8505 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8506 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8508 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8510 /* Test suites abort the 'game' after one move */
8511 if(*appData.finger) {
8513 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8514 if(!f) f = fopen(appData.finger, "w");
8515 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8516 else { DisplayFatalError("Bad output file", errno, 0); return; }
8518 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8521 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8522 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8525 while( count < adjudicateLossPlies ) {
8526 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8529 score = -score; /* Flip score for winning side */
8532 if( score > adjudicateLossThreshold ) {
8539 if( count >= adjudicateLossPlies ) {
8540 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8542 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8543 "Xboard adjudication",
8550 if(Adjudicate(cps)) {
8551 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8552 return; // [HGM] adjudicate: for all automatic game ends
8556 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8558 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8559 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8561 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8563 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8565 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8566 char buf[3*MSG_SIZ];
8568 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8569 programStats.score / 100.,
8571 programStats.time / 100.,
8572 (unsigned int)programStats.nodes,
8573 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8574 programStats.movelist);
8576 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8581 /* [AS] Clear stats for next move */
8582 ClearProgramStats();
8583 thinkOutput[0] = NULLCHAR;
8584 hiddenThinkOutputState = 0;
8587 if (gameMode == TwoMachinesPlay) {
8588 /* [HGM] relaying draw offers moved to after reception of move */
8589 /* and interpreting offer as claim if it brings draw condition */
8590 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8591 SendToProgram("draw\n", cps->other);
8593 if (cps->other->sendTime) {
8594 SendTimeRemaining(cps->other,
8595 cps->other->twoMachinesColor[0] == 'w');
8597 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8598 if (firstMove && !bookHit) {
8600 if (cps->other->useColors) {
8601 SendToProgram(cps->other->twoMachinesColor, cps->other);
8603 SendToProgram("go\n", cps->other);
8605 cps->other->maybeThinking = TRUE;
8608 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8610 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8612 if (!pausing && appData.ringBellAfterMoves) {
8613 if(!roar) RingBell();
8617 * Reenable menu items that were disabled while
8618 * machine was thinking
8620 if (gameMode != TwoMachinesPlay)
8621 SetUserThinkingEnables();
8623 // [HGM] book: after book hit opponent has received move and is now in force mode
8624 // force the book reply into it, and then fake that it outputted this move by jumping
8625 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8627 static char bookMove[MSG_SIZ]; // a bit generous?
8629 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8630 strcat(bookMove, bookHit);
8633 programStats.nodes = programStats.depth = programStats.time =
8634 programStats.score = programStats.got_only_move = 0;
8635 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8637 if(cps->lastPing != cps->lastPong) {
8638 savedMessage = message; // args for deferred call
8640 ScheduleDelayedEvent(DeferredBookMove, 10);
8649 /* Set special modes for chess engines. Later something general
8650 * could be added here; for now there is just one kludge feature,
8651 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8652 * when "xboard" is given as an interactive command.
8654 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8655 cps->useSigint = FALSE;
8656 cps->useSigterm = FALSE;
8658 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8659 ParseFeatures(message+8, cps);
8660 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8663 if (!strncmp(message, "setup ", 6) &&
8664 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8665 ) { // [HGM] allow first engine to define opening position
8666 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8667 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8669 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8670 if(startedFromSetupPosition) return;
8671 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8673 while(message[s] && message[s++] != ' ');
8674 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8675 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8676 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8677 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8678 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8679 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8682 ParseFEN(boards[0], &dummy, message+s, FALSE);
8683 DrawPosition(TRUE, boards[0]);
8684 startedFromSetupPosition = TRUE;
8687 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8688 * want this, I was asked to put it in, and obliged.
8690 if (!strncmp(message, "setboard ", 9)) {
8691 Board initial_position;
8693 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8695 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8696 DisplayError(_("Bad FEN received from engine"), 0);
8700 CopyBoard(boards[0], initial_position);
8701 initialRulePlies = FENrulePlies;
8702 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8703 else gameMode = MachinePlaysBlack;
8704 DrawPosition(FALSE, boards[currentMove]);
8710 * Look for communication commands
8712 if (!strncmp(message, "telluser ", 9)) {
8713 if(message[9] == '\\' && message[10] == '\\')
8714 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8716 DisplayNote(message + 9);
8719 if (!strncmp(message, "tellusererror ", 14)) {
8721 if(message[14] == '\\' && message[15] == '\\')
8722 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8724 DisplayError(message + 14, 0);
8727 if (!strncmp(message, "tellopponent ", 13)) {
8728 if (appData.icsActive) {
8730 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8734 DisplayNote(message + 13);
8738 if (!strncmp(message, "tellothers ", 11)) {
8739 if (appData.icsActive) {
8741 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8744 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8747 if (!strncmp(message, "tellall ", 8)) {
8748 if (appData.icsActive) {
8750 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8754 DisplayNote(message + 8);
8758 if (strncmp(message, "warning", 7) == 0) {
8759 /* Undocumented feature, use tellusererror in new code */
8760 DisplayError(message, 0);
8763 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8764 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8765 strcat(realname, " query");
8766 AskQuestion(realname, buf2, buf1, cps->pr);
8769 /* Commands from the engine directly to ICS. We don't allow these to be
8770 * sent until we are logged on. Crafty kibitzes have been known to
8771 * interfere with the login process.
8774 if (!strncmp(message, "tellics ", 8)) {
8775 SendToICS(message + 8);
8779 if (!strncmp(message, "tellicsnoalias ", 15)) {
8780 SendToICS(ics_prefix);
8781 SendToICS(message + 15);
8785 /* The following are for backward compatibility only */
8786 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8787 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8788 SendToICS(ics_prefix);
8794 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8797 if(!strncmp(message, "highlight ", 10)) {
8798 if(appData.testLegality && appData.markers) return;
8799 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8802 if(!strncmp(message, "click ", 6)) {
8803 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8804 if(appData.testLegality || !appData.oneClick) return;
8805 sscanf(message+6, "%c%d%c", &f, &y, &c);
8806 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8807 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8808 x = x*squareSize + (x+1)*lineGap + squareSize/2;
8809 y = y*squareSize + (y+1)*lineGap + squareSize/2;
8810 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8811 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8812 LeftClick(Release, lastLeftX, lastLeftY);
8813 controlKey = (c == ',');
8814 LeftClick(Press, x, y);
8815 LeftClick(Release, x, y);
8816 first.highlight = f;
8820 * If the move is illegal, cancel it and redraw the board.
8821 * Also deal with other error cases. Matching is rather loose
8822 * here to accommodate engines written before the spec.
8824 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8825 strncmp(message, "Error", 5) == 0) {
8826 if (StrStr(message, "name") ||
8827 StrStr(message, "rating") || StrStr(message, "?") ||
8828 StrStr(message, "result") || StrStr(message, "board") ||
8829 StrStr(message, "bk") || StrStr(message, "computer") ||
8830 StrStr(message, "variant") || StrStr(message, "hint") ||
8831 StrStr(message, "random") || StrStr(message, "depth") ||
8832 StrStr(message, "accepted")) {
8835 if (StrStr(message, "protover")) {
8836 /* Program is responding to input, so it's apparently done
8837 initializing, and this error message indicates it is
8838 protocol version 1. So we don't need to wait any longer
8839 for it to initialize and send feature commands. */
8840 FeatureDone(cps, 1);
8841 cps->protocolVersion = 1;
8844 cps->maybeThinking = FALSE;
8846 if (StrStr(message, "draw")) {
8847 /* Program doesn't have "draw" command */
8848 cps->sendDrawOffers = 0;
8851 if (cps->sendTime != 1 &&
8852 (StrStr(message, "time") || StrStr(message, "otim"))) {
8853 /* Program apparently doesn't have "time" or "otim" command */
8857 if (StrStr(message, "analyze")) {
8858 cps->analysisSupport = FALSE;
8859 cps->analyzing = FALSE;
8860 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8861 EditGameEvent(); // [HGM] try to preserve loaded game
8862 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8863 DisplayError(buf2, 0);
8866 if (StrStr(message, "(no matching move)st")) {
8867 /* Special kludge for GNU Chess 4 only */
8868 cps->stKludge = TRUE;
8869 SendTimeControl(cps, movesPerSession, timeControl,
8870 timeIncrement, appData.searchDepth,
8874 if (StrStr(message, "(no matching move)sd")) {
8875 /* Special kludge for GNU Chess 4 only */
8876 cps->sdKludge = TRUE;
8877 SendTimeControl(cps, movesPerSession, timeControl,
8878 timeIncrement, appData.searchDepth,
8882 if (!StrStr(message, "llegal")) {
8885 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8886 gameMode == IcsIdle) return;
8887 if (forwardMostMove <= backwardMostMove) return;
8888 if (pausing) PauseEvent();
8889 if(appData.forceIllegal) {
8890 // [HGM] illegal: machine refused move; force position after move into it
8891 SendToProgram("force\n", cps);
8892 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8893 // we have a real problem now, as SendBoard will use the a2a3 kludge
8894 // when black is to move, while there might be nothing on a2 or black
8895 // might already have the move. So send the board as if white has the move.
8896 // But first we must change the stm of the engine, as it refused the last move
8897 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8898 if(WhiteOnMove(forwardMostMove)) {
8899 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8900 SendBoard(cps, forwardMostMove); // kludgeless board
8902 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8903 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8904 SendBoard(cps, forwardMostMove+1); // kludgeless board
8906 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8907 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8908 gameMode == TwoMachinesPlay)
8909 SendToProgram("go\n", cps);
8912 if (gameMode == PlayFromGameFile) {
8913 /* Stop reading this game file */
8914 gameMode = EditGame;
8917 /* [HGM] illegal-move claim should forfeit game when Xboard */
8918 /* only passes fully legal moves */
8919 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8920 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8921 "False illegal-move claim", GE_XBOARD );
8922 return; // do not take back move we tested as valid
8924 currentMove = forwardMostMove-1;
8925 DisplayMove(currentMove-1); /* before DisplayMoveError */
8926 SwitchClocks(forwardMostMove-1); // [HGM] race
8927 DisplayBothClocks();
8928 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8929 parseList[currentMove], _(cps->which));
8930 DisplayMoveError(buf1);
8931 DrawPosition(FALSE, boards[currentMove]);
8933 SetUserThinkingEnables();
8936 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8937 /* Program has a broken "time" command that
8938 outputs a string not ending in newline.
8944 * If chess program startup fails, exit with an error message.
8945 * Attempts to recover here are futile. [HGM] Well, we try anyway
8947 if ((StrStr(message, "unknown host") != NULL)
8948 || (StrStr(message, "No remote directory") != NULL)
8949 || (StrStr(message, "not found") != NULL)
8950 || (StrStr(message, "No such file") != NULL)
8951 || (StrStr(message, "can't alloc") != NULL)
8952 || (StrStr(message, "Permission denied") != NULL)) {
8954 cps->maybeThinking = FALSE;
8955 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8956 _(cps->which), cps->program, cps->host, message);
8957 RemoveInputSource(cps->isr);
8958 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8959 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8960 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8966 * Look for hint output
8968 if (sscanf(message, "Hint: %s", buf1) == 1) {
8969 if (cps == &first && hintRequested) {
8970 hintRequested = FALSE;
8971 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8972 &fromX, &fromY, &toX, &toY, &promoChar)) {
8973 (void) CoordsToAlgebraic(boards[forwardMostMove],
8974 PosFlags(forwardMostMove),
8975 fromY, fromX, toY, toX, promoChar, buf1);
8976 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8977 DisplayInformation(buf2);
8979 /* Hint move could not be parsed!? */
8980 snprintf(buf2, sizeof(buf2),
8981 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8982 buf1, _(cps->which));
8983 DisplayError(buf2, 0);
8986 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8992 * Ignore other messages if game is not in progress
8994 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8995 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8998 * look for win, lose, draw, or draw offer
9000 if (strncmp(message, "1-0", 3) == 0) {
9001 char *p, *q, *r = "";
9002 p = strchr(message, '{');
9010 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9012 } else if (strncmp(message, "0-1", 3) == 0) {
9013 char *p, *q, *r = "";
9014 p = strchr(message, '{');
9022 /* Kludge for Arasan 4.1 bug */
9023 if (strcmp(r, "Black resigns") == 0) {
9024 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9027 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9029 } else if (strncmp(message, "1/2", 3) == 0) {
9030 char *p, *q, *r = "";
9031 p = strchr(message, '{');
9040 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9043 } else if (strncmp(message, "White resign", 12) == 0) {
9044 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9046 } else if (strncmp(message, "Black resign", 12) == 0) {
9047 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9049 } else if (strncmp(message, "White matches", 13) == 0 ||
9050 strncmp(message, "Black matches", 13) == 0 ) {
9051 /* [HGM] ignore GNUShogi noises */
9053 } else if (strncmp(message, "White", 5) == 0 &&
9054 message[5] != '(' &&
9055 StrStr(message, "Black") == NULL) {
9056 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9058 } else if (strncmp(message, "Black", 5) == 0 &&
9059 message[5] != '(') {
9060 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9062 } else if (strcmp(message, "resign") == 0 ||
9063 strcmp(message, "computer resigns") == 0) {
9065 case MachinePlaysBlack:
9066 case IcsPlayingBlack:
9067 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9069 case MachinePlaysWhite:
9070 case IcsPlayingWhite:
9071 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9073 case TwoMachinesPlay:
9074 if (cps->twoMachinesColor[0] == 'w')
9075 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9077 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9084 } else if (strncmp(message, "opponent mates", 14) == 0) {
9086 case MachinePlaysBlack:
9087 case IcsPlayingBlack:
9088 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9090 case MachinePlaysWhite:
9091 case IcsPlayingWhite:
9092 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9094 case TwoMachinesPlay:
9095 if (cps->twoMachinesColor[0] == 'w')
9096 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9098 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9105 } else if (strncmp(message, "computer mates", 14) == 0) {
9107 case MachinePlaysBlack:
9108 case IcsPlayingBlack:
9109 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9111 case MachinePlaysWhite:
9112 case IcsPlayingWhite:
9113 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9115 case TwoMachinesPlay:
9116 if (cps->twoMachinesColor[0] == 'w')
9117 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9119 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9126 } else if (strncmp(message, "checkmate", 9) == 0) {
9127 if (WhiteOnMove(forwardMostMove)) {
9128 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9130 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9133 } else if (strstr(message, "Draw") != NULL ||
9134 strstr(message, "game is a draw") != NULL) {
9135 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9137 } else if (strstr(message, "offer") != NULL &&
9138 strstr(message, "draw") != NULL) {
9140 if (appData.zippyPlay && first.initDone) {
9141 /* Relay offer to ICS */
9142 SendToICS(ics_prefix);
9143 SendToICS("draw\n");
9146 cps->offeredDraw = 2; /* valid until this engine moves twice */
9147 if (gameMode == TwoMachinesPlay) {
9148 if (cps->other->offeredDraw) {
9149 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9150 /* [HGM] in two-machine mode we delay relaying draw offer */
9151 /* until after we also have move, to see if it is really claim */
9153 } else if (gameMode == MachinePlaysWhite ||
9154 gameMode == MachinePlaysBlack) {
9155 if (userOfferedDraw) {
9156 DisplayInformation(_("Machine accepts your draw offer"));
9157 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9159 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9166 * Look for thinking output
9168 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9169 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9171 int plylev, mvleft, mvtot, curscore, time;
9172 char mvname[MOVE_LEN];
9176 int prefixHint = FALSE;
9177 mvname[0] = NULLCHAR;
9180 case MachinePlaysBlack:
9181 case IcsPlayingBlack:
9182 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9184 case MachinePlaysWhite:
9185 case IcsPlayingWhite:
9186 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9191 case IcsObserving: /* [DM] icsEngineAnalyze */
9192 if (!appData.icsEngineAnalyze) ignore = TRUE;
9194 case TwoMachinesPlay:
9195 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9205 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9207 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9208 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9210 if (plyext != ' ' && plyext != '\t') {
9214 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9215 if( cps->scoreIsAbsolute &&
9216 ( gameMode == MachinePlaysBlack ||
9217 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9218 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9219 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9220 !WhiteOnMove(currentMove)
9223 curscore = -curscore;
9226 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9228 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9231 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9232 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9233 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9234 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9235 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9236 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9240 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9241 DisplayError(_("failed writing PV"), 0);
9244 tempStats.depth = plylev;
9245 tempStats.nodes = nodes;
9246 tempStats.time = time;
9247 tempStats.score = curscore;
9248 tempStats.got_only_move = 0;
9250 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9253 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9254 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9255 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9256 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9257 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9258 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9259 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9260 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9263 /* Buffer overflow protection */
9264 if (pv[0] != NULLCHAR) {
9265 if (strlen(pv) >= sizeof(tempStats.movelist)
9266 && appData.debugMode) {
9268 "PV is too long; using the first %u bytes.\n",
9269 (unsigned) sizeof(tempStats.movelist) - 1);
9272 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9274 sprintf(tempStats.movelist, " no PV\n");
9277 if (tempStats.seen_stat) {
9278 tempStats.ok_to_send = 1;
9281 if (strchr(tempStats.movelist, '(') != NULL) {
9282 tempStats.line_is_book = 1;
9283 tempStats.nr_moves = 0;
9284 tempStats.moves_left = 0;
9286 tempStats.line_is_book = 0;
9289 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9290 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9292 SendProgramStatsToFrontend( cps, &tempStats );
9295 [AS] Protect the thinkOutput buffer from overflow... this
9296 is only useful if buf1 hasn't overflowed first!
9298 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9300 (gameMode == TwoMachinesPlay ?
9301 ToUpper(cps->twoMachinesColor[0]) : ' '),
9302 ((double) curscore) / 100.0,
9303 prefixHint ? lastHint : "",
9304 prefixHint ? " " : "" );
9306 if( buf1[0] != NULLCHAR ) {
9307 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9309 if( strlen(pv) > max_len ) {
9310 if( appData.debugMode) {
9311 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9313 pv[max_len+1] = '\0';
9316 strcat( thinkOutput, pv);
9319 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9320 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9321 DisplayMove(currentMove - 1);
9325 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9326 /* crafty (9.25+) says "(only move) <move>"
9327 * if there is only 1 legal move
9329 sscanf(p, "(only move) %s", buf1);
9330 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9331 sprintf(programStats.movelist, "%s (only move)", buf1);
9332 programStats.depth = 1;
9333 programStats.nr_moves = 1;
9334 programStats.moves_left = 1;
9335 programStats.nodes = 1;
9336 programStats.time = 1;
9337 programStats.got_only_move = 1;
9339 /* Not really, but we also use this member to
9340 mean "line isn't going to change" (Crafty
9341 isn't searching, so stats won't change) */
9342 programStats.line_is_book = 1;
9344 SendProgramStatsToFrontend( cps, &programStats );
9346 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9347 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9348 DisplayMove(currentMove - 1);
9351 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9352 &time, &nodes, &plylev, &mvleft,
9353 &mvtot, mvname) >= 5) {
9354 /* The stat01: line is from Crafty (9.29+) in response
9355 to the "." command */
9356 programStats.seen_stat = 1;
9357 cps->maybeThinking = TRUE;
9359 if (programStats.got_only_move || !appData.periodicUpdates)
9362 programStats.depth = plylev;
9363 programStats.time = time;
9364 programStats.nodes = nodes;
9365 programStats.moves_left = mvleft;
9366 programStats.nr_moves = mvtot;
9367 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9368 programStats.ok_to_send = 1;
9369 programStats.movelist[0] = '\0';
9371 SendProgramStatsToFrontend( cps, &programStats );
9375 } else if (strncmp(message,"++",2) == 0) {
9376 /* Crafty 9.29+ outputs this */
9377 programStats.got_fail = 2;
9380 } else if (strncmp(message,"--",2) == 0) {
9381 /* Crafty 9.29+ outputs this */
9382 programStats.got_fail = 1;
9385 } else if (thinkOutput[0] != NULLCHAR &&
9386 strncmp(message, " ", 4) == 0) {
9387 unsigned message_len;
9390 while (*p && *p == ' ') p++;
9392 message_len = strlen( p );
9394 /* [AS] Avoid buffer overflow */
9395 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9396 strcat(thinkOutput, " ");
9397 strcat(thinkOutput, p);
9400 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9401 strcat(programStats.movelist, " ");
9402 strcat(programStats.movelist, p);
9405 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9406 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9407 DisplayMove(currentMove - 1);
9415 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9416 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9418 ChessProgramStats cpstats;
9420 if (plyext != ' ' && plyext != '\t') {
9424 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9425 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9426 curscore = -curscore;
9429 cpstats.depth = plylev;
9430 cpstats.nodes = nodes;
9431 cpstats.time = time;
9432 cpstats.score = curscore;
9433 cpstats.got_only_move = 0;
9434 cpstats.movelist[0] = '\0';
9436 if (buf1[0] != NULLCHAR) {
9437 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9440 cpstats.ok_to_send = 0;
9441 cpstats.line_is_book = 0;
9442 cpstats.nr_moves = 0;
9443 cpstats.moves_left = 0;
9445 SendProgramStatsToFrontend( cps, &cpstats );
9452 /* Parse a game score from the character string "game", and
9453 record it as the history of the current game. The game
9454 score is NOT assumed to start from the standard position.
9455 The display is not updated in any way.
9458 ParseGameHistory (char *game)
9461 int fromX, fromY, toX, toY, boardIndex;
9466 if (appData.debugMode)
9467 fprintf(debugFP, "Parsing game history: %s\n", game);
9469 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9470 gameInfo.site = StrSave(appData.icsHost);
9471 gameInfo.date = PGNDate();
9472 gameInfo.round = StrSave("-");
9474 /* Parse out names of players */
9475 while (*game == ' ') game++;
9477 while (*game != ' ') *p++ = *game++;
9479 gameInfo.white = StrSave(buf);
9480 while (*game == ' ') game++;
9482 while (*game != ' ' && *game != '\n') *p++ = *game++;
9484 gameInfo.black = StrSave(buf);
9487 boardIndex = blackPlaysFirst ? 1 : 0;
9490 yyboardindex = boardIndex;
9491 moveType = (ChessMove) Myylex();
9493 case IllegalMove: /* maybe suicide chess, etc. */
9494 if (appData.debugMode) {
9495 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9496 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9497 setbuf(debugFP, NULL);
9499 case WhitePromotion:
9500 case BlackPromotion:
9501 case WhiteNonPromotion:
9502 case BlackNonPromotion:
9505 case WhiteCapturesEnPassant:
9506 case BlackCapturesEnPassant:
9507 case WhiteKingSideCastle:
9508 case WhiteQueenSideCastle:
9509 case BlackKingSideCastle:
9510 case BlackQueenSideCastle:
9511 case WhiteKingSideCastleWild:
9512 case WhiteQueenSideCastleWild:
9513 case BlackKingSideCastleWild:
9514 case BlackQueenSideCastleWild:
9516 case WhiteHSideCastleFR:
9517 case WhiteASideCastleFR:
9518 case BlackHSideCastleFR:
9519 case BlackASideCastleFR:
9521 fromX = currentMoveString[0] - AAA;
9522 fromY = currentMoveString[1] - ONE;
9523 toX = currentMoveString[2] - AAA;
9524 toY = currentMoveString[3] - ONE;
9525 promoChar = currentMoveString[4];
9529 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9530 fromX = moveType == WhiteDrop ?
9531 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9532 (int) CharToPiece(ToLower(currentMoveString[0]));
9534 toX = currentMoveString[2] - AAA;
9535 toY = currentMoveString[3] - ONE;
9536 promoChar = NULLCHAR;
9540 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9541 if (appData.debugMode) {
9542 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9543 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9544 setbuf(debugFP, NULL);
9546 DisplayError(buf, 0);
9548 case ImpossibleMove:
9550 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9551 if (appData.debugMode) {
9552 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9553 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9554 setbuf(debugFP, NULL);
9556 DisplayError(buf, 0);
9559 if (boardIndex < backwardMostMove) {
9560 /* Oops, gap. How did that happen? */
9561 DisplayError(_("Gap in move list"), 0);
9564 backwardMostMove = blackPlaysFirst ? 1 : 0;
9565 if (boardIndex > forwardMostMove) {
9566 forwardMostMove = boardIndex;
9570 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9571 strcat(parseList[boardIndex-1], " ");
9572 strcat(parseList[boardIndex-1], yy_text);
9584 case GameUnfinished:
9585 if (gameMode == IcsExamining) {
9586 if (boardIndex < backwardMostMove) {
9587 /* Oops, gap. How did that happen? */
9590 backwardMostMove = blackPlaysFirst ? 1 : 0;
9593 gameInfo.result = moveType;
9594 p = strchr(yy_text, '{');
9595 if (p == NULL) p = strchr(yy_text, '(');
9598 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9600 q = strchr(p, *p == '{' ? '}' : ')');
9601 if (q != NULL) *q = NULLCHAR;
9604 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9605 gameInfo.resultDetails = StrSave(p);
9608 if (boardIndex >= forwardMostMove &&
9609 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9610 backwardMostMove = blackPlaysFirst ? 1 : 0;
9613 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9614 fromY, fromX, toY, toX, promoChar,
9615 parseList[boardIndex]);
9616 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9617 /* currentMoveString is set as a side-effect of yylex */
9618 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9619 strcat(moveList[boardIndex], "\n");
9621 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9622 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9628 if(gameInfo.variant != VariantShogi)
9629 strcat(parseList[boardIndex - 1], "+");
9633 strcat(parseList[boardIndex - 1], "#");
9640 /* Apply a move to the given board */
9642 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9644 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9645 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9647 /* [HGM] compute & store e.p. status and castling rights for new position */
9648 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9650 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9651 oldEP = (signed char)board[EP_STATUS];
9652 board[EP_STATUS] = EP_NONE;
9654 if (fromY == DROP_RANK) {
9656 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9657 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9660 piece = board[toY][toX] = (ChessSquare) fromX;
9665 if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9666 victim = board[killY][killX],
9667 board[killY][killX] = EmptySquare,
9668 board[EP_STATUS] = EP_CAPTURE;
9670 if( board[toY][toX] != EmptySquare ) {
9671 board[EP_STATUS] = EP_CAPTURE;
9672 if( (fromX != toX || fromY != toY) && // not igui!
9673 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9674 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
9675 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9679 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9680 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9681 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9683 if( board[fromY][fromX] == WhitePawn ) {
9684 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9685 board[EP_STATUS] = EP_PAWN_MOVE;
9687 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9688 gameInfo.variant != VariantBerolina || toX < fromX)
9689 board[EP_STATUS] = toX | berolina;
9690 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9691 gameInfo.variant != VariantBerolina || toX > fromX)
9692 board[EP_STATUS] = toX;
9695 if( board[fromY][fromX] == BlackPawn ) {
9696 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9697 board[EP_STATUS] = EP_PAWN_MOVE;
9698 if( toY-fromY== -2) {
9699 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9700 gameInfo.variant != VariantBerolina || toX < fromX)
9701 board[EP_STATUS] = toX | berolina;
9702 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9703 gameInfo.variant != VariantBerolina || toX > fromX)
9704 board[EP_STATUS] = toX;
9708 for(i=0; i<nrCastlingRights; i++) {
9709 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9710 board[CASTLING][i] == toX && castlingRank[i] == toY
9711 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9714 if(gameInfo.variant == VariantSChess) { // update virginity
9715 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9716 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9717 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9718 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9721 if (fromX == toX && fromY == toY) return;
9723 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9724 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9725 if(gameInfo.variant == VariantKnightmate)
9726 king += (int) WhiteUnicorn - (int) WhiteKing;
9728 /* Code added by Tord: */
9729 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9730 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9731 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9732 board[fromY][fromX] = EmptySquare;
9733 board[toY][toX] = EmptySquare;
9734 if((toX > fromX) != (piece == WhiteRook)) {
9735 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9737 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9739 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9740 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9741 board[fromY][fromX] = EmptySquare;
9742 board[toY][toX] = EmptySquare;
9743 if((toX > fromX) != (piece == BlackRook)) {
9744 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9746 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9748 /* End of code added by Tord */
9750 } else if (board[fromY][fromX] == king
9751 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9752 && toY == fromY && toX > fromX+1) {
9753 board[fromY][fromX] = EmptySquare;
9754 board[toY][toX] = king;
9755 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9756 board[fromY][BOARD_RGHT-1] = EmptySquare;
9757 } else if (board[fromY][fromX] == king
9758 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9759 && toY == fromY && toX < fromX-1) {
9760 board[fromY][fromX] = EmptySquare;
9761 board[toY][toX] = king;
9762 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9763 board[fromY][BOARD_LEFT] = EmptySquare;
9764 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9765 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9766 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9768 /* white pawn promotion */
9769 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9770 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9771 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9772 board[fromY][fromX] = EmptySquare;
9773 } else if ((fromY >= BOARD_HEIGHT>>1)
9774 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9776 && gameInfo.variant != VariantXiangqi
9777 && gameInfo.variant != VariantBerolina
9778 && (board[fromY][fromX] == WhitePawn)
9779 && (board[toY][toX] == EmptySquare)) {
9780 board[fromY][fromX] = EmptySquare;
9781 board[toY][toX] = WhitePawn;
9782 captured = board[toY - 1][toX];
9783 board[toY - 1][toX] = EmptySquare;
9784 } else if ((fromY == BOARD_HEIGHT-4)
9786 && gameInfo.variant == VariantBerolina
9787 && (board[fromY][fromX] == WhitePawn)
9788 && (board[toY][toX] == EmptySquare)) {
9789 board[fromY][fromX] = EmptySquare;
9790 board[toY][toX] = WhitePawn;
9791 if(oldEP & EP_BEROLIN_A) {
9792 captured = board[fromY][fromX-1];
9793 board[fromY][fromX-1] = EmptySquare;
9794 }else{ captured = board[fromY][fromX+1];
9795 board[fromY][fromX+1] = EmptySquare;
9797 } else if (board[fromY][fromX] == king
9798 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9799 && toY == fromY && toX > fromX+1) {
9800 board[fromY][fromX] = EmptySquare;
9801 board[toY][toX] = king;
9802 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9803 board[fromY][BOARD_RGHT-1] = EmptySquare;
9804 } else if (board[fromY][fromX] == king
9805 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9806 && toY == fromY && toX < fromX-1) {
9807 board[fromY][fromX] = EmptySquare;
9808 board[toY][toX] = king;
9809 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9810 board[fromY][BOARD_LEFT] = EmptySquare;
9811 } else if (fromY == 7 && fromX == 3
9812 && board[fromY][fromX] == BlackKing
9813 && toY == 7 && toX == 5) {
9814 board[fromY][fromX] = EmptySquare;
9815 board[toY][toX] = BlackKing;
9816 board[fromY][7] = EmptySquare;
9817 board[toY][4] = BlackRook;
9818 } else if (fromY == 7 && fromX == 3
9819 && board[fromY][fromX] == BlackKing
9820 && toY == 7 && toX == 1) {
9821 board[fromY][fromX] = EmptySquare;
9822 board[toY][toX] = BlackKing;
9823 board[fromY][0] = EmptySquare;
9824 board[toY][2] = BlackRook;
9825 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9826 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9827 && toY < promoRank && promoChar
9829 /* black pawn promotion */
9830 board[toY][toX] = CharToPiece(ToLower(promoChar));
9831 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9832 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9833 board[fromY][fromX] = EmptySquare;
9834 } else if ((fromY < BOARD_HEIGHT>>1)
9835 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9837 && gameInfo.variant != VariantXiangqi
9838 && gameInfo.variant != VariantBerolina
9839 && (board[fromY][fromX] == BlackPawn)
9840 && (board[toY][toX] == EmptySquare)) {
9841 board[fromY][fromX] = EmptySquare;
9842 board[toY][toX] = BlackPawn;
9843 captured = board[toY + 1][toX];
9844 board[toY + 1][toX] = EmptySquare;
9845 } else if ((fromY == 3)
9847 && gameInfo.variant == VariantBerolina
9848 && (board[fromY][fromX] == BlackPawn)
9849 && (board[toY][toX] == EmptySquare)) {
9850 board[fromY][fromX] = EmptySquare;
9851 board[toY][toX] = BlackPawn;
9852 if(oldEP & EP_BEROLIN_A) {
9853 captured = board[fromY][fromX-1];
9854 board[fromY][fromX-1] = EmptySquare;
9855 }else{ captured = board[fromY][fromX+1];
9856 board[fromY][fromX+1] = EmptySquare;
9859 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9860 board[fromY][fromX] = EmptySquare;
9861 board[toY][toX] = piece;
9865 if (gameInfo.holdingsWidth != 0) {
9867 /* !!A lot more code needs to be written to support holdings */
9868 /* [HGM] OK, so I have written it. Holdings are stored in the */
9869 /* penultimate board files, so they are automaticlly stored */
9870 /* in the game history. */
9871 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9872 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9873 /* Delete from holdings, by decreasing count */
9874 /* and erasing image if necessary */
9875 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9876 if(p < (int) BlackPawn) { /* white drop */
9877 p -= (int)WhitePawn;
9878 p = PieceToNumber((ChessSquare)p);
9879 if(p >= gameInfo.holdingsSize) p = 0;
9880 if(--board[p][BOARD_WIDTH-2] <= 0)
9881 board[p][BOARD_WIDTH-1] = EmptySquare;
9882 if((int)board[p][BOARD_WIDTH-2] < 0)
9883 board[p][BOARD_WIDTH-2] = 0;
9884 } else { /* black drop */
9885 p -= (int)BlackPawn;
9886 p = PieceToNumber((ChessSquare)p);
9887 if(p >= gameInfo.holdingsSize) p = 0;
9888 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9889 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9890 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9891 board[BOARD_HEIGHT-1-p][1] = 0;
9894 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9895 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9896 /* [HGM] holdings: Add to holdings, if holdings exist */
9897 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9898 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9899 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9902 if (p >= (int) BlackPawn) {
9903 p -= (int)BlackPawn;
9904 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9905 /* in Shogi restore piece to its original first */
9906 captured = (ChessSquare) (DEMOTED captured);
9909 p = PieceToNumber((ChessSquare)p);
9910 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9911 board[p][BOARD_WIDTH-2]++;
9912 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9914 p -= (int)WhitePawn;
9915 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9916 captured = (ChessSquare) (DEMOTED captured);
9919 p = PieceToNumber((ChessSquare)p);
9920 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9921 board[BOARD_HEIGHT-1-p][1]++;
9922 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9925 } else if (gameInfo.variant == VariantAtomic) {
9926 if (captured != EmptySquare) {
9928 for (y = toY-1; y <= toY+1; y++) {
9929 for (x = toX-1; x <= toX+1; x++) {
9930 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9931 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9932 board[y][x] = EmptySquare;
9936 board[toY][toX] = EmptySquare;
9939 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9940 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9942 if(promoChar == '+') {
9943 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9944 board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9945 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9946 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9947 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9948 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9949 board[toY][toX] = newPiece;
9951 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9952 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9953 // [HGM] superchess: take promotion piece out of holdings
9954 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9955 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9956 if(!--board[k][BOARD_WIDTH-2])
9957 board[k][BOARD_WIDTH-1] = EmptySquare;
9959 if(!--board[BOARD_HEIGHT-1-k][1])
9960 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9965 /* Updates forwardMostMove */
9967 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9969 int x = toX, y = toY;
9970 char *s = parseList[forwardMostMove];
9971 ChessSquare p = boards[forwardMostMove][toY][toX];
9972 // forwardMostMove++; // [HGM] bare: moved downstream
9974 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
9975 (void) CoordsToAlgebraic(boards[forwardMostMove],
9976 PosFlags(forwardMostMove),
9977 fromY, fromX, y, x, promoChar,
9979 if(killX >= 0 && killY >= 0)
9980 sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
9982 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9983 int timeLeft; static int lastLoadFlag=0; int king, piece;
9984 piece = boards[forwardMostMove][fromY][fromX];
9985 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9986 if(gameInfo.variant == VariantKnightmate)
9987 king += (int) WhiteUnicorn - (int) WhiteKing;
9988 if(forwardMostMove == 0) {
9989 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9990 fprintf(serverMoves, "%s;", UserName());
9991 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9992 fprintf(serverMoves, "%s;", second.tidy);
9993 fprintf(serverMoves, "%s;", first.tidy);
9994 if(gameMode == MachinePlaysWhite)
9995 fprintf(serverMoves, "%s;", UserName());
9996 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9997 fprintf(serverMoves, "%s;", second.tidy);
9998 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9999 lastLoadFlag = loadFlag;
10001 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10002 // print castling suffix
10003 if( toY == fromY && piece == king ) {
10005 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10007 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10010 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10011 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10012 boards[forwardMostMove][toY][toX] == EmptySquare
10013 && fromX != toX && fromY != toY)
10014 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10015 // promotion suffix
10016 if(promoChar != NULLCHAR) {
10017 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10018 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10019 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10020 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10023 char buf[MOVE_LEN*2], *p; int len;
10024 fprintf(serverMoves, "/%d/%d",
10025 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10026 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10027 else timeLeft = blackTimeRemaining/1000;
10028 fprintf(serverMoves, "/%d", timeLeft);
10029 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10030 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10031 if(p = strchr(buf, '=')) *p = NULLCHAR;
10032 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10033 fprintf(serverMoves, "/%s", buf);
10035 fflush(serverMoves);
10038 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10039 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10042 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10043 if (commentList[forwardMostMove+1] != NULL) {
10044 free(commentList[forwardMostMove+1]);
10045 commentList[forwardMostMove+1] = NULL;
10047 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10048 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10049 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10050 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10051 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10052 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10053 adjustedClock = FALSE;
10054 gameInfo.result = GameUnfinished;
10055 if (gameInfo.resultDetails != NULL) {
10056 free(gameInfo.resultDetails);
10057 gameInfo.resultDetails = NULL;
10059 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10060 moveList[forwardMostMove - 1]);
10061 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10067 if(gameInfo.variant != VariantShogi)
10068 strcat(parseList[forwardMostMove - 1], "+");
10072 strcat(parseList[forwardMostMove - 1], "#");
10077 /* Updates currentMove if not pausing */
10079 ShowMove (int fromX, int fromY, int toX, int toY)
10081 int instant = (gameMode == PlayFromGameFile) ?
10082 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10083 if(appData.noGUI) return;
10084 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10086 if (forwardMostMove == currentMove + 1) {
10087 AnimateMove(boards[forwardMostMove - 1],
10088 fromX, fromY, toX, toY);
10091 currentMove = forwardMostMove;
10094 killX = killY = -1; // [HGM] lion: used up
10096 if (instant) return;
10098 DisplayMove(currentMove - 1);
10099 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10100 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10101 SetHighlights(fromX, fromY, toX, toY);
10104 DrawPosition(FALSE, boards[currentMove]);
10105 DisplayBothClocks();
10106 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10110 SendEgtPath (ChessProgramState *cps)
10111 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10112 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10114 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10117 char c, *q = name+1, *r, *s;
10119 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10120 while(*p && *p != ',') *q++ = *p++;
10121 *q++ = ':'; *q = 0;
10122 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10123 strcmp(name, ",nalimov:") == 0 ) {
10124 // take nalimov path from the menu-changeable option first, if it is defined
10125 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10126 SendToProgram(buf,cps); // send egtbpath command for nalimov
10128 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10129 (s = StrStr(appData.egtFormats, name)) != NULL) {
10130 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10131 s = r = StrStr(s, ":") + 1; // beginning of path info
10132 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10133 c = *r; *r = 0; // temporarily null-terminate path info
10134 *--q = 0; // strip of trailig ':' from name
10135 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10137 SendToProgram(buf,cps); // send egtbpath command for this format
10139 if(*p == ',') p++; // read away comma to position for next format name
10144 NonStandardBoardSize ()
10146 /* [HGM] Awkward testing. Should really be a table */
10147 int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10148 if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10149 if( gameInfo.variant == VariantXiangqi )
10150 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10151 if( gameInfo.variant == VariantShogi )
10152 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10153 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10154 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10155 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10156 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10157 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10158 if( gameInfo.variant == VariantCourier )
10159 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10160 if( gameInfo.variant == VariantSuper )
10161 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10162 if( gameInfo.variant == VariantGreat )
10163 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10164 if( gameInfo.variant == VariantSChess )
10165 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10166 if( gameInfo.variant == VariantGrand )
10167 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10168 if( gameInfo.variant == VariantChu )
10169 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
10174 InitChessProgram (ChessProgramState *cps, int setup)
10175 /* setup needed to setup FRC opening position */
10177 char buf[MSG_SIZ], b[MSG_SIZ];
10178 if (appData.noChessProgram) return;
10179 hintRequested = FALSE;
10180 bookRequested = FALSE;
10182 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10183 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10184 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10185 if(cps->memSize) { /* [HGM] memory */
10186 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10187 SendToProgram(buf, cps);
10189 SendEgtPath(cps); /* [HGM] EGT */
10190 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10191 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10192 SendToProgram(buf, cps);
10195 SendToProgram(cps->initString, cps);
10196 if (gameInfo.variant != VariantNormal &&
10197 gameInfo.variant != VariantLoadable
10198 /* [HGM] also send variant if board size non-standard */
10199 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10201 char *v = VariantName(gameInfo.variant);
10202 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10203 /* [HGM] in protocol 1 we have to assume all variants valid */
10204 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10205 DisplayFatalError(buf, 0, 1);
10209 if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10210 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10211 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10212 /* [HGM] varsize: try first if this defiant size variant is specifically known */
10213 if(StrStr(cps->variants, b) == NULL) {
10214 // specific sized variant not known, check if general sizing allowed
10215 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10216 if(StrStr(cps->variants, "boardsize") == NULL) {
10217 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10218 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10219 DisplayFatalError(buf, 0, 1);
10222 /* [HGM] here we really should compare with the maximum supported board size */
10225 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10226 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10227 SendToProgram(buf, cps);
10229 currentlyInitializedVariant = gameInfo.variant;
10231 /* [HGM] send opening position in FRC to first engine */
10233 SendToProgram("force\n", cps);
10235 /* engine is now in force mode! Set flag to wake it up after first move. */
10236 setboardSpoiledMachineBlack = 1;
10239 if (cps->sendICS) {
10240 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10241 SendToProgram(buf, cps);
10243 cps->maybeThinking = FALSE;
10244 cps->offeredDraw = 0;
10245 if (!appData.icsActive) {
10246 SendTimeControl(cps, movesPerSession, timeControl,
10247 timeIncrement, appData.searchDepth,
10250 if (appData.showThinking
10251 // [HGM] thinking: four options require thinking output to be sent
10252 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10254 SendToProgram("post\n", cps);
10256 SendToProgram("hard\n", cps);
10257 if (!appData.ponderNextMove) {
10258 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10259 it without being sure what state we are in first. "hard"
10260 is not a toggle, so that one is OK.
10262 SendToProgram("easy\n", cps);
10264 if (cps->usePing) {
10265 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10266 SendToProgram(buf, cps);
10268 cps->initDone = TRUE;
10269 ClearEngineOutputPane(cps == &second);
10274 ResendOptions (ChessProgramState *cps)
10275 { // send the stored value of the options
10278 Option *opt = cps->option;
10279 for(i=0; i<cps->nrOptions; i++, opt++) {
10280 switch(opt->type) {
10284 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10287 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10290 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10296 SendToProgram(buf, cps);
10301 StartChessProgram (ChessProgramState *cps)
10306 if (appData.noChessProgram) return;
10307 cps->initDone = FALSE;
10309 if (strcmp(cps->host, "localhost") == 0) {
10310 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10311 } else if (*appData.remoteShell == NULLCHAR) {
10312 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10314 if (*appData.remoteUser == NULLCHAR) {
10315 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10318 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10319 cps->host, appData.remoteUser, cps->program);
10321 err = StartChildProcess(buf, "", &cps->pr);
10325 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10326 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10327 if(cps != &first) return;
10328 appData.noChessProgram = TRUE;
10331 // DisplayFatalError(buf, err, 1);
10332 // cps->pr = NoProc;
10333 // cps->isr = NULL;
10337 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10338 if (cps->protocolVersion > 1) {
10339 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10340 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10341 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10342 cps->comboCnt = 0; // and values of combo boxes
10344 SendToProgram(buf, cps);
10345 if(cps->reload) ResendOptions(cps);
10347 SendToProgram("xboard\n", cps);
10352 TwoMachinesEventIfReady P((void))
10354 static int curMess = 0;
10355 if (first.lastPing != first.lastPong) {
10356 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10357 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10360 if (second.lastPing != second.lastPong) {
10361 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10362 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10365 DisplayMessage("", ""); curMess = 0;
10366 TwoMachinesEvent();
10370 MakeName (char *template)
10374 static char buf[MSG_SIZ];
10378 clock = time((time_t *)NULL);
10379 tm = localtime(&clock);
10381 while(*p++ = *template++) if(p[-1] == '%') {
10382 switch(*template++) {
10383 case 0: *p = 0; return buf;
10384 case 'Y': i = tm->tm_year+1900; break;
10385 case 'y': i = tm->tm_year-100; break;
10386 case 'M': i = tm->tm_mon+1; break;
10387 case 'd': i = tm->tm_mday; break;
10388 case 'h': i = tm->tm_hour; break;
10389 case 'm': i = tm->tm_min; break;
10390 case 's': i = tm->tm_sec; break;
10393 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10399 CountPlayers (char *p)
10402 while(p = strchr(p, '\n')) p++, n++; // count participants
10407 WriteTourneyFile (char *results, FILE *f)
10408 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10409 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10410 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10411 // create a file with tournament description
10412 fprintf(f, "-participants {%s}\n", appData.participants);
10413 fprintf(f, "-seedBase %d\n", appData.seedBase);
10414 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10415 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10416 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10417 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10418 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10419 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10420 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10421 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10422 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10423 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10424 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10425 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10426 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10427 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10428 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10429 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10430 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10431 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10432 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10433 fprintf(f, "-smpCores %d\n", appData.smpCores);
10435 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10437 fprintf(f, "-mps %d\n", appData.movesPerSession);
10438 fprintf(f, "-tc %s\n", appData.timeControl);
10439 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10441 fprintf(f, "-results \"%s\"\n", results);
10446 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10449 Substitute (char *participants, int expunge)
10451 int i, changed, changes=0, nPlayers=0;
10452 char *p, *q, *r, buf[MSG_SIZ];
10453 if(participants == NULL) return;
10454 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10455 r = p = participants; q = appData.participants;
10456 while(*p && *p == *q) {
10457 if(*p == '\n') r = p+1, nPlayers++;
10460 if(*p) { // difference
10461 while(*p && *p++ != '\n');
10462 while(*q && *q++ != '\n');
10463 changed = nPlayers;
10464 changes = 1 + (strcmp(p, q) != 0);
10466 if(changes == 1) { // a single engine mnemonic was changed
10467 q = r; while(*q) nPlayers += (*q++ == '\n');
10468 p = buf; while(*r && (*p = *r++) != '\n') p++;
10470 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10471 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10472 if(mnemonic[i]) { // The substitute is valid
10474 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10475 flock(fileno(f), LOCK_EX);
10476 ParseArgsFromFile(f);
10477 fseek(f, 0, SEEK_SET);
10478 FREE(appData.participants); appData.participants = participants;
10479 if(expunge) { // erase results of replaced engine
10480 int len = strlen(appData.results), w, b, dummy;
10481 for(i=0; i<len; i++) {
10482 Pairing(i, nPlayers, &w, &b, &dummy);
10483 if((w == changed || b == changed) && appData.results[i] == '*') {
10484 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10489 for(i=0; i<len; i++) {
10490 Pairing(i, nPlayers, &w, &b, &dummy);
10491 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10494 WriteTourneyFile(appData.results, f);
10495 fclose(f); // release lock
10498 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10500 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10501 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10502 free(participants);
10507 CheckPlayers (char *participants)
10510 char buf[MSG_SIZ], *p;
10511 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10512 while(p = strchr(participants, '\n')) {
10514 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10516 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10518 DisplayError(buf, 0);
10522 participants = p + 1;
10528 CreateTourney (char *name)
10531 if(matchMode && strcmp(name, appData.tourneyFile)) {
10532 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10534 if(name[0] == NULLCHAR) {
10535 if(appData.participants[0])
10536 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10539 f = fopen(name, "r");
10540 if(f) { // file exists
10541 ASSIGN(appData.tourneyFile, name);
10542 ParseArgsFromFile(f); // parse it
10544 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10545 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10546 DisplayError(_("Not enough participants"), 0);
10549 if(CheckPlayers(appData.participants)) return 0;
10550 ASSIGN(appData.tourneyFile, name);
10551 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10552 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10555 appData.noChessProgram = FALSE;
10556 appData.clockMode = TRUE;
10562 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10564 char buf[MSG_SIZ], *p, *q;
10565 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10566 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10567 skip = !all && group[0]; // if group requested, we start in skip mode
10568 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10569 p = names; q = buf; header = 0;
10570 while(*p && *p != '\n') *q++ = *p++;
10572 if(*p == '\n') p++;
10573 if(buf[0] == '#') {
10574 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10575 depth++; // we must be entering a new group
10576 if(all) continue; // suppress printing group headers when complete list requested
10578 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10580 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10581 if(engineList[i]) free(engineList[i]);
10582 engineList[i] = strdup(buf);
10583 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10584 if(engineMnemonic[i]) free(engineMnemonic[i]);
10585 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10587 sscanf(q + 8, "%s", buf + strlen(buf));
10590 engineMnemonic[i] = strdup(buf);
10593 engineList[i] = engineMnemonic[i] = NULL;
10597 // following implemented as macro to avoid type limitations
10598 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10601 SwapEngines (int n)
10602 { // swap settings for first engine and other engine (so far only some selected options)
10607 SWAP(chessProgram, p)
10609 SWAP(hasOwnBookUCI, h)
10610 SWAP(protocolVersion, h)
10612 SWAP(scoreIsAbsolute, h)
10617 SWAP(engOptions, p)
10618 SWAP(engInitString, p)
10619 SWAP(computerString, p)
10621 SWAP(fenOverride, p)
10623 SWAP(accumulateTC, h)
10628 GetEngineLine (char *s, int n)
10632 extern char *icsNames;
10633 if(!s || !*s) return 0;
10634 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10635 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10636 if(!mnemonic[i]) return 0;
10637 if(n == 11) return 1; // just testing if there was a match
10638 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10639 if(n == 1) SwapEngines(n);
10640 ParseArgsFromString(buf);
10641 if(n == 1) SwapEngines(n);
10642 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10643 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10644 ParseArgsFromString(buf);
10650 SetPlayer (int player, char *p)
10651 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10653 char buf[MSG_SIZ], *engineName;
10654 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10655 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10656 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10658 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10659 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10660 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10661 ParseArgsFromString(buf);
10662 } else { // no engine with this nickname is installed!
10663 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10664 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10665 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10667 DisplayError(buf, 0);
10674 char *recentEngines;
10677 RecentEngineEvent (int nr)
10680 // SwapEngines(1); // bump first to second
10681 // ReplaceEngine(&second, 1); // and load it there
10682 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10683 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10684 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10685 ReplaceEngine(&first, 0);
10686 FloatToFront(&appData.recentEngineList, command[n]);
10691 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10692 { // determine players from game number
10693 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10695 if(appData.tourneyType == 0) {
10696 roundsPerCycle = (nPlayers - 1) | 1;
10697 pairingsPerRound = nPlayers / 2;
10698 } else if(appData.tourneyType > 0) {
10699 roundsPerCycle = nPlayers - appData.tourneyType;
10700 pairingsPerRound = appData.tourneyType;
10702 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10703 gamesPerCycle = gamesPerRound * roundsPerCycle;
10704 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10705 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10706 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10707 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10708 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10709 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10711 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10712 if(appData.roundSync) *syncInterval = gamesPerRound;
10714 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10716 if(appData.tourneyType == 0) {
10717 if(curPairing == (nPlayers-1)/2 ) {
10718 *whitePlayer = curRound;
10719 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10721 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10722 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10723 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10724 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10726 } else if(appData.tourneyType > 1) {
10727 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10728 *whitePlayer = curRound + appData.tourneyType;
10729 } else if(appData.tourneyType > 0) {
10730 *whitePlayer = curPairing;
10731 *blackPlayer = curRound + appData.tourneyType;
10734 // take care of white/black alternation per round.
10735 // For cycles and games this is already taken care of by default, derived from matchGame!
10736 return curRound & 1;
10740 NextTourneyGame (int nr, int *swapColors)
10741 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10743 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10745 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10746 tf = fopen(appData.tourneyFile, "r");
10747 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10748 ParseArgsFromFile(tf); fclose(tf);
10749 InitTimeControls(); // TC might be altered from tourney file
10751 nPlayers = CountPlayers(appData.participants); // count participants
10752 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10753 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10756 p = q = appData.results;
10757 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10758 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10759 DisplayMessage(_("Waiting for other game(s)"),"");
10760 waitingForGame = TRUE;
10761 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10764 waitingForGame = FALSE;
10767 if(appData.tourneyType < 0) {
10768 if(nr>=0 && !pairingReceived) {
10770 if(pairing.pr == NoProc) {
10771 if(!appData.pairingEngine[0]) {
10772 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10775 StartChessProgram(&pairing); // starts the pairing engine
10777 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10778 SendToProgram(buf, &pairing);
10779 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10780 SendToProgram(buf, &pairing);
10781 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10783 pairingReceived = 0; // ... so we continue here
10785 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10786 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10787 matchGame = 1; roundNr = nr / syncInterval + 1;
10790 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10792 // redefine engines, engine dir, etc.
10793 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10794 if(first.pr == NoProc) {
10795 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10796 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10798 if(second.pr == NoProc) {
10800 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10801 SwapEngines(1); // and make that valid for second engine by swapping
10802 InitEngine(&second, 1);
10804 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10805 UpdateLogos(FALSE); // leave display to ModeHiglight()
10811 { // performs game initialization that does not invoke engines, and then tries to start the game
10812 int res, firstWhite, swapColors = 0;
10813 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10814 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
10816 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10817 if(strcmp(buf, currentDebugFile)) { // name has changed
10818 FILE *f = fopen(buf, "w");
10819 if(f) { // if opening the new file failed, just keep using the old one
10820 ASSIGN(currentDebugFile, buf);
10824 if(appData.serverFileName) {
10825 if(serverFP) fclose(serverFP);
10826 serverFP = fopen(appData.serverFileName, "w");
10827 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10828 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10832 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10833 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10834 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10835 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10836 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10837 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10838 Reset(FALSE, first.pr != NoProc);
10839 res = LoadGameOrPosition(matchGame); // setup game
10840 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10841 if(!res) return; // abort when bad game/pos file
10842 TwoMachinesEvent();
10846 UserAdjudicationEvent (int result)
10848 ChessMove gameResult = GameIsDrawn;
10851 gameResult = WhiteWins;
10853 else if( result < 0 ) {
10854 gameResult = BlackWins;
10857 if( gameMode == TwoMachinesPlay ) {
10858 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10863 // [HGM] save: calculate checksum of game to make games easily identifiable
10865 StringCheckSum (char *s)
10868 if(s==NULL) return 0;
10869 while(*s) i = i*259 + *s++;
10877 for(i=backwardMostMove; i<forwardMostMove; i++) {
10878 sum += pvInfoList[i].depth;
10879 sum += StringCheckSum(parseList[i]);
10880 sum += StringCheckSum(commentList[i]);
10883 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10884 return sum + StringCheckSum(commentList[i]);
10885 } // end of save patch
10888 GameEnds (ChessMove result, char *resultDetails, int whosays)
10890 GameMode nextGameMode;
10892 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10894 if(endingGame) return; /* [HGM] crash: forbid recursion */
10896 if(twoBoards) { // [HGM] dual: switch back to one board
10897 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10898 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10900 if (appData.debugMode) {
10901 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10902 result, resultDetails ? resultDetails : "(null)", whosays);
10905 fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10907 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10909 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10910 /* If we are playing on ICS, the server decides when the
10911 game is over, but the engine can offer to draw, claim
10915 if (appData.zippyPlay && first.initDone) {
10916 if (result == GameIsDrawn) {
10917 /* In case draw still needs to be claimed */
10918 SendToICS(ics_prefix);
10919 SendToICS("draw\n");
10920 } else if (StrCaseStr(resultDetails, "resign")) {
10921 SendToICS(ics_prefix);
10922 SendToICS("resign\n");
10926 endingGame = 0; /* [HGM] crash */
10930 /* If we're loading the game from a file, stop */
10931 if (whosays == GE_FILE) {
10932 (void) StopLoadGameTimer();
10936 /* Cancel draw offers */
10937 first.offeredDraw = second.offeredDraw = 0;
10939 /* If this is an ICS game, only ICS can really say it's done;
10940 if not, anyone can. */
10941 isIcsGame = (gameMode == IcsPlayingWhite ||
10942 gameMode == IcsPlayingBlack ||
10943 gameMode == IcsObserving ||
10944 gameMode == IcsExamining);
10946 if (!isIcsGame || whosays == GE_ICS) {
10947 /* OK -- not an ICS game, or ICS said it was done */
10949 if (!isIcsGame && !appData.noChessProgram)
10950 SetUserThinkingEnables();
10952 /* [HGM] if a machine claims the game end we verify this claim */
10953 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10954 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10956 ChessMove trueResult = (ChessMove) -1;
10958 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10959 first.twoMachinesColor[0] :
10960 second.twoMachinesColor[0] ;
10962 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10963 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10964 /* [HGM] verify: engine mate claims accepted if they were flagged */
10965 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10967 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10968 /* [HGM] verify: engine mate claims accepted if they were flagged */
10969 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10971 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10972 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10975 // now verify win claims, but not in drop games, as we don't understand those yet
10976 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10977 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10978 (result == WhiteWins && claimer == 'w' ||
10979 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10980 if (appData.debugMode) {
10981 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10982 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10984 if(result != trueResult) {
10985 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10986 result = claimer == 'w' ? BlackWins : WhiteWins;
10987 resultDetails = buf;
10990 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10991 && (forwardMostMove <= backwardMostMove ||
10992 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10993 (claimer=='b')==(forwardMostMove&1))
10995 /* [HGM] verify: draws that were not flagged are false claims */
10996 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10997 result = claimer == 'w' ? BlackWins : WhiteWins;
10998 resultDetails = buf;
11000 /* (Claiming a loss is accepted no questions asked!) */
11001 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11002 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11003 result = GameUnfinished;
11004 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11006 /* [HGM] bare: don't allow bare King to win */
11007 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11008 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11009 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11010 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11011 && result != GameIsDrawn)
11012 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11013 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11014 int p = (signed char)boards[forwardMostMove][i][j] - color;
11015 if(p >= 0 && p <= (int)WhiteKing) k++;
11017 if (appData.debugMode) {
11018 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11019 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11022 result = GameIsDrawn;
11023 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11024 resultDetails = buf;
11030 if(serverMoves != NULL && !loadFlag) { char c = '=';
11031 if(result==WhiteWins) c = '+';
11032 if(result==BlackWins) c = '-';
11033 if(resultDetails != NULL)
11034 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11036 if (resultDetails != NULL) {
11037 gameInfo.result = result;
11038 gameInfo.resultDetails = StrSave(resultDetails);
11040 /* display last move only if game was not loaded from file */
11041 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11042 DisplayMove(currentMove - 1);
11044 if (forwardMostMove != 0) {
11045 if (gameMode != PlayFromGameFile && gameMode != EditGame
11046 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11048 if (*appData.saveGameFile != NULLCHAR) {
11049 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11050 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11052 SaveGameToFile(appData.saveGameFile, TRUE);
11053 } else if (appData.autoSaveGames) {
11054 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11056 if (*appData.savePositionFile != NULLCHAR) {
11057 SavePositionToFile(appData.savePositionFile);
11059 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11063 /* Tell program how game ended in case it is learning */
11064 /* [HGM] Moved this to after saving the PGN, just in case */
11065 /* engine died and we got here through time loss. In that */
11066 /* case we will get a fatal error writing the pipe, which */
11067 /* would otherwise lose us the PGN. */
11068 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11069 /* output during GameEnds should never be fatal anymore */
11070 if (gameMode == MachinePlaysWhite ||
11071 gameMode == MachinePlaysBlack ||
11072 gameMode == TwoMachinesPlay ||
11073 gameMode == IcsPlayingWhite ||
11074 gameMode == IcsPlayingBlack ||
11075 gameMode == BeginningOfGame) {
11077 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11079 if (first.pr != NoProc) {
11080 SendToProgram(buf, &first);
11082 if (second.pr != NoProc &&
11083 gameMode == TwoMachinesPlay) {
11084 SendToProgram(buf, &second);
11089 if (appData.icsActive) {
11090 if (appData.quietPlay &&
11091 (gameMode == IcsPlayingWhite ||
11092 gameMode == IcsPlayingBlack)) {
11093 SendToICS(ics_prefix);
11094 SendToICS("set shout 1\n");
11096 nextGameMode = IcsIdle;
11097 ics_user_moved = FALSE;
11098 /* clean up premove. It's ugly when the game has ended and the
11099 * premove highlights are still on the board.
11102 gotPremove = FALSE;
11103 ClearPremoveHighlights();
11104 DrawPosition(FALSE, boards[currentMove]);
11106 if (whosays == GE_ICS) {
11109 if (gameMode == IcsPlayingWhite)
11111 else if(gameMode == IcsPlayingBlack)
11112 PlayIcsLossSound();
11115 if (gameMode == IcsPlayingBlack)
11117 else if(gameMode == IcsPlayingWhite)
11118 PlayIcsLossSound();
11121 PlayIcsDrawSound();
11124 PlayIcsUnfinishedSound();
11127 if(appData.quitNext) { ExitEvent(0); return; }
11128 } else if (gameMode == EditGame ||
11129 gameMode == PlayFromGameFile ||
11130 gameMode == AnalyzeMode ||
11131 gameMode == AnalyzeFile) {
11132 nextGameMode = gameMode;
11134 nextGameMode = EndOfGame;
11139 nextGameMode = gameMode;
11142 if (appData.noChessProgram) {
11143 gameMode = nextGameMode;
11145 endingGame = 0; /* [HGM] crash */
11150 /* Put first chess program into idle state */
11151 if (first.pr != NoProc &&
11152 (gameMode == MachinePlaysWhite ||
11153 gameMode == MachinePlaysBlack ||
11154 gameMode == TwoMachinesPlay ||
11155 gameMode == IcsPlayingWhite ||
11156 gameMode == IcsPlayingBlack ||
11157 gameMode == BeginningOfGame)) {
11158 SendToProgram("force\n", &first);
11159 if (first.usePing) {
11161 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11162 SendToProgram(buf, &first);
11165 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11166 /* Kill off first chess program */
11167 if (first.isr != NULL)
11168 RemoveInputSource(first.isr);
11171 if (first.pr != NoProc) {
11173 DoSleep( appData.delayBeforeQuit );
11174 SendToProgram("quit\n", &first);
11175 DoSleep( appData.delayAfterQuit );
11176 DestroyChildProcess(first.pr, first.useSigterm);
11177 first.reload = TRUE;
11181 if (second.reuse) {
11182 /* Put second chess program into idle state */
11183 if (second.pr != NoProc &&
11184 gameMode == TwoMachinesPlay) {
11185 SendToProgram("force\n", &second);
11186 if (second.usePing) {
11188 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11189 SendToProgram(buf, &second);
11192 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11193 /* Kill off second chess program */
11194 if (second.isr != NULL)
11195 RemoveInputSource(second.isr);
11198 if (second.pr != NoProc) {
11199 DoSleep( appData.delayBeforeQuit );
11200 SendToProgram("quit\n", &second);
11201 DoSleep( appData.delayAfterQuit );
11202 DestroyChildProcess(second.pr, second.useSigterm);
11203 second.reload = TRUE;
11205 second.pr = NoProc;
11208 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11209 char resChar = '=';
11213 if (first.twoMachinesColor[0] == 'w') {
11216 second.matchWins++;
11221 if (first.twoMachinesColor[0] == 'b') {
11224 second.matchWins++;
11227 case GameUnfinished:
11233 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11234 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11235 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11236 ReserveGame(nextGame, resChar); // sets nextGame
11237 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11238 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11239 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11241 if (nextGame <= appData.matchGames && !abortMatch) {
11242 gameMode = nextGameMode;
11243 matchGame = nextGame; // this will be overruled in tourney mode!
11244 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11245 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11246 endingGame = 0; /* [HGM] crash */
11249 gameMode = nextGameMode;
11250 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11251 first.tidy, second.tidy,
11252 first.matchWins, second.matchWins,
11253 appData.matchGames - (first.matchWins + second.matchWins));
11254 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11255 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11256 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11257 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11258 first.twoMachinesColor = "black\n";
11259 second.twoMachinesColor = "white\n";
11261 first.twoMachinesColor = "white\n";
11262 second.twoMachinesColor = "black\n";
11266 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11267 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11269 gameMode = nextGameMode;
11271 endingGame = 0; /* [HGM] crash */
11272 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11273 if(matchMode == TRUE) { // match through command line: exit with or without popup
11275 ToNrEvent(forwardMostMove);
11276 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11278 } else DisplayFatalError(buf, 0, 0);
11279 } else { // match through menu; just stop, with or without popup
11280 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11283 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11284 } else DisplayNote(buf);
11286 if(ranking) free(ranking);
11290 /* Assumes program was just initialized (initString sent).
11291 Leaves program in force mode. */
11293 FeedMovesToProgram (ChessProgramState *cps, int upto)
11297 if (appData.debugMode)
11298 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11299 startedFromSetupPosition ? "position and " : "",
11300 backwardMostMove, upto, cps->which);
11301 if(currentlyInitializedVariant != gameInfo.variant) {
11303 // [HGM] variantswitch: make engine aware of new variant
11304 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11305 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11306 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11307 SendToProgram(buf, cps);
11308 currentlyInitializedVariant = gameInfo.variant;
11310 SendToProgram("force\n", cps);
11311 if (startedFromSetupPosition) {
11312 SendBoard(cps, backwardMostMove);
11313 if (appData.debugMode) {
11314 fprintf(debugFP, "feedMoves\n");
11317 for (i = backwardMostMove; i < upto; i++) {
11318 SendMoveToProgram(i, cps);
11324 ResurrectChessProgram ()
11326 /* The chess program may have exited.
11327 If so, restart it and feed it all the moves made so far. */
11328 static int doInit = 0;
11330 if (appData.noChessProgram) return 1;
11332 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11333 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11334 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11335 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11337 if (first.pr != NoProc) return 1;
11338 StartChessProgram(&first);
11340 InitChessProgram(&first, FALSE);
11341 FeedMovesToProgram(&first, currentMove);
11343 if (!first.sendTime) {
11344 /* can't tell gnuchess what its clock should read,
11345 so we bow to its notion. */
11347 timeRemaining[0][currentMove] = whiteTimeRemaining;
11348 timeRemaining[1][currentMove] = blackTimeRemaining;
11351 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11352 appData.icsEngineAnalyze) && first.analysisSupport) {
11353 SendToProgram("analyze\n", &first);
11354 first.analyzing = TRUE;
11360 * Button procedures
11363 Reset (int redraw, int init)
11367 if (appData.debugMode) {
11368 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11369 redraw, init, gameMode);
11371 CleanupTail(); // [HGM] vari: delete any stored variations
11372 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11373 pausing = pauseExamInvalid = FALSE;
11374 startedFromSetupPosition = blackPlaysFirst = FALSE;
11376 whiteFlag = blackFlag = FALSE;
11377 userOfferedDraw = FALSE;
11378 hintRequested = bookRequested = FALSE;
11379 first.maybeThinking = FALSE;
11380 second.maybeThinking = FALSE;
11381 first.bookSuspend = FALSE; // [HGM] book
11382 second.bookSuspend = FALSE;
11383 thinkOutput[0] = NULLCHAR;
11384 lastHint[0] = NULLCHAR;
11385 ClearGameInfo(&gameInfo);
11386 gameInfo.variant = StringToVariant(appData.variant);
11387 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11388 ics_user_moved = ics_clock_paused = FALSE;
11389 ics_getting_history = H_FALSE;
11391 white_holding[0] = black_holding[0] = NULLCHAR;
11392 ClearProgramStats();
11393 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11397 flipView = appData.flipView;
11398 ClearPremoveHighlights();
11399 gotPremove = FALSE;
11400 alarmSounded = FALSE;
11401 killX = killY = -1; // [HGM] lion
11403 GameEnds(EndOfFile, NULL, GE_PLAYER);
11404 if(appData.serverMovesName != NULL) {
11405 /* [HGM] prepare to make moves file for broadcasting */
11406 clock_t t = clock();
11407 if(serverMoves != NULL) fclose(serverMoves);
11408 serverMoves = fopen(appData.serverMovesName, "r");
11409 if(serverMoves != NULL) {
11410 fclose(serverMoves);
11411 /* delay 15 sec before overwriting, so all clients can see end */
11412 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11414 serverMoves = fopen(appData.serverMovesName, "w");
11418 gameMode = BeginningOfGame;
11420 if(appData.icsActive) gameInfo.variant = VariantNormal;
11421 currentMove = forwardMostMove = backwardMostMove = 0;
11422 MarkTargetSquares(1);
11423 InitPosition(redraw);
11424 for (i = 0; i < MAX_MOVES; i++) {
11425 if (commentList[i] != NULL) {
11426 free(commentList[i]);
11427 commentList[i] = NULL;
11431 timeRemaining[0][0] = whiteTimeRemaining;
11432 timeRemaining[1][0] = blackTimeRemaining;
11434 if (first.pr == NoProc) {
11435 StartChessProgram(&first);
11438 InitChessProgram(&first, startedFromSetupPosition);
11441 DisplayMessage("", "");
11442 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11443 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11444 ClearMap(); // [HGM] exclude: invalidate map
11448 AutoPlayGameLoop ()
11451 if (!AutoPlayOneMove())
11453 if (matchMode || appData.timeDelay == 0)
11455 if (appData.timeDelay < 0)
11457 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11465 ReloadGame(1); // next game
11471 int fromX, fromY, toX, toY;
11473 if (appData.debugMode) {
11474 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11477 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11480 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11481 pvInfoList[currentMove].depth = programStats.depth;
11482 pvInfoList[currentMove].score = programStats.score;
11483 pvInfoList[currentMove].time = 0;
11484 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11485 else { // append analysis of final position as comment
11487 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11488 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11490 programStats.depth = 0;
11493 if (currentMove >= forwardMostMove) {
11494 if(gameMode == AnalyzeFile) {
11495 if(appData.loadGameIndex == -1) {
11496 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11497 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11499 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11502 // gameMode = EndOfGame;
11503 // ModeHighlight();
11505 /* [AS] Clear current move marker at the end of a game */
11506 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11511 toX = moveList[currentMove][2] - AAA;
11512 toY = moveList[currentMove][3] - ONE;
11514 if (moveList[currentMove][1] == '@') {
11515 if (appData.highlightLastMove) {
11516 SetHighlights(-1, -1, toX, toY);
11519 fromX = moveList[currentMove][0] - AAA;
11520 fromY = moveList[currentMove][1] - ONE;
11522 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11524 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11526 if (appData.highlightLastMove) {
11527 SetHighlights(fromX, fromY, toX, toY);
11530 DisplayMove(currentMove);
11531 SendMoveToProgram(currentMove++, &first);
11532 DisplayBothClocks();
11533 DrawPosition(FALSE, boards[currentMove]);
11534 // [HGM] PV info: always display, routine tests if empty
11535 DisplayComment(currentMove - 1, commentList[currentMove]);
11541 LoadGameOneMove (ChessMove readAhead)
11543 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11544 char promoChar = NULLCHAR;
11545 ChessMove moveType;
11546 char move[MSG_SIZ];
11549 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11550 gameMode != AnalyzeMode && gameMode != Training) {
11555 yyboardindex = forwardMostMove;
11556 if (readAhead != EndOfFile) {
11557 moveType = readAhead;
11559 if (gameFileFP == NULL)
11561 moveType = (ChessMove) Myylex();
11565 switch (moveType) {
11567 if (appData.debugMode)
11568 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11571 /* append the comment but don't display it */
11572 AppendComment(currentMove, p, FALSE);
11575 case WhiteCapturesEnPassant:
11576 case BlackCapturesEnPassant:
11577 case WhitePromotion:
11578 case BlackPromotion:
11579 case WhiteNonPromotion:
11580 case BlackNonPromotion:
11583 case WhiteKingSideCastle:
11584 case WhiteQueenSideCastle:
11585 case BlackKingSideCastle:
11586 case BlackQueenSideCastle:
11587 case WhiteKingSideCastleWild:
11588 case WhiteQueenSideCastleWild:
11589 case BlackKingSideCastleWild:
11590 case BlackQueenSideCastleWild:
11592 case WhiteHSideCastleFR:
11593 case WhiteASideCastleFR:
11594 case BlackHSideCastleFR:
11595 case BlackASideCastleFR:
11597 if (appData.debugMode)
11598 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11599 fromX = currentMoveString[0] - AAA;
11600 fromY = currentMoveString[1] - ONE;
11601 toX = currentMoveString[2] - AAA;
11602 toY = currentMoveString[3] - ONE;
11603 promoChar = currentMoveString[4];
11604 if(promoChar == ';') promoChar = NULLCHAR;
11609 if (appData.debugMode)
11610 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11611 fromX = moveType == WhiteDrop ?
11612 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11613 (int) CharToPiece(ToLower(currentMoveString[0]));
11615 toX = currentMoveString[2] - AAA;
11616 toY = currentMoveString[3] - ONE;
11622 case GameUnfinished:
11623 if (appData.debugMode)
11624 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11625 p = strchr(yy_text, '{');
11626 if (p == NULL) p = strchr(yy_text, '(');
11629 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11631 q = strchr(p, *p == '{' ? '}' : ')');
11632 if (q != NULL) *q = NULLCHAR;
11635 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11636 GameEnds(moveType, p, GE_FILE);
11638 if (cmailMsgLoaded) {
11640 flipView = WhiteOnMove(currentMove);
11641 if (moveType == GameUnfinished) flipView = !flipView;
11642 if (appData.debugMode)
11643 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11648 if (appData.debugMode)
11649 fprintf(debugFP, "Parser hit end of file\n");
11650 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11656 if (WhiteOnMove(currentMove)) {
11657 GameEnds(BlackWins, "Black mates", GE_FILE);
11659 GameEnds(WhiteWins, "White mates", GE_FILE);
11663 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11669 case MoveNumberOne:
11670 if (lastLoadGameStart == GNUChessGame) {
11671 /* GNUChessGames have numbers, but they aren't move numbers */
11672 if (appData.debugMode)
11673 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11674 yy_text, (int) moveType);
11675 return LoadGameOneMove(EndOfFile); /* tail recursion */
11677 /* else fall thru */
11682 /* Reached start of next game in file */
11683 if (appData.debugMode)
11684 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11685 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11691 if (WhiteOnMove(currentMove)) {
11692 GameEnds(BlackWins, "Black mates", GE_FILE);
11694 GameEnds(WhiteWins, "White mates", GE_FILE);
11698 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11704 case PositionDiagram: /* should not happen; ignore */
11705 case ElapsedTime: /* ignore */
11706 case NAG: /* ignore */
11707 if (appData.debugMode)
11708 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11709 yy_text, (int) moveType);
11710 return LoadGameOneMove(EndOfFile); /* tail recursion */
11713 if (appData.testLegality) {
11714 if (appData.debugMode)
11715 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11716 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11717 (forwardMostMove / 2) + 1,
11718 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11719 DisplayError(move, 0);
11722 if (appData.debugMode)
11723 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11724 yy_text, currentMoveString);
11725 fromX = currentMoveString[0] - AAA;
11726 fromY = currentMoveString[1] - ONE;
11727 toX = currentMoveString[2] - AAA;
11728 toY = currentMoveString[3] - ONE;
11729 promoChar = currentMoveString[4];
11733 case AmbiguousMove:
11734 if (appData.debugMode)
11735 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11736 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11737 (forwardMostMove / 2) + 1,
11738 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11739 DisplayError(move, 0);
11744 case ImpossibleMove:
11745 if (appData.debugMode)
11746 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11747 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11748 (forwardMostMove / 2) + 1,
11749 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11750 DisplayError(move, 0);
11756 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11757 DrawPosition(FALSE, boards[currentMove]);
11758 DisplayBothClocks();
11759 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11760 DisplayComment(currentMove - 1, commentList[currentMove]);
11762 (void) StopLoadGameTimer();
11764 cmailOldMove = forwardMostMove;
11767 /* currentMoveString is set as a side-effect of yylex */
11769 thinkOutput[0] = NULLCHAR;
11770 MakeMove(fromX, fromY, toX, toY, promoChar);
11771 killX = killY = -1; // [HGM] lion: used up
11772 currentMove = forwardMostMove;
11777 /* Load the nth game from the given file */
11779 LoadGameFromFile (char *filename, int n, char *title, int useList)
11784 if (strcmp(filename, "-") == 0) {
11788 f = fopen(filename, "rb");
11790 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11791 DisplayError(buf, errno);
11795 if (fseek(f, 0, 0) == -1) {
11796 /* f is not seekable; probably a pipe */
11799 if (useList && n == 0) {
11800 int error = GameListBuild(f);
11802 DisplayError(_("Cannot build game list"), error);
11803 } else if (!ListEmpty(&gameList) &&
11804 ((ListGame *) gameList.tailPred)->number > 1) {
11805 GameListPopUp(f, title);
11812 return LoadGame(f, n, title, FALSE);
11817 MakeRegisteredMove ()
11819 int fromX, fromY, toX, toY;
11821 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11822 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11825 if (appData.debugMode)
11826 fprintf(debugFP, "Restoring %s for game %d\n",
11827 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11829 thinkOutput[0] = NULLCHAR;
11830 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11831 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11832 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11833 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11834 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11835 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11836 MakeMove(fromX, fromY, toX, toY, promoChar);
11837 ShowMove(fromX, fromY, toX, toY);
11839 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11846 if (WhiteOnMove(currentMove)) {
11847 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11849 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11854 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11861 if (WhiteOnMove(currentMove)) {
11862 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11864 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11869 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11880 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11882 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11886 if (gameNumber > nCmailGames) {
11887 DisplayError(_("No more games in this message"), 0);
11890 if (f == lastLoadGameFP) {
11891 int offset = gameNumber - lastLoadGameNumber;
11893 cmailMsg[0] = NULLCHAR;
11894 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11895 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11896 nCmailMovesRegistered--;
11898 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11899 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11900 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11903 if (! RegisterMove()) return FALSE;
11907 retVal = LoadGame(f, gameNumber, title, useList);
11909 /* Make move registered during previous look at this game, if any */
11910 MakeRegisteredMove();
11912 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11913 commentList[currentMove]
11914 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11915 DisplayComment(currentMove - 1, commentList[currentMove]);
11921 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11923 ReloadGame (int offset)
11925 int gameNumber = lastLoadGameNumber + offset;
11926 if (lastLoadGameFP == NULL) {
11927 DisplayError(_("No game has been loaded yet"), 0);
11930 if (gameNumber <= 0) {
11931 DisplayError(_("Can't back up any further"), 0);
11934 if (cmailMsgLoaded) {
11935 return CmailLoadGame(lastLoadGameFP, gameNumber,
11936 lastLoadGameTitle, lastLoadGameUseList);
11938 return LoadGame(lastLoadGameFP, gameNumber,
11939 lastLoadGameTitle, lastLoadGameUseList);
11943 int keys[EmptySquare+1];
11946 PositionMatches (Board b1, Board b2)
11949 switch(appData.searchMode) {
11950 case 1: return CompareWithRights(b1, b2);
11952 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11953 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11957 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11958 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11959 sum += keys[b1[r][f]] - keys[b2[r][f]];
11963 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11964 sum += keys[b1[r][f]] - keys[b2[r][f]];
11976 int pieceList[256], quickBoard[256];
11977 ChessSquare pieceType[256] = { EmptySquare };
11978 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11979 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11980 int soughtTotal, turn;
11981 Boolean epOK, flipSearch;
11984 unsigned char piece, to;
11987 #define DSIZE (250000)
11989 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11990 Move *moveDatabase = initialSpace;
11991 unsigned int movePtr, dataSize = DSIZE;
11994 MakePieceList (Board board, int *counts)
11996 int r, f, n=Q_PROMO, total=0;
11997 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11998 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11999 int sq = f + (r<<4);
12000 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12001 quickBoard[sq] = ++n;
12003 pieceType[n] = board[r][f];
12004 counts[board[r][f]]++;
12005 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12006 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12010 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12015 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12017 int sq = fromX + (fromY<<4);
12018 int piece = quickBoard[sq];
12019 quickBoard[sq] = 0;
12020 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12021 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12022 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12023 moveDatabase[movePtr++].piece = Q_WCASTL;
12024 quickBoard[sq] = piece;
12025 piece = quickBoard[from]; quickBoard[from] = 0;
12026 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12028 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12029 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12030 moveDatabase[movePtr++].piece = Q_BCASTL;
12031 quickBoard[sq] = piece;
12032 piece = quickBoard[from]; quickBoard[from] = 0;
12033 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12035 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12036 quickBoard[(fromY<<4)+toX] = 0;
12037 moveDatabase[movePtr].piece = Q_EP;
12038 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12039 moveDatabase[movePtr].to = sq;
12041 if(promoPiece != pieceType[piece]) {
12042 moveDatabase[movePtr++].piece = Q_PROMO;
12043 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12045 moveDatabase[movePtr].piece = piece;
12046 quickBoard[sq] = piece;
12051 PackGame (Board board)
12053 Move *newSpace = NULL;
12054 moveDatabase[movePtr].piece = 0; // terminate previous game
12055 if(movePtr > dataSize) {
12056 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12057 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12058 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12061 Move *p = moveDatabase, *q = newSpace;
12062 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12063 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12064 moveDatabase = newSpace;
12065 } else { // calloc failed, we must be out of memory. Too bad...
12066 dataSize = 0; // prevent calloc events for all subsequent games
12067 return 0; // and signal this one isn't cached
12071 MakePieceList(board, counts);
12076 QuickCompare (Board board, int *minCounts, int *maxCounts)
12077 { // compare according to search mode
12079 switch(appData.searchMode)
12081 case 1: // exact position match
12082 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12083 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12084 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12087 case 2: // can have extra material on empty squares
12088 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12089 if(board[r][f] == EmptySquare) continue;
12090 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12093 case 3: // material with exact Pawn structure
12094 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12095 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12096 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12097 } // fall through to material comparison
12098 case 4: // exact material
12099 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12101 case 6: // material range with given imbalance
12102 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12103 // fall through to range comparison
12104 case 5: // material range
12105 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12111 QuickScan (Board board, Move *move)
12112 { // reconstruct game,and compare all positions in it
12113 int cnt=0, stretch=0, total = MakePieceList(board, counts);
12115 int piece = move->piece;
12116 int to = move->to, from = pieceList[piece];
12117 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12118 if(!piece) return -1;
12119 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12120 piece = (++move)->piece;
12121 from = pieceList[piece];
12122 counts[pieceType[piece]]--;
12123 pieceType[piece] = (ChessSquare) move->to;
12124 counts[move->to]++;
12125 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12126 counts[pieceType[quickBoard[to]]]--;
12127 quickBoard[to] = 0; total--;
12130 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12131 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12132 from = pieceList[piece]; // so this must be King
12133 quickBoard[from] = 0;
12134 pieceList[piece] = to;
12135 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12136 quickBoard[from] = 0; // rook
12137 quickBoard[to] = piece;
12138 to = move->to; piece = move->piece;
12142 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12143 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12144 quickBoard[from] = 0;
12146 quickBoard[to] = piece;
12147 pieceList[piece] = to;
12149 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12150 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12151 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12152 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12154 static int lastCounts[EmptySquare+1];
12156 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12157 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12158 } else stretch = 0;
12159 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12168 flipSearch = FALSE;
12169 CopyBoard(soughtBoard, boards[currentMove]);
12170 soughtTotal = MakePieceList(soughtBoard, maxSought);
12171 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12172 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12173 CopyBoard(reverseBoard, boards[currentMove]);
12174 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12175 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12176 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12177 reverseBoard[r][f] = piece;
12179 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12180 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12181 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12182 || (boards[currentMove][CASTLING][2] == NoRights ||
12183 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12184 && (boards[currentMove][CASTLING][5] == NoRights ||
12185 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12188 CopyBoard(flipBoard, soughtBoard);
12189 CopyBoard(rotateBoard, reverseBoard);
12190 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12191 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12192 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12195 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12196 if(appData.searchMode >= 5) {
12197 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12198 MakePieceList(soughtBoard, minSought);
12199 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12201 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12202 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12205 GameInfo dummyInfo;
12206 static int creatingBook;
12209 GameContainsPosition (FILE *f, ListGame *lg)
12211 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12212 int fromX, fromY, toX, toY;
12214 static int initDone=FALSE;
12216 // weed out games based on numerical tag comparison
12217 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12218 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12219 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12220 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12222 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12225 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12226 else CopyBoard(boards[scratch], initialPosition); // default start position
12229 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12230 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12233 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12234 fseek(f, lg->offset, 0);
12237 yyboardindex = scratch;
12238 quickFlag = plyNr+1;
12243 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12249 if(plyNr) return -1; // after we have seen moves, this is for new game
12252 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12253 case ImpossibleMove:
12254 case WhiteWins: // game ends here with these four
12257 case GameUnfinished:
12261 if(appData.testLegality) return -1;
12262 case WhiteCapturesEnPassant:
12263 case BlackCapturesEnPassant:
12264 case WhitePromotion:
12265 case BlackPromotion:
12266 case WhiteNonPromotion:
12267 case BlackNonPromotion:
12270 case WhiteKingSideCastle:
12271 case WhiteQueenSideCastle:
12272 case BlackKingSideCastle:
12273 case BlackQueenSideCastle:
12274 case WhiteKingSideCastleWild:
12275 case WhiteQueenSideCastleWild:
12276 case BlackKingSideCastleWild:
12277 case BlackQueenSideCastleWild:
12278 case WhiteHSideCastleFR:
12279 case WhiteASideCastleFR:
12280 case BlackHSideCastleFR:
12281 case BlackASideCastleFR:
12282 fromX = currentMoveString[0] - AAA;
12283 fromY = currentMoveString[1] - ONE;
12284 toX = currentMoveString[2] - AAA;
12285 toY = currentMoveString[3] - ONE;
12286 promoChar = currentMoveString[4];
12290 fromX = next == WhiteDrop ?
12291 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12292 (int) CharToPiece(ToLower(currentMoveString[0]));
12294 toX = currentMoveString[2] - AAA;
12295 toY = currentMoveString[3] - ONE;
12299 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12301 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12302 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12303 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12304 if(appData.findMirror) {
12305 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12306 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12311 /* Load the nth game from open file f */
12313 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12317 int gn = gameNumber;
12318 ListGame *lg = NULL;
12319 int numPGNTags = 0;
12321 GameMode oldGameMode;
12322 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12324 if (appData.debugMode)
12325 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12327 if (gameMode == Training )
12328 SetTrainingModeOff();
12330 oldGameMode = gameMode;
12331 if (gameMode != BeginningOfGame) {
12332 Reset(FALSE, TRUE);
12334 killX = killY = -1; // [HGM] lion: in case we did not Reset
12337 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12338 fclose(lastLoadGameFP);
12342 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12345 fseek(f, lg->offset, 0);
12346 GameListHighlight(gameNumber);
12347 pos = lg->position;
12351 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12352 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12354 DisplayError(_("Game number out of range"), 0);
12359 if (fseek(f, 0, 0) == -1) {
12360 if (f == lastLoadGameFP ?
12361 gameNumber == lastLoadGameNumber + 1 :
12365 DisplayError(_("Can't seek on game file"), 0);
12370 lastLoadGameFP = f;
12371 lastLoadGameNumber = gameNumber;
12372 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12373 lastLoadGameUseList = useList;
12377 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12378 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12379 lg->gameInfo.black);
12381 } else if (*title != NULLCHAR) {
12382 if (gameNumber > 1) {
12383 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12386 DisplayTitle(title);
12390 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12391 gameMode = PlayFromGameFile;
12395 currentMove = forwardMostMove = backwardMostMove = 0;
12396 CopyBoard(boards[0], initialPosition);
12400 * Skip the first gn-1 games in the file.
12401 * Also skip over anything that precedes an identifiable
12402 * start of game marker, to avoid being confused by
12403 * garbage at the start of the file. Currently
12404 * recognized start of game markers are the move number "1",
12405 * the pattern "gnuchess .* game", the pattern
12406 * "^[#;%] [^ ]* game file", and a PGN tag block.
12407 * A game that starts with one of the latter two patterns
12408 * will also have a move number 1, possibly
12409 * following a position diagram.
12410 * 5-4-02: Let's try being more lenient and allowing a game to
12411 * start with an unnumbered move. Does that break anything?
12413 cm = lastLoadGameStart = EndOfFile;
12415 yyboardindex = forwardMostMove;
12416 cm = (ChessMove) Myylex();
12419 if (cmailMsgLoaded) {
12420 nCmailGames = CMAIL_MAX_GAMES - gn;
12423 DisplayError(_("Game not found in file"), 0);
12430 lastLoadGameStart = cm;
12433 case MoveNumberOne:
12434 switch (lastLoadGameStart) {
12439 case MoveNumberOne:
12441 gn--; /* count this game */
12442 lastLoadGameStart = cm;
12451 switch (lastLoadGameStart) {
12454 case MoveNumberOne:
12456 gn--; /* count this game */
12457 lastLoadGameStart = cm;
12460 lastLoadGameStart = cm; /* game counted already */
12468 yyboardindex = forwardMostMove;
12469 cm = (ChessMove) Myylex();
12470 } while (cm == PGNTag || cm == Comment);
12477 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12478 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12479 != CMAIL_OLD_RESULT) {
12481 cmailResult[ CMAIL_MAX_GAMES
12482 - gn - 1] = CMAIL_OLD_RESULT;
12489 /* Only a NormalMove can be at the start of a game
12490 * without a position diagram. */
12491 if (lastLoadGameStart == EndOfFile ) {
12493 lastLoadGameStart = MoveNumberOne;
12502 if (appData.debugMode)
12503 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12505 if (cm == XBoardGame) {
12506 /* Skip any header junk before position diagram and/or move 1 */
12508 yyboardindex = forwardMostMove;
12509 cm = (ChessMove) Myylex();
12511 if (cm == EndOfFile ||
12512 cm == GNUChessGame || cm == XBoardGame) {
12513 /* Empty game; pretend end-of-file and handle later */
12518 if (cm == MoveNumberOne || cm == PositionDiagram ||
12519 cm == PGNTag || cm == Comment)
12522 } else if (cm == GNUChessGame) {
12523 if (gameInfo.event != NULL) {
12524 free(gameInfo.event);
12526 gameInfo.event = StrSave(yy_text);
12529 startedFromSetupPosition = FALSE;
12530 while (cm == PGNTag) {
12531 if (appData.debugMode)
12532 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12533 err = ParsePGNTag(yy_text, &gameInfo);
12534 if (!err) numPGNTags++;
12536 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12537 if(gameInfo.variant != oldVariant) {
12538 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12539 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12540 InitPosition(TRUE);
12541 oldVariant = gameInfo.variant;
12542 if (appData.debugMode)
12543 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12547 if (gameInfo.fen != NULL) {
12548 Board initial_position;
12549 startedFromSetupPosition = TRUE;
12550 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12552 DisplayError(_("Bad FEN position in file"), 0);
12555 CopyBoard(boards[0], initial_position);
12556 if (blackPlaysFirst) {
12557 currentMove = forwardMostMove = backwardMostMove = 1;
12558 CopyBoard(boards[1], initial_position);
12559 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12560 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12561 timeRemaining[0][1] = whiteTimeRemaining;
12562 timeRemaining[1][1] = blackTimeRemaining;
12563 if (commentList[0] != NULL) {
12564 commentList[1] = commentList[0];
12565 commentList[0] = NULL;
12568 currentMove = forwardMostMove = backwardMostMove = 0;
12570 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12572 initialRulePlies = FENrulePlies;
12573 for( i=0; i< nrCastlingRights; i++ )
12574 initialRights[i] = initial_position[CASTLING][i];
12576 yyboardindex = forwardMostMove;
12577 free(gameInfo.fen);
12578 gameInfo.fen = NULL;
12581 yyboardindex = forwardMostMove;
12582 cm = (ChessMove) Myylex();
12584 /* Handle comments interspersed among the tags */
12585 while (cm == Comment) {
12587 if (appData.debugMode)
12588 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12590 AppendComment(currentMove, p, FALSE);
12591 yyboardindex = forwardMostMove;
12592 cm = (ChessMove) Myylex();
12596 /* don't rely on existence of Event tag since if game was
12597 * pasted from clipboard the Event tag may not exist
12599 if (numPGNTags > 0){
12601 if (gameInfo.variant == VariantNormal) {
12602 VariantClass v = StringToVariant(gameInfo.event);
12603 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12604 if(v < VariantShogi) gameInfo.variant = v;
12607 if( appData.autoDisplayTags ) {
12608 tags = PGNTags(&gameInfo);
12609 TagsPopUp(tags, CmailMsg());
12614 /* Make something up, but don't display it now */
12619 if (cm == PositionDiagram) {
12622 Board initial_position;
12624 if (appData.debugMode)
12625 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12627 if (!startedFromSetupPosition) {
12629 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12630 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12641 initial_position[i][j++] = CharToPiece(*p);
12644 while (*p == ' ' || *p == '\t' ||
12645 *p == '\n' || *p == '\r') p++;
12647 if (strncmp(p, "black", strlen("black"))==0)
12648 blackPlaysFirst = TRUE;
12650 blackPlaysFirst = FALSE;
12651 startedFromSetupPosition = TRUE;
12653 CopyBoard(boards[0], initial_position);
12654 if (blackPlaysFirst) {
12655 currentMove = forwardMostMove = backwardMostMove = 1;
12656 CopyBoard(boards[1], initial_position);
12657 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12658 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12659 timeRemaining[0][1] = whiteTimeRemaining;
12660 timeRemaining[1][1] = blackTimeRemaining;
12661 if (commentList[0] != NULL) {
12662 commentList[1] = commentList[0];
12663 commentList[0] = NULL;
12666 currentMove = forwardMostMove = backwardMostMove = 0;
12669 yyboardindex = forwardMostMove;
12670 cm = (ChessMove) Myylex();
12673 if(!creatingBook) {
12674 if (first.pr == NoProc) {
12675 StartChessProgram(&first);
12677 InitChessProgram(&first, FALSE);
12678 SendToProgram("force\n", &first);
12679 if (startedFromSetupPosition) {
12680 SendBoard(&first, forwardMostMove);
12681 if (appData.debugMode) {
12682 fprintf(debugFP, "Load Game\n");
12684 DisplayBothClocks();
12688 /* [HGM] server: flag to write setup moves in broadcast file as one */
12689 loadFlag = appData.suppressLoadMoves;
12691 while (cm == Comment) {
12693 if (appData.debugMode)
12694 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12696 AppendComment(currentMove, p, FALSE);
12697 yyboardindex = forwardMostMove;
12698 cm = (ChessMove) Myylex();
12701 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12702 cm == WhiteWins || cm == BlackWins ||
12703 cm == GameIsDrawn || cm == GameUnfinished) {
12704 DisplayMessage("", _("No moves in game"));
12705 if (cmailMsgLoaded) {
12706 if (appData.debugMode)
12707 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12711 DrawPosition(FALSE, boards[currentMove]);
12712 DisplayBothClocks();
12713 gameMode = EditGame;
12720 // [HGM] PV info: routine tests if comment empty
12721 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12722 DisplayComment(currentMove - 1, commentList[currentMove]);
12724 if (!matchMode && appData.timeDelay != 0)
12725 DrawPosition(FALSE, boards[currentMove]);
12727 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12728 programStats.ok_to_send = 1;
12731 /* if the first token after the PGN tags is a move
12732 * and not move number 1, retrieve it from the parser
12734 if (cm != MoveNumberOne)
12735 LoadGameOneMove(cm);
12737 /* load the remaining moves from the file */
12738 while (LoadGameOneMove(EndOfFile)) {
12739 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12740 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12743 /* rewind to the start of the game */
12744 currentMove = backwardMostMove;
12746 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12748 if (oldGameMode == AnalyzeFile) {
12749 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12750 AnalyzeFileEvent();
12752 if (oldGameMode == AnalyzeMode) {
12753 AnalyzeFileEvent();
12756 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12757 long int w, b; // [HGM] adjourn: restore saved clock times
12758 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12759 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12760 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12761 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12765 if(creatingBook) return TRUE;
12766 if (!matchMode && pos > 0) {
12767 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12769 if (matchMode || appData.timeDelay == 0) {
12771 } else if (appData.timeDelay > 0) {
12772 AutoPlayGameLoop();
12775 if (appData.debugMode)
12776 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12778 loadFlag = 0; /* [HGM] true game starts */
12782 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12784 ReloadPosition (int offset)
12786 int positionNumber = lastLoadPositionNumber + offset;
12787 if (lastLoadPositionFP == NULL) {
12788 DisplayError(_("No position has been loaded yet"), 0);
12791 if (positionNumber <= 0) {
12792 DisplayError(_("Can't back up any further"), 0);
12795 return LoadPosition(lastLoadPositionFP, positionNumber,
12796 lastLoadPositionTitle);
12799 /* Load the nth position from the given file */
12801 LoadPositionFromFile (char *filename, int n, char *title)
12806 if (strcmp(filename, "-") == 0) {
12807 return LoadPosition(stdin, n, "stdin");
12809 f = fopen(filename, "rb");
12811 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12812 DisplayError(buf, errno);
12815 return LoadPosition(f, n, title);
12820 /* Load the nth position from the given open file, and close it */
12822 LoadPosition (FILE *f, int positionNumber, char *title)
12824 char *p, line[MSG_SIZ];
12825 Board initial_position;
12826 int i, j, fenMode, pn;
12828 if (gameMode == Training )
12829 SetTrainingModeOff();
12831 if (gameMode != BeginningOfGame) {
12832 Reset(FALSE, TRUE);
12834 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12835 fclose(lastLoadPositionFP);
12837 if (positionNumber == 0) positionNumber = 1;
12838 lastLoadPositionFP = f;
12839 lastLoadPositionNumber = positionNumber;
12840 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12841 if (first.pr == NoProc && !appData.noChessProgram) {
12842 StartChessProgram(&first);
12843 InitChessProgram(&first, FALSE);
12845 pn = positionNumber;
12846 if (positionNumber < 0) {
12847 /* Negative position number means to seek to that byte offset */
12848 if (fseek(f, -positionNumber, 0) == -1) {
12849 DisplayError(_("Can't seek on position file"), 0);
12854 if (fseek(f, 0, 0) == -1) {
12855 if (f == lastLoadPositionFP ?
12856 positionNumber == lastLoadPositionNumber + 1 :
12857 positionNumber == 1) {
12860 DisplayError(_("Can't seek on position file"), 0);
12865 /* See if this file is FEN or old-style xboard */
12866 if (fgets(line, MSG_SIZ, f) == NULL) {
12867 DisplayError(_("Position not found in file"), 0);
12870 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12871 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12874 if (fenMode || line[0] == '#') pn--;
12876 /* skip positions before number pn */
12877 if (fgets(line, MSG_SIZ, f) == NULL) {
12879 DisplayError(_("Position not found in file"), 0);
12882 if (fenMode || line[0] == '#') pn--;
12887 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12888 DisplayError(_("Bad FEN position in file"), 0);
12892 (void) fgets(line, MSG_SIZ, f);
12893 (void) fgets(line, MSG_SIZ, f);
12895 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12896 (void) fgets(line, MSG_SIZ, f);
12897 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12900 initial_position[i][j++] = CharToPiece(*p);
12904 blackPlaysFirst = FALSE;
12906 (void) fgets(line, MSG_SIZ, f);
12907 if (strncmp(line, "black", strlen("black"))==0)
12908 blackPlaysFirst = TRUE;
12911 startedFromSetupPosition = TRUE;
12913 CopyBoard(boards[0], initial_position);
12914 if (blackPlaysFirst) {
12915 currentMove = forwardMostMove = backwardMostMove = 1;
12916 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12917 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12918 CopyBoard(boards[1], initial_position);
12919 DisplayMessage("", _("Black to play"));
12921 currentMove = forwardMostMove = backwardMostMove = 0;
12922 DisplayMessage("", _("White to play"));
12924 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12925 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12926 SendToProgram("force\n", &first);
12927 SendBoard(&first, forwardMostMove);
12929 if (appData.debugMode) {
12931 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12932 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12933 fprintf(debugFP, "Load Position\n");
12936 if (positionNumber > 1) {
12937 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12938 DisplayTitle(line);
12940 DisplayTitle(title);
12942 gameMode = EditGame;
12945 timeRemaining[0][1] = whiteTimeRemaining;
12946 timeRemaining[1][1] = blackTimeRemaining;
12947 DrawPosition(FALSE, boards[currentMove]);
12954 CopyPlayerNameIntoFileName (char **dest, char *src)
12956 while (*src != NULLCHAR && *src != ',') {
12961 *(*dest)++ = *src++;
12967 DefaultFileName (char *ext)
12969 static char def[MSG_SIZ];
12972 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12974 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12976 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12978 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12985 /* Save the current game to the given file */
12987 SaveGameToFile (char *filename, int append)
12991 int result, i, t,tot=0;
12993 if (strcmp(filename, "-") == 0) {
12994 return SaveGame(stdout, 0, NULL);
12996 for(i=0; i<10; i++) { // upto 10 tries
12997 f = fopen(filename, append ? "a" : "w");
12998 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12999 if(f || errno != 13) break;
13000 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13004 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13005 DisplayError(buf, errno);
13008 safeStrCpy(buf, lastMsg, MSG_SIZ);
13009 DisplayMessage(_("Waiting for access to save file"), "");
13010 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13011 DisplayMessage(_("Saving game"), "");
13012 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13013 result = SaveGame(f, 0, NULL);
13014 DisplayMessage(buf, "");
13021 SavePart (char *str)
13023 static char buf[MSG_SIZ];
13026 p = strchr(str, ' ');
13027 if (p == NULL) return str;
13028 strncpy(buf, str, p - str);
13029 buf[p - str] = NULLCHAR;
13033 #define PGN_MAX_LINE 75
13035 #define PGN_SIDE_WHITE 0
13036 #define PGN_SIDE_BLACK 1
13039 FindFirstMoveOutOfBook (int side)
13043 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13044 int index = backwardMostMove;
13045 int has_book_hit = 0;
13047 if( (index % 2) != side ) {
13051 while( index < forwardMostMove ) {
13052 /* Check to see if engine is in book */
13053 int depth = pvInfoList[index].depth;
13054 int score = pvInfoList[index].score;
13060 else if( score == 0 && depth == 63 ) {
13061 in_book = 1; /* Zappa */
13063 else if( score == 2 && depth == 99 ) {
13064 in_book = 1; /* Abrok */
13067 has_book_hit += in_book;
13083 GetOutOfBookInfo (char * buf)
13087 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13089 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13090 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13094 if( oob[0] >= 0 || oob[1] >= 0 ) {
13095 for( i=0; i<2; i++ ) {
13099 if( i > 0 && oob[0] >= 0 ) {
13100 strcat( buf, " " );
13103 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13104 sprintf( buf+strlen(buf), "%s%.2f",
13105 pvInfoList[idx].score >= 0 ? "+" : "",
13106 pvInfoList[idx].score / 100.0 );
13112 /* Save game in PGN style and close the file */
13114 SaveGamePGN (FILE *f)
13116 int i, offset, linelen, newblock;
13119 int movelen, numlen, blank;
13120 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13122 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13124 PrintPGNTags(f, &gameInfo);
13126 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13128 if (backwardMostMove > 0 || startedFromSetupPosition) {
13129 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13130 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13131 fprintf(f, "\n{--------------\n");
13132 PrintPosition(f, backwardMostMove);
13133 fprintf(f, "--------------}\n");
13137 /* [AS] Out of book annotation */
13138 if( appData.saveOutOfBookInfo ) {
13141 GetOutOfBookInfo( buf );
13143 if( buf[0] != '\0' ) {
13144 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13151 i = backwardMostMove;
13155 while (i < forwardMostMove) {
13156 /* Print comments preceding this move */
13157 if (commentList[i] != NULL) {
13158 if (linelen > 0) fprintf(f, "\n");
13159 fprintf(f, "%s", commentList[i]);
13164 /* Format move number */
13166 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13169 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13171 numtext[0] = NULLCHAR;
13173 numlen = strlen(numtext);
13176 /* Print move number */
13177 blank = linelen > 0 && numlen > 0;
13178 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13187 fprintf(f, "%s", numtext);
13191 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13192 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13195 blank = linelen > 0 && movelen > 0;
13196 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13205 fprintf(f, "%s", move_buffer);
13206 linelen += movelen;
13208 /* [AS] Add PV info if present */
13209 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13210 /* [HGM] add time */
13211 char buf[MSG_SIZ]; int seconds;
13213 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13219 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13222 seconds = (seconds + 4)/10; // round to full seconds
13224 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13226 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13229 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13230 pvInfoList[i].score >= 0 ? "+" : "",
13231 pvInfoList[i].score / 100.0,
13232 pvInfoList[i].depth,
13235 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13237 /* Print score/depth */
13238 blank = linelen > 0 && movelen > 0;
13239 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13248 fprintf(f, "%s", move_buffer);
13249 linelen += movelen;
13255 /* Start a new line */
13256 if (linelen > 0) fprintf(f, "\n");
13258 /* Print comments after last move */
13259 if (commentList[i] != NULL) {
13260 fprintf(f, "%s\n", commentList[i]);
13264 if (gameInfo.resultDetails != NULL &&
13265 gameInfo.resultDetails[0] != NULLCHAR) {
13266 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13267 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13268 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13269 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13270 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13272 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13276 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13280 /* Save game in old style and close the file */
13282 SaveGameOldStyle (FILE *f)
13287 tm = time((time_t *) NULL);
13289 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13292 if (backwardMostMove > 0 || startedFromSetupPosition) {
13293 fprintf(f, "\n[--------------\n");
13294 PrintPosition(f, backwardMostMove);
13295 fprintf(f, "--------------]\n");
13300 i = backwardMostMove;
13301 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13303 while (i < forwardMostMove) {
13304 if (commentList[i] != NULL) {
13305 fprintf(f, "[%s]\n", commentList[i]);
13308 if ((i % 2) == 1) {
13309 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13312 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13314 if (commentList[i] != NULL) {
13318 if (i >= forwardMostMove) {
13322 fprintf(f, "%s\n", parseList[i]);
13327 if (commentList[i] != NULL) {
13328 fprintf(f, "[%s]\n", commentList[i]);
13331 /* This isn't really the old style, but it's close enough */
13332 if (gameInfo.resultDetails != NULL &&
13333 gameInfo.resultDetails[0] != NULLCHAR) {
13334 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13335 gameInfo.resultDetails);
13337 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13344 /* Save the current game to open file f and close the file */
13346 SaveGame (FILE *f, int dummy, char *dummy2)
13348 if (gameMode == EditPosition) EditPositionDone(TRUE);
13349 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13350 if (appData.oldSaveStyle)
13351 return SaveGameOldStyle(f);
13353 return SaveGamePGN(f);
13356 /* Save the current position to the given file */
13358 SavePositionToFile (char *filename)
13363 if (strcmp(filename, "-") == 0) {
13364 return SavePosition(stdout, 0, NULL);
13366 f = fopen(filename, "a");
13368 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13369 DisplayError(buf, errno);
13372 safeStrCpy(buf, lastMsg, MSG_SIZ);
13373 DisplayMessage(_("Waiting for access to save file"), "");
13374 flock(fileno(f), LOCK_EX); // [HGM] lock
13375 DisplayMessage(_("Saving position"), "");
13376 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13377 SavePosition(f, 0, NULL);
13378 DisplayMessage(buf, "");
13384 /* Save the current position to the given open file and close the file */
13386 SavePosition (FILE *f, int dummy, char *dummy2)
13391 if (gameMode == EditPosition) EditPositionDone(TRUE);
13392 if (appData.oldSaveStyle) {
13393 tm = time((time_t *) NULL);
13395 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13397 fprintf(f, "[--------------\n");
13398 PrintPosition(f, currentMove);
13399 fprintf(f, "--------------]\n");
13401 fen = PositionToFEN(currentMove, NULL, 1);
13402 fprintf(f, "%s\n", fen);
13410 ReloadCmailMsgEvent (int unregister)
13413 static char *inFilename = NULL;
13414 static char *outFilename;
13416 struct stat inbuf, outbuf;
13419 /* Any registered moves are unregistered if unregister is set, */
13420 /* i.e. invoked by the signal handler */
13422 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13423 cmailMoveRegistered[i] = FALSE;
13424 if (cmailCommentList[i] != NULL) {
13425 free(cmailCommentList[i]);
13426 cmailCommentList[i] = NULL;
13429 nCmailMovesRegistered = 0;
13432 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13433 cmailResult[i] = CMAIL_NOT_RESULT;
13437 if (inFilename == NULL) {
13438 /* Because the filenames are static they only get malloced once */
13439 /* and they never get freed */
13440 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13441 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13443 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13444 sprintf(outFilename, "%s.out", appData.cmailGameName);
13447 status = stat(outFilename, &outbuf);
13449 cmailMailedMove = FALSE;
13451 status = stat(inFilename, &inbuf);
13452 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13455 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13456 counts the games, notes how each one terminated, etc.
13458 It would be nice to remove this kludge and instead gather all
13459 the information while building the game list. (And to keep it
13460 in the game list nodes instead of having a bunch of fixed-size
13461 parallel arrays.) Note this will require getting each game's
13462 termination from the PGN tags, as the game list builder does
13463 not process the game moves. --mann
13465 cmailMsgLoaded = TRUE;
13466 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13468 /* Load first game in the file or popup game menu */
13469 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13471 #endif /* !WIN32 */
13479 char string[MSG_SIZ];
13481 if ( cmailMailedMove
13482 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13483 return TRUE; /* Allow free viewing */
13486 /* Unregister move to ensure that we don't leave RegisterMove */
13487 /* with the move registered when the conditions for registering no */
13489 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13490 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13491 nCmailMovesRegistered --;
13493 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13495 free(cmailCommentList[lastLoadGameNumber - 1]);
13496 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13500 if (cmailOldMove == -1) {
13501 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13505 if (currentMove > cmailOldMove + 1) {
13506 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13510 if (currentMove < cmailOldMove) {
13511 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13515 if (forwardMostMove > currentMove) {
13516 /* Silently truncate extra moves */
13520 if ( (currentMove == cmailOldMove + 1)
13521 || ( (currentMove == cmailOldMove)
13522 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13523 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13524 if (gameInfo.result != GameUnfinished) {
13525 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13528 if (commentList[currentMove] != NULL) {
13529 cmailCommentList[lastLoadGameNumber - 1]
13530 = StrSave(commentList[currentMove]);
13532 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13534 if (appData.debugMode)
13535 fprintf(debugFP, "Saving %s for game %d\n",
13536 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13538 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13540 f = fopen(string, "w");
13541 if (appData.oldSaveStyle) {
13542 SaveGameOldStyle(f); /* also closes the file */
13544 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13545 f = fopen(string, "w");
13546 SavePosition(f, 0, NULL); /* also closes the file */
13548 fprintf(f, "{--------------\n");
13549 PrintPosition(f, currentMove);
13550 fprintf(f, "--------------}\n\n");
13552 SaveGame(f, 0, NULL); /* also closes the file*/
13555 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13556 nCmailMovesRegistered ++;
13557 } else if (nCmailGames == 1) {
13558 DisplayError(_("You have not made a move yet"), 0);
13569 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13570 FILE *commandOutput;
13571 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13572 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13578 if (! cmailMsgLoaded) {
13579 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13583 if (nCmailGames == nCmailResults) {
13584 DisplayError(_("No unfinished games"), 0);
13588 #if CMAIL_PROHIBIT_REMAIL
13589 if (cmailMailedMove) {
13590 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);
13591 DisplayError(msg, 0);
13596 if (! (cmailMailedMove || RegisterMove())) return;
13598 if ( cmailMailedMove
13599 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13600 snprintf(string, MSG_SIZ, partCommandString,
13601 appData.debugMode ? " -v" : "", appData.cmailGameName);
13602 commandOutput = popen(string, "r");
13604 if (commandOutput == NULL) {
13605 DisplayError(_("Failed to invoke cmail"), 0);
13607 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13608 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13610 if (nBuffers > 1) {
13611 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13612 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13613 nBytes = MSG_SIZ - 1;
13615 (void) memcpy(msg, buffer, nBytes);
13617 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13619 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13620 cmailMailedMove = TRUE; /* Prevent >1 moves */
13623 for (i = 0; i < nCmailGames; i ++) {
13624 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13629 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13631 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13633 appData.cmailGameName,
13635 LoadGameFromFile(buffer, 1, buffer, FALSE);
13636 cmailMsgLoaded = FALSE;
13640 DisplayInformation(msg);
13641 pclose(commandOutput);
13644 if ((*cmailMsg) != '\0') {
13645 DisplayInformation(cmailMsg);
13650 #endif /* !WIN32 */
13659 int prependComma = 0;
13661 char string[MSG_SIZ]; /* Space for game-list */
13664 if (!cmailMsgLoaded) return "";
13666 if (cmailMailedMove) {
13667 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13669 /* Create a list of games left */
13670 snprintf(string, MSG_SIZ, "[");
13671 for (i = 0; i < nCmailGames; i ++) {
13672 if (! ( cmailMoveRegistered[i]
13673 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13674 if (prependComma) {
13675 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13677 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13681 strcat(string, number);
13684 strcat(string, "]");
13686 if (nCmailMovesRegistered + nCmailResults == 0) {
13687 switch (nCmailGames) {
13689 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13693 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13697 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13702 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13704 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13709 if (nCmailResults == nCmailGames) {
13710 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13712 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13717 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13729 if (gameMode == Training)
13730 SetTrainingModeOff();
13733 cmailMsgLoaded = FALSE;
13734 if (appData.icsActive) {
13735 SendToICS(ics_prefix);
13736 SendToICS("refresh\n");
13741 ExitEvent (int status)
13745 /* Give up on clean exit */
13749 /* Keep trying for clean exit */
13753 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13755 if (telnetISR != NULL) {
13756 RemoveInputSource(telnetISR);
13758 if (icsPR != NoProc) {
13759 DestroyChildProcess(icsPR, TRUE);
13762 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13763 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13765 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13766 /* make sure this other one finishes before killing it! */
13767 if(endingGame) { int count = 0;
13768 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13769 while(endingGame && count++ < 10) DoSleep(1);
13770 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13773 /* Kill off chess programs */
13774 if (first.pr != NoProc) {
13777 DoSleep( appData.delayBeforeQuit );
13778 SendToProgram("quit\n", &first);
13779 DoSleep( appData.delayAfterQuit );
13780 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13782 if (second.pr != NoProc) {
13783 DoSleep( appData.delayBeforeQuit );
13784 SendToProgram("quit\n", &second);
13785 DoSleep( appData.delayAfterQuit );
13786 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13788 if (first.isr != NULL) {
13789 RemoveInputSource(first.isr);
13791 if (second.isr != NULL) {
13792 RemoveInputSource(second.isr);
13795 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13796 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13798 ShutDownFrontEnd();
13803 PauseEngine (ChessProgramState *cps)
13805 SendToProgram("pause\n", cps);
13810 UnPauseEngine (ChessProgramState *cps)
13812 SendToProgram("resume\n", cps);
13819 if (appData.debugMode)
13820 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13824 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13826 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13827 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13828 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13830 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13831 HandleMachineMove(stashedInputMove, stalledEngine);
13832 stalledEngine = NULL;
13835 if (gameMode == MachinePlaysWhite ||
13836 gameMode == TwoMachinesPlay ||
13837 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13838 if(first.pause) UnPauseEngine(&first);
13839 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13840 if(second.pause) UnPauseEngine(&second);
13841 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13844 DisplayBothClocks();
13846 if (gameMode == PlayFromGameFile) {
13847 if (appData.timeDelay >= 0)
13848 AutoPlayGameLoop();
13849 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13850 Reset(FALSE, TRUE);
13851 SendToICS(ics_prefix);
13852 SendToICS("refresh\n");
13853 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13854 ForwardInner(forwardMostMove);
13856 pauseExamInvalid = FALSE;
13858 switch (gameMode) {
13862 pauseExamForwardMostMove = forwardMostMove;
13863 pauseExamInvalid = FALSE;
13866 case IcsPlayingWhite:
13867 case IcsPlayingBlack:
13871 case PlayFromGameFile:
13872 (void) StopLoadGameTimer();
13876 case BeginningOfGame:
13877 if (appData.icsActive) return;
13878 /* else fall through */
13879 case MachinePlaysWhite:
13880 case MachinePlaysBlack:
13881 case TwoMachinesPlay:
13882 if (forwardMostMove == 0)
13883 return; /* don't pause if no one has moved */
13884 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13885 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13886 if(onMove->pause) { // thinking engine can be paused
13887 PauseEngine(onMove); // do it
13888 if(onMove->other->pause) // pondering opponent can always be paused immediately
13889 PauseEngine(onMove->other);
13891 SendToProgram("easy\n", onMove->other);
13893 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13894 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13896 PauseEngine(&first);
13898 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13899 } else { // human on move, pause pondering by either method
13901 PauseEngine(&first);
13902 else if(appData.ponderNextMove)
13903 SendToProgram("easy\n", &first);
13906 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13916 EditCommentEvent ()
13918 char title[MSG_SIZ];
13920 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13921 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13923 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13924 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13925 parseList[currentMove - 1]);
13928 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13935 char *tags = PGNTags(&gameInfo);
13937 EditTagsPopUp(tags, NULL);
13944 if(second.analyzing) {
13945 SendToProgram("exit\n", &second);
13946 second.analyzing = FALSE;
13948 if (second.pr == NoProc) StartChessProgram(&second);
13949 InitChessProgram(&second, FALSE);
13950 FeedMovesToProgram(&second, currentMove);
13952 SendToProgram("analyze\n", &second);
13953 second.analyzing = TRUE;
13957 /* Toggle ShowThinking */
13959 ToggleShowThinking()
13961 appData.showThinking = !appData.showThinking;
13962 ShowThinkingEvent();
13966 AnalyzeModeEvent ()
13970 if (!first.analysisSupport) {
13971 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13972 DisplayError(buf, 0);
13975 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13976 if (appData.icsActive) {
13977 if (gameMode != IcsObserving) {
13978 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13979 DisplayError(buf, 0);
13981 if (appData.icsEngineAnalyze) {
13982 if (appData.debugMode)
13983 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13989 /* if enable, user wants to disable icsEngineAnalyze */
13990 if (appData.icsEngineAnalyze) {
13995 appData.icsEngineAnalyze = TRUE;
13996 if (appData.debugMode)
13997 fprintf(debugFP, "ICS engine analyze starting... \n");
14000 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14001 if (appData.noChessProgram || gameMode == AnalyzeMode)
14004 if (gameMode != AnalyzeFile) {
14005 if (!appData.icsEngineAnalyze) {
14007 if (gameMode != EditGame) return 0;
14009 if (!appData.showThinking) ToggleShowThinking();
14010 ResurrectChessProgram();
14011 SendToProgram("analyze\n", &first);
14012 first.analyzing = TRUE;
14013 /*first.maybeThinking = TRUE;*/
14014 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14015 EngineOutputPopUp();
14017 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14022 StartAnalysisClock();
14023 GetTimeMark(&lastNodeCountTime);
14029 AnalyzeFileEvent ()
14031 if (appData.noChessProgram || gameMode == AnalyzeFile)
14034 if (!first.analysisSupport) {
14036 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14037 DisplayError(buf, 0);
14041 if (gameMode != AnalyzeMode) {
14042 keepInfo = 1; // mere annotating should not alter PGN tags
14045 if (gameMode != EditGame) return;
14046 if (!appData.showThinking) ToggleShowThinking();
14047 ResurrectChessProgram();
14048 SendToProgram("analyze\n", &first);
14049 first.analyzing = TRUE;
14050 /*first.maybeThinking = TRUE;*/
14051 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14052 EngineOutputPopUp();
14054 gameMode = AnalyzeFile;
14058 StartAnalysisClock();
14059 GetTimeMark(&lastNodeCountTime);
14061 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14062 AnalysisPeriodicEvent(1);
14066 MachineWhiteEvent ()
14069 char *bookHit = NULL;
14071 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14075 if (gameMode == PlayFromGameFile ||
14076 gameMode == TwoMachinesPlay ||
14077 gameMode == Training ||
14078 gameMode == AnalyzeMode ||
14079 gameMode == EndOfGame)
14082 if (gameMode == EditPosition)
14083 EditPositionDone(TRUE);
14085 if (!WhiteOnMove(currentMove)) {
14086 DisplayError(_("It is not White's turn"), 0);
14090 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14093 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14094 gameMode == AnalyzeFile)
14097 ResurrectChessProgram(); /* in case it isn't running */
14098 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14099 gameMode = MachinePlaysWhite;
14102 gameMode = MachinePlaysWhite;
14106 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14108 if (first.sendName) {
14109 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14110 SendToProgram(buf, &first);
14112 if (first.sendTime) {
14113 if (first.useColors) {
14114 SendToProgram("black\n", &first); /*gnu kludge*/
14116 SendTimeRemaining(&first, TRUE);
14118 if (first.useColors) {
14119 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14121 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14122 SetMachineThinkingEnables();
14123 first.maybeThinking = TRUE;
14127 if (appData.autoFlipView && !flipView) {
14128 flipView = !flipView;
14129 DrawPosition(FALSE, NULL);
14130 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14133 if(bookHit) { // [HGM] book: simulate book reply
14134 static char bookMove[MSG_SIZ]; // a bit generous?
14136 programStats.nodes = programStats.depth = programStats.time =
14137 programStats.score = programStats.got_only_move = 0;
14138 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14140 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14141 strcat(bookMove, bookHit);
14142 HandleMachineMove(bookMove, &first);
14147 MachineBlackEvent ()
14150 char *bookHit = NULL;
14152 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14156 if (gameMode == PlayFromGameFile ||
14157 gameMode == TwoMachinesPlay ||
14158 gameMode == Training ||
14159 gameMode == AnalyzeMode ||
14160 gameMode == EndOfGame)
14163 if (gameMode == EditPosition)
14164 EditPositionDone(TRUE);
14166 if (WhiteOnMove(currentMove)) {
14167 DisplayError(_("It is not Black's turn"), 0);
14171 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14174 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14175 gameMode == AnalyzeFile)
14178 ResurrectChessProgram(); /* in case it isn't running */
14179 gameMode = MachinePlaysBlack;
14183 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14185 if (first.sendName) {
14186 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14187 SendToProgram(buf, &first);
14189 if (first.sendTime) {
14190 if (first.useColors) {
14191 SendToProgram("white\n", &first); /*gnu kludge*/
14193 SendTimeRemaining(&first, FALSE);
14195 if (first.useColors) {
14196 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14198 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14199 SetMachineThinkingEnables();
14200 first.maybeThinking = TRUE;
14203 if (appData.autoFlipView && flipView) {
14204 flipView = !flipView;
14205 DrawPosition(FALSE, NULL);
14206 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14208 if(bookHit) { // [HGM] book: simulate book reply
14209 static char bookMove[MSG_SIZ]; // a bit generous?
14211 programStats.nodes = programStats.depth = programStats.time =
14212 programStats.score = programStats.got_only_move = 0;
14213 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14215 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14216 strcat(bookMove, bookHit);
14217 HandleMachineMove(bookMove, &first);
14223 DisplayTwoMachinesTitle ()
14226 if (appData.matchGames > 0) {
14227 if(appData.tourneyFile[0]) {
14228 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14229 gameInfo.white, _("vs."), gameInfo.black,
14230 nextGame+1, appData.matchGames+1,
14231 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14233 if (first.twoMachinesColor[0] == 'w') {
14234 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14235 gameInfo.white, _("vs."), gameInfo.black,
14236 first.matchWins, second.matchWins,
14237 matchGame - 1 - (first.matchWins + second.matchWins));
14239 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14240 gameInfo.white, _("vs."), gameInfo.black,
14241 second.matchWins, first.matchWins,
14242 matchGame - 1 - (first.matchWins + second.matchWins));
14245 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14251 SettingsMenuIfReady ()
14253 if (second.lastPing != second.lastPong) {
14254 DisplayMessage("", _("Waiting for second chess program"));
14255 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14259 DisplayMessage("", "");
14260 SettingsPopUp(&second);
14264 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14267 if (cps->pr == NoProc) {
14268 StartChessProgram(cps);
14269 if (cps->protocolVersion == 1) {
14271 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14273 /* kludge: allow timeout for initial "feature" command */
14274 if(retry != TwoMachinesEventIfReady) FreezeUI();
14275 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14276 DisplayMessage("", buf);
14277 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14285 TwoMachinesEvent P((void))
14289 ChessProgramState *onmove;
14290 char *bookHit = NULL;
14291 static int stalling = 0;
14295 if (appData.noChessProgram) return;
14297 switch (gameMode) {
14298 case TwoMachinesPlay:
14300 case MachinePlaysWhite:
14301 case MachinePlaysBlack:
14302 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14303 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14307 case BeginningOfGame:
14308 case PlayFromGameFile:
14311 if (gameMode != EditGame) return;
14314 EditPositionDone(TRUE);
14325 // forwardMostMove = currentMove;
14326 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14327 startingEngine = TRUE;
14329 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14331 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14332 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14333 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14336 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14338 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14339 startingEngine = FALSE;
14340 DisplayError("second engine does not play this", 0);
14345 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14346 SendToProgram("force\n", &second);
14348 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14351 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14352 if(appData.matchPause>10000 || appData.matchPause<10)
14353 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14354 wait = SubtractTimeMarks(&now, &pauseStart);
14355 if(wait < appData.matchPause) {
14356 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14359 // we are now committed to starting the game
14361 DisplayMessage("", "");
14362 if (startedFromSetupPosition) {
14363 SendBoard(&second, backwardMostMove);
14364 if (appData.debugMode) {
14365 fprintf(debugFP, "Two Machines\n");
14368 for (i = backwardMostMove; i < forwardMostMove; i++) {
14369 SendMoveToProgram(i, &second);
14372 gameMode = TwoMachinesPlay;
14373 pausing = startingEngine = FALSE;
14374 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14376 DisplayTwoMachinesTitle();
14378 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14383 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14384 SendToProgram(first.computerString, &first);
14385 if (first.sendName) {
14386 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14387 SendToProgram(buf, &first);
14389 SendToProgram(second.computerString, &second);
14390 if (second.sendName) {
14391 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14392 SendToProgram(buf, &second);
14396 if (!first.sendTime || !second.sendTime) {
14397 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14398 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14400 if (onmove->sendTime) {
14401 if (onmove->useColors) {
14402 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14404 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14406 if (onmove->useColors) {
14407 SendToProgram(onmove->twoMachinesColor, onmove);
14409 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14410 // SendToProgram("go\n", onmove);
14411 onmove->maybeThinking = TRUE;
14412 SetMachineThinkingEnables();
14416 if(bookHit) { // [HGM] book: simulate book reply
14417 static char bookMove[MSG_SIZ]; // a bit generous?
14419 programStats.nodes = programStats.depth = programStats.time =
14420 programStats.score = programStats.got_only_move = 0;
14421 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14423 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14424 strcat(bookMove, bookHit);
14425 savedMessage = bookMove; // args for deferred call
14426 savedState = onmove;
14427 ScheduleDelayedEvent(DeferredBookMove, 1);
14434 if (gameMode == Training) {
14435 SetTrainingModeOff();
14436 gameMode = PlayFromGameFile;
14437 DisplayMessage("", _("Training mode off"));
14439 gameMode = Training;
14440 animateTraining = appData.animate;
14442 /* make sure we are not already at the end of the game */
14443 if (currentMove < forwardMostMove) {
14444 SetTrainingModeOn();
14445 DisplayMessage("", _("Training mode on"));
14447 gameMode = PlayFromGameFile;
14448 DisplayError(_("Already at end of game"), 0);
14457 if (!appData.icsActive) return;
14458 switch (gameMode) {
14459 case IcsPlayingWhite:
14460 case IcsPlayingBlack:
14463 case BeginningOfGame:
14471 EditPositionDone(TRUE);
14484 gameMode = IcsIdle;
14494 switch (gameMode) {
14496 SetTrainingModeOff();
14498 case MachinePlaysWhite:
14499 case MachinePlaysBlack:
14500 case BeginningOfGame:
14501 SendToProgram("force\n", &first);
14502 SetUserThinkingEnables();
14504 case PlayFromGameFile:
14505 (void) StopLoadGameTimer();
14506 if (gameFileFP != NULL) {
14511 EditPositionDone(TRUE);
14516 SendToProgram("force\n", &first);
14518 case TwoMachinesPlay:
14519 GameEnds(EndOfFile, NULL, GE_PLAYER);
14520 ResurrectChessProgram();
14521 SetUserThinkingEnables();
14524 ResurrectChessProgram();
14526 case IcsPlayingBlack:
14527 case IcsPlayingWhite:
14528 DisplayError(_("Warning: You are still playing a game"), 0);
14531 DisplayError(_("Warning: You are still observing a game"), 0);
14534 DisplayError(_("Warning: You are still examining a game"), 0);
14545 first.offeredDraw = second.offeredDraw = 0;
14547 if (gameMode == PlayFromGameFile) {
14548 whiteTimeRemaining = timeRemaining[0][currentMove];
14549 blackTimeRemaining = timeRemaining[1][currentMove];
14553 if (gameMode == MachinePlaysWhite ||
14554 gameMode == MachinePlaysBlack ||
14555 gameMode == TwoMachinesPlay ||
14556 gameMode == EndOfGame) {
14557 i = forwardMostMove;
14558 while (i > currentMove) {
14559 SendToProgram("undo\n", &first);
14562 if(!adjustedClock) {
14563 whiteTimeRemaining = timeRemaining[0][currentMove];
14564 blackTimeRemaining = timeRemaining[1][currentMove];
14565 DisplayBothClocks();
14567 if (whiteFlag || blackFlag) {
14568 whiteFlag = blackFlag = 0;
14573 gameMode = EditGame;
14580 EditPositionEvent ()
14582 if (gameMode == EditPosition) {
14588 if (gameMode != EditGame) return;
14590 gameMode = EditPosition;
14593 if (currentMove > 0)
14594 CopyBoard(boards[0], boards[currentMove]);
14596 blackPlaysFirst = !WhiteOnMove(currentMove);
14598 currentMove = forwardMostMove = backwardMostMove = 0;
14599 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14601 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14607 /* [DM] icsEngineAnalyze - possible call from other functions */
14608 if (appData.icsEngineAnalyze) {
14609 appData.icsEngineAnalyze = FALSE;
14611 DisplayMessage("",_("Close ICS engine analyze..."));
14613 if (first.analysisSupport && first.analyzing) {
14614 SendToBoth("exit\n");
14615 first.analyzing = second.analyzing = FALSE;
14617 thinkOutput[0] = NULLCHAR;
14621 EditPositionDone (Boolean fakeRights)
14623 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14625 startedFromSetupPosition = TRUE;
14626 InitChessProgram(&first, FALSE);
14627 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14628 boards[0][EP_STATUS] = EP_NONE;
14629 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14630 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14631 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14632 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14633 } else boards[0][CASTLING][2] = NoRights;
14634 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14635 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14636 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14637 } else boards[0][CASTLING][5] = NoRights;
14638 if(gameInfo.variant == VariantSChess) {
14640 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14641 boards[0][VIRGIN][i] = 0;
14642 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14643 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14647 SendToProgram("force\n", &first);
14648 if (blackPlaysFirst) {
14649 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14650 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14651 currentMove = forwardMostMove = backwardMostMove = 1;
14652 CopyBoard(boards[1], boards[0]);
14654 currentMove = forwardMostMove = backwardMostMove = 0;
14656 SendBoard(&first, forwardMostMove);
14657 if (appData.debugMode) {
14658 fprintf(debugFP, "EditPosDone\n");
14661 DisplayMessage("", "");
14662 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14663 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14664 gameMode = EditGame;
14666 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14667 ClearHighlights(); /* [AS] */
14670 /* Pause for `ms' milliseconds */
14671 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14673 TimeDelay (long ms)
14680 } while (SubtractTimeMarks(&m2, &m1) < ms);
14683 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14685 SendMultiLineToICS (char *buf)
14687 char temp[MSG_SIZ+1], *p;
14694 strncpy(temp, buf, len);
14699 if (*p == '\n' || *p == '\r')
14704 strcat(temp, "\n");
14706 SendToPlayer(temp, strlen(temp));
14710 SetWhiteToPlayEvent ()
14712 if (gameMode == EditPosition) {
14713 blackPlaysFirst = FALSE;
14714 DisplayBothClocks(); /* works because currentMove is 0 */
14715 } else if (gameMode == IcsExamining) {
14716 SendToICS(ics_prefix);
14717 SendToICS("tomove white\n");
14722 SetBlackToPlayEvent ()
14724 if (gameMode == EditPosition) {
14725 blackPlaysFirst = TRUE;
14726 currentMove = 1; /* kludge */
14727 DisplayBothClocks();
14729 } else if (gameMode == IcsExamining) {
14730 SendToICS(ics_prefix);
14731 SendToICS("tomove black\n");
14736 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14739 ChessSquare piece = boards[0][y][x];
14740 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14741 static int lastVariant;
14743 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14745 switch (selection) {
14747 CopyBoard(currentBoard, boards[0]);
14748 CopyBoard(menuBoard, initialPosition);
14749 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14750 SendToICS(ics_prefix);
14751 SendToICS("bsetup clear\n");
14752 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14753 SendToICS(ics_prefix);
14754 SendToICS("clearboard\n");
14757 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14758 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14759 for (y = 0; y < BOARD_HEIGHT; y++) {
14760 if (gameMode == IcsExamining) {
14761 if (boards[currentMove][y][x] != EmptySquare) {
14762 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14767 if(boards[0][y][x] != p) nonEmpty++;
14768 boards[0][y][x] = p;
14771 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14773 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14774 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
14775 ChessSquare p = menuBoard[0][x];
14776 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14777 p = menuBoard[BOARD_HEIGHT-1][x];
14778 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14780 DisplayMessage("Clicking clock again restores position", "");
14781 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14782 if(!nonEmpty) { // asked to clear an empty board
14783 CopyBoard(boards[0], menuBoard);
14785 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14786 CopyBoard(boards[0], initialPosition);
14788 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14789 && !CompareBoards(nullBoard, erasedBoard)) {
14790 CopyBoard(boards[0], erasedBoard);
14792 CopyBoard(erasedBoard, currentBoard);
14796 if (gameMode == EditPosition) {
14797 DrawPosition(FALSE, boards[0]);
14802 SetWhiteToPlayEvent();
14806 SetBlackToPlayEvent();
14810 if (gameMode == IcsExamining) {
14811 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14812 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14815 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14816 if(x == BOARD_LEFT-2) {
14817 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14818 boards[0][y][1] = 0;
14820 if(x == BOARD_RGHT+1) {
14821 if(y >= gameInfo.holdingsSize) break;
14822 boards[0][y][BOARD_WIDTH-2] = 0;
14825 boards[0][y][x] = EmptySquare;
14826 DrawPosition(FALSE, boards[0]);
14831 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14832 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14833 selection = (ChessSquare) (PROMOTED piece);
14834 } else if(piece == EmptySquare) selection = WhiteSilver;
14835 else selection = (ChessSquare)((int)piece - 1);
14839 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14840 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14841 selection = (ChessSquare) (DEMOTED piece);
14842 } else if(piece == EmptySquare) selection = BlackSilver;
14843 else selection = (ChessSquare)((int)piece + 1);
14848 if(gameInfo.variant == VariantShatranj ||
14849 gameInfo.variant == VariantXiangqi ||
14850 gameInfo.variant == VariantCourier ||
14851 gameInfo.variant == VariantASEAN ||
14852 gameInfo.variant == VariantMakruk )
14853 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14858 if(gameInfo.variant == VariantXiangqi)
14859 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14860 if(gameInfo.variant == VariantKnightmate)
14861 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14864 if (gameMode == IcsExamining) {
14865 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14866 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14867 PieceToChar(selection), AAA + x, ONE + y);
14870 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14872 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14873 n = PieceToNumber(selection - BlackPawn);
14874 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14875 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14876 boards[0][BOARD_HEIGHT-1-n][1]++;
14878 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14879 n = PieceToNumber(selection);
14880 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14881 boards[0][n][BOARD_WIDTH-1] = selection;
14882 boards[0][n][BOARD_WIDTH-2]++;
14885 boards[0][y][x] = selection;
14886 DrawPosition(TRUE, boards[0]);
14888 fromX = fromY = -1;
14896 DropMenuEvent (ChessSquare selection, int x, int y)
14898 ChessMove moveType;
14900 switch (gameMode) {
14901 case IcsPlayingWhite:
14902 case MachinePlaysBlack:
14903 if (!WhiteOnMove(currentMove)) {
14904 DisplayMoveError(_("It is Black's turn"));
14907 moveType = WhiteDrop;
14909 case IcsPlayingBlack:
14910 case MachinePlaysWhite:
14911 if (WhiteOnMove(currentMove)) {
14912 DisplayMoveError(_("It is White's turn"));
14915 moveType = BlackDrop;
14918 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14924 if (moveType == BlackDrop && selection < BlackPawn) {
14925 selection = (ChessSquare) ((int) selection
14926 + (int) BlackPawn - (int) WhitePawn);
14928 if (boards[currentMove][y][x] != EmptySquare) {
14929 DisplayMoveError(_("That square is occupied"));
14933 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14939 /* Accept a pending offer of any kind from opponent */
14941 if (appData.icsActive) {
14942 SendToICS(ics_prefix);
14943 SendToICS("accept\n");
14944 } else if (cmailMsgLoaded) {
14945 if (currentMove == cmailOldMove &&
14946 commentList[cmailOldMove] != NULL &&
14947 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14948 "Black offers a draw" : "White offers a draw")) {
14950 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14951 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14953 DisplayError(_("There is no pending offer on this move"), 0);
14954 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14957 /* Not used for offers from chess program */
14964 /* Decline a pending offer of any kind from opponent */
14966 if (appData.icsActive) {
14967 SendToICS(ics_prefix);
14968 SendToICS("decline\n");
14969 } else if (cmailMsgLoaded) {
14970 if (currentMove == cmailOldMove &&
14971 commentList[cmailOldMove] != NULL &&
14972 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14973 "Black offers a draw" : "White offers a draw")) {
14975 AppendComment(cmailOldMove, "Draw declined", TRUE);
14976 DisplayComment(cmailOldMove - 1, "Draw declined");
14979 DisplayError(_("There is no pending offer on this move"), 0);
14982 /* Not used for offers from chess program */
14989 /* Issue ICS rematch command */
14990 if (appData.icsActive) {
14991 SendToICS(ics_prefix);
14992 SendToICS("rematch\n");
14999 /* Call your opponent's flag (claim a win on time) */
15000 if (appData.icsActive) {
15001 SendToICS(ics_prefix);
15002 SendToICS("flag\n");
15004 switch (gameMode) {
15007 case MachinePlaysWhite:
15010 GameEnds(GameIsDrawn, "Both players ran out of time",
15013 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15015 DisplayError(_("Your opponent is not out of time"), 0);
15018 case MachinePlaysBlack:
15021 GameEnds(GameIsDrawn, "Both players ran out of time",
15024 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15026 DisplayError(_("Your opponent is not out of time"), 0);
15034 ClockClick (int which)
15035 { // [HGM] code moved to back-end from winboard.c
15036 if(which) { // black clock
15037 if (gameMode == EditPosition || gameMode == IcsExamining) {
15038 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15039 SetBlackToPlayEvent();
15040 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15041 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15042 } else if (shiftKey) {
15043 AdjustClock(which, -1);
15044 } else if (gameMode == IcsPlayingWhite ||
15045 gameMode == MachinePlaysBlack) {
15048 } else { // white clock
15049 if (gameMode == EditPosition || gameMode == IcsExamining) {
15050 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15051 SetWhiteToPlayEvent();
15052 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15053 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15054 } else if (shiftKey) {
15055 AdjustClock(which, -1);
15056 } else if (gameMode == IcsPlayingBlack ||
15057 gameMode == MachinePlaysWhite) {
15066 /* Offer draw or accept pending draw offer from opponent */
15068 if (appData.icsActive) {
15069 /* Note: tournament rules require draw offers to be
15070 made after you make your move but before you punch
15071 your clock. Currently ICS doesn't let you do that;
15072 instead, you immediately punch your clock after making
15073 a move, but you can offer a draw at any time. */
15075 SendToICS(ics_prefix);
15076 SendToICS("draw\n");
15077 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15078 } else if (cmailMsgLoaded) {
15079 if (currentMove == cmailOldMove &&
15080 commentList[cmailOldMove] != NULL &&
15081 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15082 "Black offers a draw" : "White offers a draw")) {
15083 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15084 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15085 } else if (currentMove == cmailOldMove + 1) {
15086 char *offer = WhiteOnMove(cmailOldMove) ?
15087 "White offers a draw" : "Black offers a draw";
15088 AppendComment(currentMove, offer, TRUE);
15089 DisplayComment(currentMove - 1, offer);
15090 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15092 DisplayError(_("You must make your move before offering a draw"), 0);
15093 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15095 } else if (first.offeredDraw) {
15096 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15098 if (first.sendDrawOffers) {
15099 SendToProgram("draw\n", &first);
15100 userOfferedDraw = TRUE;
15108 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15110 if (appData.icsActive) {
15111 SendToICS(ics_prefix);
15112 SendToICS("adjourn\n");
15114 /* Currently GNU Chess doesn't offer or accept Adjourns */
15122 /* Offer Abort or accept pending Abort offer from opponent */
15124 if (appData.icsActive) {
15125 SendToICS(ics_prefix);
15126 SendToICS("abort\n");
15128 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15135 /* Resign. You can do this even if it's not your turn. */
15137 if (appData.icsActive) {
15138 SendToICS(ics_prefix);
15139 SendToICS("resign\n");
15141 switch (gameMode) {
15142 case MachinePlaysWhite:
15143 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15145 case MachinePlaysBlack:
15146 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15149 if (cmailMsgLoaded) {
15151 if (WhiteOnMove(cmailOldMove)) {
15152 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15154 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15156 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15167 StopObservingEvent ()
15169 /* Stop observing current games */
15170 SendToICS(ics_prefix);
15171 SendToICS("unobserve\n");
15175 StopExaminingEvent ()
15177 /* Stop observing current game */
15178 SendToICS(ics_prefix);
15179 SendToICS("unexamine\n");
15183 ForwardInner (int target)
15185 int limit; int oldSeekGraphUp = seekGraphUp;
15187 if (appData.debugMode)
15188 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15189 target, currentMove, forwardMostMove);
15191 if (gameMode == EditPosition)
15194 seekGraphUp = FALSE;
15195 MarkTargetSquares(1);
15197 if (gameMode == PlayFromGameFile && !pausing)
15200 if (gameMode == IcsExamining && pausing)
15201 limit = pauseExamForwardMostMove;
15203 limit = forwardMostMove;
15205 if (target > limit) target = limit;
15207 if (target > 0 && moveList[target - 1][0]) {
15208 int fromX, fromY, toX, toY;
15209 toX = moveList[target - 1][2] - AAA;
15210 toY = moveList[target - 1][3] - ONE;
15211 if (moveList[target - 1][1] == '@') {
15212 if (appData.highlightLastMove) {
15213 SetHighlights(-1, -1, toX, toY);
15216 fromX = moveList[target - 1][0] - AAA;
15217 fromY = moveList[target - 1][1] - ONE;
15218 if (target == currentMove + 1) {
15219 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15221 if (appData.highlightLastMove) {
15222 SetHighlights(fromX, fromY, toX, toY);
15226 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15227 gameMode == Training || gameMode == PlayFromGameFile ||
15228 gameMode == AnalyzeFile) {
15229 while (currentMove < target) {
15230 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15231 SendMoveToProgram(currentMove++, &first);
15234 currentMove = target;
15237 if (gameMode == EditGame || gameMode == EndOfGame) {
15238 whiteTimeRemaining = timeRemaining[0][currentMove];
15239 blackTimeRemaining = timeRemaining[1][currentMove];
15241 DisplayBothClocks();
15242 DisplayMove(currentMove - 1);
15243 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15244 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15245 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15246 DisplayComment(currentMove - 1, commentList[currentMove]);
15248 ClearMap(); // [HGM] exclude: invalidate map
15255 if (gameMode == IcsExamining && !pausing) {
15256 SendToICS(ics_prefix);
15257 SendToICS("forward\n");
15259 ForwardInner(currentMove + 1);
15266 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15267 /* to optimze, we temporarily turn off analysis mode while we feed
15268 * the remaining moves to the engine. Otherwise we get analysis output
15271 if (first.analysisSupport) {
15272 SendToProgram("exit\nforce\n", &first);
15273 first.analyzing = FALSE;
15277 if (gameMode == IcsExamining && !pausing) {
15278 SendToICS(ics_prefix);
15279 SendToICS("forward 999999\n");
15281 ForwardInner(forwardMostMove);
15284 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15285 /* we have fed all the moves, so reactivate analysis mode */
15286 SendToProgram("analyze\n", &first);
15287 first.analyzing = TRUE;
15288 /*first.maybeThinking = TRUE;*/
15289 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15294 BackwardInner (int target)
15296 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15298 if (appData.debugMode)
15299 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15300 target, currentMove, forwardMostMove);
15302 if (gameMode == EditPosition) return;
15303 seekGraphUp = FALSE;
15304 MarkTargetSquares(1);
15305 if (currentMove <= backwardMostMove) {
15307 DrawPosition(full_redraw, boards[currentMove]);
15310 if (gameMode == PlayFromGameFile && !pausing)
15313 if (moveList[target][0]) {
15314 int fromX, fromY, toX, toY;
15315 toX = moveList[target][2] - AAA;
15316 toY = moveList[target][3] - ONE;
15317 if (moveList[target][1] == '@') {
15318 if (appData.highlightLastMove) {
15319 SetHighlights(-1, -1, toX, toY);
15322 fromX = moveList[target][0] - AAA;
15323 fromY = moveList[target][1] - ONE;
15324 if (target == currentMove - 1) {
15325 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15327 if (appData.highlightLastMove) {
15328 SetHighlights(fromX, fromY, toX, toY);
15332 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15333 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15334 while (currentMove > target) {
15335 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15336 // null move cannot be undone. Reload program with move history before it.
15338 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15339 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15341 SendBoard(&first, i);
15342 if(second.analyzing) SendBoard(&second, i);
15343 for(currentMove=i; currentMove<target; currentMove++) {
15344 SendMoveToProgram(currentMove, &first);
15345 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15349 SendToBoth("undo\n");
15353 currentMove = target;
15356 if (gameMode == EditGame || gameMode == EndOfGame) {
15357 whiteTimeRemaining = timeRemaining[0][currentMove];
15358 blackTimeRemaining = timeRemaining[1][currentMove];
15360 DisplayBothClocks();
15361 DisplayMove(currentMove - 1);
15362 DrawPosition(full_redraw, boards[currentMove]);
15363 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15364 // [HGM] PV info: routine tests if comment empty
15365 DisplayComment(currentMove - 1, commentList[currentMove]);
15366 ClearMap(); // [HGM] exclude: invalidate map
15372 if (gameMode == IcsExamining && !pausing) {
15373 SendToICS(ics_prefix);
15374 SendToICS("backward\n");
15376 BackwardInner(currentMove - 1);
15383 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15384 /* to optimize, we temporarily turn off analysis mode while we undo
15385 * all the moves. Otherwise we get analysis output after each undo.
15387 if (first.analysisSupport) {
15388 SendToProgram("exit\nforce\n", &first);
15389 first.analyzing = FALSE;
15393 if (gameMode == IcsExamining && !pausing) {
15394 SendToICS(ics_prefix);
15395 SendToICS("backward 999999\n");
15397 BackwardInner(backwardMostMove);
15400 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15401 /* we have fed all the moves, so reactivate analysis mode */
15402 SendToProgram("analyze\n", &first);
15403 first.analyzing = TRUE;
15404 /*first.maybeThinking = TRUE;*/
15405 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15412 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15413 if (to >= forwardMostMove) to = forwardMostMove;
15414 if (to <= backwardMostMove) to = backwardMostMove;
15415 if (to < currentMove) {
15423 RevertEvent (Boolean annotate)
15425 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15428 if (gameMode != IcsExamining) {
15429 DisplayError(_("You are not examining a game"), 0);
15433 DisplayError(_("You can't revert while pausing"), 0);
15436 SendToICS(ics_prefix);
15437 SendToICS("revert\n");
15441 RetractMoveEvent ()
15443 switch (gameMode) {
15444 case MachinePlaysWhite:
15445 case MachinePlaysBlack:
15446 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15447 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15450 if (forwardMostMove < 2) return;
15451 currentMove = forwardMostMove = forwardMostMove - 2;
15452 whiteTimeRemaining = timeRemaining[0][currentMove];
15453 blackTimeRemaining = timeRemaining[1][currentMove];
15454 DisplayBothClocks();
15455 DisplayMove(currentMove - 1);
15456 ClearHighlights();/*!! could figure this out*/
15457 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15458 SendToProgram("remove\n", &first);
15459 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15462 case BeginningOfGame:
15466 case IcsPlayingWhite:
15467 case IcsPlayingBlack:
15468 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15469 SendToICS(ics_prefix);
15470 SendToICS("takeback 2\n");
15472 SendToICS(ics_prefix);
15473 SendToICS("takeback 1\n");
15482 ChessProgramState *cps;
15484 switch (gameMode) {
15485 case MachinePlaysWhite:
15486 if (!WhiteOnMove(forwardMostMove)) {
15487 DisplayError(_("It is your turn"), 0);
15492 case MachinePlaysBlack:
15493 if (WhiteOnMove(forwardMostMove)) {
15494 DisplayError(_("It is your turn"), 0);
15499 case TwoMachinesPlay:
15500 if (WhiteOnMove(forwardMostMove) ==
15501 (first.twoMachinesColor[0] == 'w')) {
15507 case BeginningOfGame:
15511 SendToProgram("?\n", cps);
15515 TruncateGameEvent ()
15518 if (gameMode != EditGame) return;
15525 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15526 if (forwardMostMove > currentMove) {
15527 if (gameInfo.resultDetails != NULL) {
15528 free(gameInfo.resultDetails);
15529 gameInfo.resultDetails = NULL;
15530 gameInfo.result = GameUnfinished;
15532 forwardMostMove = currentMove;
15533 HistorySet(parseList, backwardMostMove, forwardMostMove,
15541 if (appData.noChessProgram) return;
15542 switch (gameMode) {
15543 case MachinePlaysWhite:
15544 if (WhiteOnMove(forwardMostMove)) {
15545 DisplayError(_("Wait until your turn."), 0);
15549 case BeginningOfGame:
15550 case MachinePlaysBlack:
15551 if (!WhiteOnMove(forwardMostMove)) {
15552 DisplayError(_("Wait until your turn."), 0);
15557 DisplayError(_("No hint available"), 0);
15560 SendToProgram("hint\n", &first);
15561 hintRequested = TRUE;
15567 ListGame * lg = (ListGame *) gameList.head;
15570 static int secondTime = FALSE;
15572 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15573 DisplayError(_("Game list not loaded or empty"), 0);
15577 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15580 DisplayNote(_("Book file exists! Try again for overwrite."));
15584 creatingBook = TRUE;
15585 secondTime = FALSE;
15587 /* Get list size */
15588 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15589 LoadGame(f, nItem, "", TRUE);
15590 AddGameToBook(TRUE);
15591 lg = (ListGame *) lg->node.succ;
15594 creatingBook = FALSE;
15601 if (appData.noChessProgram) return;
15602 switch (gameMode) {
15603 case MachinePlaysWhite:
15604 if (WhiteOnMove(forwardMostMove)) {
15605 DisplayError(_("Wait until your turn."), 0);
15609 case BeginningOfGame:
15610 case MachinePlaysBlack:
15611 if (!WhiteOnMove(forwardMostMove)) {
15612 DisplayError(_("Wait until your turn."), 0);
15617 EditPositionDone(TRUE);
15619 case TwoMachinesPlay:
15624 SendToProgram("bk\n", &first);
15625 bookOutput[0] = NULLCHAR;
15626 bookRequested = TRUE;
15632 char *tags = PGNTags(&gameInfo);
15633 TagsPopUp(tags, CmailMsg());
15637 /* end button procedures */
15640 PrintPosition (FILE *fp, int move)
15644 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15645 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15646 char c = PieceToChar(boards[move][i][j]);
15647 fputc(c == 'x' ? '.' : c, fp);
15648 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15651 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15652 fprintf(fp, "white to play\n");
15654 fprintf(fp, "black to play\n");
15658 PrintOpponents (FILE *fp)
15660 if (gameInfo.white != NULL) {
15661 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15667 /* Find last component of program's own name, using some heuristics */
15669 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15672 int local = (strcmp(host, "localhost") == 0);
15673 while (!local && (p = strchr(prog, ';')) != NULL) {
15675 while (*p == ' ') p++;
15678 if (*prog == '"' || *prog == '\'') {
15679 q = strchr(prog + 1, *prog);
15681 q = strchr(prog, ' ');
15683 if (q == NULL) q = prog + strlen(prog);
15685 while (p >= prog && *p != '/' && *p != '\\') p--;
15687 if(p == prog && *p == '"') p++;
15689 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15690 memcpy(buf, p, q - p);
15691 buf[q - p] = NULLCHAR;
15699 TimeControlTagValue ()
15702 if (!appData.clockMode) {
15703 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15704 } else if (movesPerSession > 0) {
15705 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15706 } else if (timeIncrement == 0) {
15707 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15709 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15711 return StrSave(buf);
15717 /* This routine is used only for certain modes */
15718 VariantClass v = gameInfo.variant;
15719 ChessMove r = GameUnfinished;
15722 if(keepInfo) return;
15724 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15725 r = gameInfo.result;
15726 p = gameInfo.resultDetails;
15727 gameInfo.resultDetails = NULL;
15729 ClearGameInfo(&gameInfo);
15730 gameInfo.variant = v;
15732 switch (gameMode) {
15733 case MachinePlaysWhite:
15734 gameInfo.event = StrSave( appData.pgnEventHeader );
15735 gameInfo.site = StrSave(HostName());
15736 gameInfo.date = PGNDate();
15737 gameInfo.round = StrSave("-");
15738 gameInfo.white = StrSave(first.tidy);
15739 gameInfo.black = StrSave(UserName());
15740 gameInfo.timeControl = TimeControlTagValue();
15743 case MachinePlaysBlack:
15744 gameInfo.event = StrSave( appData.pgnEventHeader );
15745 gameInfo.site = StrSave(HostName());
15746 gameInfo.date = PGNDate();
15747 gameInfo.round = StrSave("-");
15748 gameInfo.white = StrSave(UserName());
15749 gameInfo.black = StrSave(first.tidy);
15750 gameInfo.timeControl = TimeControlTagValue();
15753 case TwoMachinesPlay:
15754 gameInfo.event = StrSave( appData.pgnEventHeader );
15755 gameInfo.site = StrSave(HostName());
15756 gameInfo.date = PGNDate();
15759 snprintf(buf, MSG_SIZ, "%d", roundNr);
15760 gameInfo.round = StrSave(buf);
15762 gameInfo.round = StrSave("-");
15764 if (first.twoMachinesColor[0] == 'w') {
15765 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15766 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15768 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15769 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15771 gameInfo.timeControl = TimeControlTagValue();
15775 gameInfo.event = StrSave("Edited game");
15776 gameInfo.site = StrSave(HostName());
15777 gameInfo.date = PGNDate();
15778 gameInfo.round = StrSave("-");
15779 gameInfo.white = StrSave("-");
15780 gameInfo.black = StrSave("-");
15781 gameInfo.result = r;
15782 gameInfo.resultDetails = p;
15786 gameInfo.event = StrSave("Edited position");
15787 gameInfo.site = StrSave(HostName());
15788 gameInfo.date = PGNDate();
15789 gameInfo.round = StrSave("-");
15790 gameInfo.white = StrSave("-");
15791 gameInfo.black = StrSave("-");
15794 case IcsPlayingWhite:
15795 case IcsPlayingBlack:
15800 case PlayFromGameFile:
15801 gameInfo.event = StrSave("Game from non-PGN file");
15802 gameInfo.site = StrSave(HostName());
15803 gameInfo.date = PGNDate();
15804 gameInfo.round = StrSave("-");
15805 gameInfo.white = StrSave("?");
15806 gameInfo.black = StrSave("?");
15815 ReplaceComment (int index, char *text)
15821 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15822 pvInfoList[index-1].depth == len &&
15823 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15824 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15825 while (*text == '\n') text++;
15826 len = strlen(text);
15827 while (len > 0 && text[len - 1] == '\n') len--;
15829 if (commentList[index] != NULL)
15830 free(commentList[index]);
15833 commentList[index] = NULL;
15836 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15837 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15838 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15839 commentList[index] = (char *) malloc(len + 2);
15840 strncpy(commentList[index], text, len);
15841 commentList[index][len] = '\n';
15842 commentList[index][len + 1] = NULLCHAR;
15844 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15846 commentList[index] = (char *) malloc(len + 7);
15847 safeStrCpy(commentList[index], "{\n", 3);
15848 safeStrCpy(commentList[index]+2, text, len+1);
15849 commentList[index][len+2] = NULLCHAR;
15850 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15851 strcat(commentList[index], "\n}\n");
15856 CrushCRs (char *text)
15864 if (ch == '\r') continue;
15866 } while (ch != '\0');
15870 AppendComment (int index, char *text, Boolean addBraces)
15871 /* addBraces tells if we should add {} */
15876 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15877 if(addBraces == 3) addBraces = 0; else // force appending literally
15878 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15881 while (*text == '\n') text++;
15882 len = strlen(text);
15883 while (len > 0 && text[len - 1] == '\n') len--;
15884 text[len] = NULLCHAR;
15886 if (len == 0) return;
15888 if (commentList[index] != NULL) {
15889 Boolean addClosingBrace = addBraces;
15890 old = commentList[index];
15891 oldlen = strlen(old);
15892 while(commentList[index][oldlen-1] == '\n')
15893 commentList[index][--oldlen] = NULLCHAR;
15894 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15895 safeStrCpy(commentList[index], old, oldlen + len + 6);
15897 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15898 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15899 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15900 while (*text == '\n') { text++; len--; }
15901 commentList[index][--oldlen] = NULLCHAR;
15903 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15904 else strcat(commentList[index], "\n");
15905 strcat(commentList[index], text);
15906 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15907 else strcat(commentList[index], "\n");
15909 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15911 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15912 else commentList[index][0] = NULLCHAR;
15913 strcat(commentList[index], text);
15914 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15915 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15920 FindStr (char * text, char * sub_text)
15922 char * result = strstr( text, sub_text );
15924 if( result != NULL ) {
15925 result += strlen( sub_text );
15931 /* [AS] Try to extract PV info from PGN comment */
15932 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15934 GetInfoFromComment (int index, char * text)
15936 char * sep = text, *p;
15938 if( text != NULL && index > 0 ) {
15941 int time = -1, sec = 0, deci;
15942 char * s_eval = FindStr( text, "[%eval " );
15943 char * s_emt = FindStr( text, "[%emt " );
15945 if( s_eval != NULL || s_emt != NULL ) {
15947 if(0) { // [HGM] this code is not finished, and could actually be detrimental
15952 if( s_eval != NULL ) {
15953 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15957 if( delim != ']' ) {
15962 if( s_emt != NULL ) {
15967 /* We expect something like: [+|-]nnn.nn/dd */
15970 if(*text != '{') return text; // [HGM] braces: must be normal comment
15972 sep = strchr( text, '/' );
15973 if( sep == NULL || sep < (text+4) ) {
15978 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15979 if(p[1] == '(') { // comment starts with PV
15980 p = strchr(p, ')'); // locate end of PV
15981 if(p == NULL || sep < p+5) return text;
15982 // at this point we have something like "{(.*) +0.23/6 ..."
15983 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15984 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15985 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15987 time = -1; sec = -1; deci = -1;
15988 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15989 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15990 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15991 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15995 if( score_lo < 0 || score_lo >= 100 ) {
15999 if(sec >= 0) time = 600*time + 10*sec; else
16000 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16002 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16004 /* [HGM] PV time: now locate end of PV info */
16005 while( *++sep >= '0' && *sep <= '9'); // strip depth
16007 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16009 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16011 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16012 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16023 pvInfoList[index-1].depth = depth;
16024 pvInfoList[index-1].score = score;
16025 pvInfoList[index-1].time = 10*time; // centi-sec
16026 if(*sep == '}') *sep = 0; else *--sep = '{';
16027 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16033 SendToProgram (char *message, ChessProgramState *cps)
16035 int count, outCount, error;
16038 if (cps->pr == NoProc) return;
16041 if (appData.debugMode) {
16044 fprintf(debugFP, "%ld >%-6s: %s",
16045 SubtractTimeMarks(&now, &programStartTime),
16046 cps->which, message);
16048 fprintf(serverFP, "%ld >%-6s: %s",
16049 SubtractTimeMarks(&now, &programStartTime),
16050 cps->which, message), fflush(serverFP);
16053 count = strlen(message);
16054 outCount = OutputToProcess(cps->pr, message, count, &error);
16055 if (outCount < count && !exiting
16056 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16057 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16058 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16059 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16060 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16061 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16062 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16063 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16065 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16066 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16067 gameInfo.result = res;
16069 gameInfo.resultDetails = StrSave(buf);
16071 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16072 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16077 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16081 ChessProgramState *cps = (ChessProgramState *)closure;
16083 if (isr != cps->isr) return; /* Killed intentionally */
16086 RemoveInputSource(cps->isr);
16087 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16088 _(cps->which), cps->program);
16089 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16090 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16091 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16092 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16093 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16094 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16096 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16097 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16098 gameInfo.result = res;
16100 gameInfo.resultDetails = StrSave(buf);
16102 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16103 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16105 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16106 _(cps->which), cps->program);
16107 RemoveInputSource(cps->isr);
16109 /* [AS] Program is misbehaving badly... kill it */
16110 if( count == -2 ) {
16111 DestroyChildProcess( cps->pr, 9 );
16115 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16120 if ((end_str = strchr(message, '\r')) != NULL)
16121 *end_str = NULLCHAR;
16122 if ((end_str = strchr(message, '\n')) != NULL)
16123 *end_str = NULLCHAR;
16125 if (appData.debugMode) {
16126 TimeMark now; int print = 1;
16127 char *quote = ""; char c; int i;
16129 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16130 char start = message[0];
16131 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16132 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16133 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16134 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16135 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16136 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16137 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16138 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16139 sscanf(message, "hint: %c", &c)!=1 &&
16140 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16141 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16142 print = (appData.engineComments >= 2);
16144 message[0] = start; // restore original message
16148 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16149 SubtractTimeMarks(&now, &programStartTime), cps->which,
16153 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16154 SubtractTimeMarks(&now, &programStartTime), cps->which,
16156 message), fflush(serverFP);
16160 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16161 if (appData.icsEngineAnalyze) {
16162 if (strstr(message, "whisper") != NULL ||
16163 strstr(message, "kibitz") != NULL ||
16164 strstr(message, "tellics") != NULL) return;
16167 HandleMachineMove(message, cps);
16172 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16177 if( timeControl_2 > 0 ) {
16178 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16179 tc = timeControl_2;
16182 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16183 inc /= cps->timeOdds;
16184 st /= cps->timeOdds;
16186 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16189 /* Set exact time per move, normally using st command */
16190 if (cps->stKludge) {
16191 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16193 if (seconds == 0) {
16194 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16196 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16199 snprintf(buf, MSG_SIZ, "st %d\n", st);
16202 /* Set conventional or incremental time control, using level command */
16203 if (seconds == 0) {
16204 /* Note old gnuchess bug -- minutes:seconds used to not work.
16205 Fixed in later versions, but still avoid :seconds
16206 when seconds is 0. */
16207 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16209 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16210 seconds, inc/1000.);
16213 SendToProgram(buf, cps);
16215 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16216 /* Orthogonally, limit search to given depth */
16218 if (cps->sdKludge) {
16219 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16221 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16223 SendToProgram(buf, cps);
16226 if(cps->nps >= 0) { /* [HGM] nps */
16227 if(cps->supportsNPS == FALSE)
16228 cps->nps = -1; // don't use if engine explicitly says not supported!
16230 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16231 SendToProgram(buf, cps);
16236 ChessProgramState *
16238 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16240 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16241 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16247 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16249 char message[MSG_SIZ];
16252 /* Note: this routine must be called when the clocks are stopped
16253 or when they have *just* been set or switched; otherwise
16254 it will be off by the time since the current tick started.
16256 if (machineWhite) {
16257 time = whiteTimeRemaining / 10;
16258 otime = blackTimeRemaining / 10;
16260 time = blackTimeRemaining / 10;
16261 otime = whiteTimeRemaining / 10;
16263 /* [HGM] translate opponent's time by time-odds factor */
16264 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16266 if (time <= 0) time = 1;
16267 if (otime <= 0) otime = 1;
16269 snprintf(message, MSG_SIZ, "time %ld\n", time);
16270 SendToProgram(message, cps);
16272 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16273 SendToProgram(message, cps);
16277 EngineDefinedVariant (ChessProgramState *cps, int n)
16278 { // return name of n-th unknown variant that engine supports
16279 static char buf[MSG_SIZ];
16280 char *p, *s = cps->variants;
16281 if(!s) return NULL;
16282 do { // parse string from variants feature
16284 p = strchr(s, ',');
16285 if(p) *p = NULLCHAR;
16286 v = StringToVariant(s);
16287 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16288 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16289 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16292 if(n < 0) return buf;
16298 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16301 int len = strlen(name);
16304 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16306 sscanf(*p, "%d", &val);
16308 while (**p && **p != ' ')
16310 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16311 SendToProgram(buf, cps);
16318 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16321 int len = strlen(name);
16322 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16324 sscanf(*p, "%d", loc);
16325 while (**p && **p != ' ') (*p)++;
16326 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16327 SendToProgram(buf, cps);
16334 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16337 int len = strlen(name);
16338 if (strncmp((*p), name, len) == 0
16339 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16341 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16342 sscanf(*p, "%[^\"]", *loc);
16343 while (**p && **p != '\"') (*p)++;
16344 if (**p == '\"') (*p)++;
16345 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16346 SendToProgram(buf, cps);
16353 ParseOption (Option *opt, ChessProgramState *cps)
16354 // [HGM] options: process the string that defines an engine option, and determine
16355 // name, type, default value, and allowed value range
16357 char *p, *q, buf[MSG_SIZ];
16358 int n, min = (-1)<<31, max = 1<<31, def;
16360 if(p = strstr(opt->name, " -spin ")) {
16361 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16362 if(max < min) max = min; // enforce consistency
16363 if(def < min) def = min;
16364 if(def > max) def = max;
16369 } else if((p = strstr(opt->name, " -slider "))) {
16370 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16371 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16372 if(max < min) max = min; // enforce consistency
16373 if(def < min) def = min;
16374 if(def > max) def = max;
16378 opt->type = Spin; // Slider;
16379 } else if((p = strstr(opt->name, " -string "))) {
16380 opt->textValue = p+9;
16381 opt->type = TextBox;
16382 } else if((p = strstr(opt->name, " -file "))) {
16383 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16384 opt->textValue = p+7;
16385 opt->type = FileName; // FileName;
16386 } else if((p = strstr(opt->name, " -path "))) {
16387 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16388 opt->textValue = p+7;
16389 opt->type = PathName; // PathName;
16390 } else if(p = strstr(opt->name, " -check ")) {
16391 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16392 opt->value = (def != 0);
16393 opt->type = CheckBox;
16394 } else if(p = strstr(opt->name, " -combo ")) {
16395 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16396 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16397 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16398 opt->value = n = 0;
16399 while(q = StrStr(q, " /// ")) {
16400 n++; *q = 0; // count choices, and null-terminate each of them
16402 if(*q == '*') { // remember default, which is marked with * prefix
16406 cps->comboList[cps->comboCnt++] = q;
16408 cps->comboList[cps->comboCnt++] = NULL;
16410 opt->type = ComboBox;
16411 } else if(p = strstr(opt->name, " -button")) {
16412 opt->type = Button;
16413 } else if(p = strstr(opt->name, " -save")) {
16414 opt->type = SaveButton;
16415 } else return FALSE;
16416 *p = 0; // terminate option name
16417 // now look if the command-line options define a setting for this engine option.
16418 if(cps->optionSettings && cps->optionSettings[0])
16419 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16420 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16421 snprintf(buf, MSG_SIZ, "option %s", p);
16422 if(p = strstr(buf, ",")) *p = 0;
16423 if(q = strchr(buf, '=')) switch(opt->type) {
16425 for(n=0; n<opt->max; n++)
16426 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16429 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16433 opt->value = atoi(q+1);
16438 SendToProgram(buf, cps);
16444 FeatureDone (ChessProgramState *cps, int val)
16446 DelayedEventCallback cb = GetDelayedEvent();
16447 if ((cb == InitBackEnd3 && cps == &first) ||
16448 (cb == SettingsMenuIfReady && cps == &second) ||
16449 (cb == LoadEngine) ||
16450 (cb == TwoMachinesEventIfReady)) {
16451 CancelDelayedEvent();
16452 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16454 cps->initDone = val;
16455 if(val) cps->reload = FALSE;
16458 /* Parse feature command from engine */
16460 ParseFeatures (char *args, ChessProgramState *cps)
16468 while (*p == ' ') p++;
16469 if (*p == NULLCHAR) return;
16471 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16472 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16473 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16474 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16475 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16476 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16477 if (BoolFeature(&p, "reuse", &val, cps)) {
16478 /* Engine can disable reuse, but can't enable it if user said no */
16479 if (!val) cps->reuse = FALSE;
16482 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16483 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16484 if (gameMode == TwoMachinesPlay) {
16485 DisplayTwoMachinesTitle();
16491 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16492 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16493 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16494 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16495 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16496 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16497 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16498 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16499 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16500 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16501 if (IntFeature(&p, "done", &val, cps)) {
16502 FeatureDone(cps, val);
16505 /* Added by Tord: */
16506 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16507 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16508 /* End of additions by Tord */
16510 /* [HGM] added features: */
16511 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16512 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16513 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16514 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16515 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16516 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16517 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16518 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16519 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16520 FREE(cps->option[cps->nrOptions].name);
16521 cps->option[cps->nrOptions].name = q; q = NULL;
16522 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16523 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16524 SendToProgram(buf, cps);
16527 if(cps->nrOptions >= MAX_OPTIONS) {
16529 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16530 DisplayError(buf, 0);
16534 /* End of additions by HGM */
16536 /* unknown feature: complain and skip */
16538 while (*q && *q != '=') q++;
16539 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16540 SendToProgram(buf, cps);
16546 while (*p && *p != '\"') p++;
16547 if (*p == '\"') p++;
16549 while (*p && *p != ' ') p++;
16557 PeriodicUpdatesEvent (int newState)
16559 if (newState == appData.periodicUpdates)
16562 appData.periodicUpdates=newState;
16564 /* Display type changes, so update it now */
16565 // DisplayAnalysis();
16567 /* Get the ball rolling again... */
16569 AnalysisPeriodicEvent(1);
16570 StartAnalysisClock();
16575 PonderNextMoveEvent (int newState)
16577 if (newState == appData.ponderNextMove) return;
16578 if (gameMode == EditPosition) EditPositionDone(TRUE);
16580 SendToProgram("hard\n", &first);
16581 if (gameMode == TwoMachinesPlay) {
16582 SendToProgram("hard\n", &second);
16585 SendToProgram("easy\n", &first);
16586 thinkOutput[0] = NULLCHAR;
16587 if (gameMode == TwoMachinesPlay) {
16588 SendToProgram("easy\n", &second);
16591 appData.ponderNextMove = newState;
16595 NewSettingEvent (int option, int *feature, char *command, int value)
16599 if (gameMode == EditPosition) EditPositionDone(TRUE);
16600 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16601 if(feature == NULL || *feature) SendToProgram(buf, &first);
16602 if (gameMode == TwoMachinesPlay) {
16603 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16608 ShowThinkingEvent ()
16609 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16611 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16612 int newState = appData.showThinking
16613 // [HGM] thinking: other features now need thinking output as well
16614 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16616 if (oldState == newState) return;
16617 oldState = newState;
16618 if (gameMode == EditPosition) EditPositionDone(TRUE);
16620 SendToProgram("post\n", &first);
16621 if (gameMode == TwoMachinesPlay) {
16622 SendToProgram("post\n", &second);
16625 SendToProgram("nopost\n", &first);
16626 thinkOutput[0] = NULLCHAR;
16627 if (gameMode == TwoMachinesPlay) {
16628 SendToProgram("nopost\n", &second);
16631 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16635 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16637 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16638 if (pr == NoProc) return;
16639 AskQuestion(title, question, replyPrefix, pr);
16643 TypeInEvent (char firstChar)
16645 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16646 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16647 gameMode == AnalyzeMode || gameMode == EditGame ||
16648 gameMode == EditPosition || gameMode == IcsExamining ||
16649 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16650 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16651 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16652 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16653 gameMode == Training) PopUpMoveDialog(firstChar);
16657 TypeInDoneEvent (char *move)
16660 int n, fromX, fromY, toX, toY;
16662 ChessMove moveType;
16665 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16666 EditPositionPasteFEN(move);
16669 // [HGM] movenum: allow move number to be typed in any mode
16670 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16674 // undocumented kludge: allow command-line option to be typed in!
16675 // (potentially fatal, and does not implement the effect of the option.)
16676 // should only be used for options that are values on which future decisions will be made,
16677 // and definitely not on options that would be used during initialization.
16678 if(strstr(move, "!!! -") == move) {
16679 ParseArgsFromString(move+4);
16683 if (gameMode != EditGame && currentMove != forwardMostMove &&
16684 gameMode != Training) {
16685 DisplayMoveError(_("Displayed move is not current"));
16687 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16688 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16689 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16690 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16691 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16692 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16694 DisplayMoveError(_("Could not parse move"));
16700 DisplayMove (int moveNumber)
16702 char message[MSG_SIZ];
16704 char cpThinkOutput[MSG_SIZ];
16706 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16708 if (moveNumber == forwardMostMove - 1 ||
16709 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16711 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16713 if (strchr(cpThinkOutput, '\n')) {
16714 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16717 *cpThinkOutput = NULLCHAR;
16720 /* [AS] Hide thinking from human user */
16721 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16722 *cpThinkOutput = NULLCHAR;
16723 if( thinkOutput[0] != NULLCHAR ) {
16726 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16727 cpThinkOutput[i] = '.';
16729 cpThinkOutput[i] = NULLCHAR;
16730 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16734 if (moveNumber == forwardMostMove - 1 &&
16735 gameInfo.resultDetails != NULL) {
16736 if (gameInfo.resultDetails[0] == NULLCHAR) {
16737 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16739 snprintf(res, MSG_SIZ, " {%s} %s",
16740 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16746 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16747 DisplayMessage(res, cpThinkOutput);
16749 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16750 WhiteOnMove(moveNumber) ? " " : ".. ",
16751 parseList[moveNumber], res);
16752 DisplayMessage(message, cpThinkOutput);
16757 DisplayComment (int moveNumber, char *text)
16759 char title[MSG_SIZ];
16761 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16762 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16764 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16765 WhiteOnMove(moveNumber) ? " " : ".. ",
16766 parseList[moveNumber]);
16768 if (text != NULL && (appData.autoDisplayComment || commentUp))
16769 CommentPopUp(title, text);
16772 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16773 * might be busy thinking or pondering. It can be omitted if your
16774 * gnuchess is configured to stop thinking immediately on any user
16775 * input. However, that gnuchess feature depends on the FIONREAD
16776 * ioctl, which does not work properly on some flavors of Unix.
16779 Attention (ChessProgramState *cps)
16782 if (!cps->useSigint) return;
16783 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16784 switch (gameMode) {
16785 case MachinePlaysWhite:
16786 case MachinePlaysBlack:
16787 case TwoMachinesPlay:
16788 case IcsPlayingWhite:
16789 case IcsPlayingBlack:
16792 /* Skip if we know it isn't thinking */
16793 if (!cps->maybeThinking) return;
16794 if (appData.debugMode)
16795 fprintf(debugFP, "Interrupting %s\n", cps->which);
16796 InterruptChildProcess(cps->pr);
16797 cps->maybeThinking = FALSE;
16802 #endif /*ATTENTION*/
16808 if (whiteTimeRemaining <= 0) {
16811 if (appData.icsActive) {
16812 if (appData.autoCallFlag &&
16813 gameMode == IcsPlayingBlack && !blackFlag) {
16814 SendToICS(ics_prefix);
16815 SendToICS("flag\n");
16819 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16821 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16822 if (appData.autoCallFlag) {
16823 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16830 if (blackTimeRemaining <= 0) {
16833 if (appData.icsActive) {
16834 if (appData.autoCallFlag &&
16835 gameMode == IcsPlayingWhite && !whiteFlag) {
16836 SendToICS(ics_prefix);
16837 SendToICS("flag\n");
16841 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16843 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16844 if (appData.autoCallFlag) {
16845 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16856 CheckTimeControl ()
16858 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16859 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16862 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16864 if ( !WhiteOnMove(forwardMostMove) ) {
16865 /* White made time control */
16866 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16867 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16868 /* [HGM] time odds: correct new time quota for time odds! */
16869 / WhitePlayer()->timeOdds;
16870 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16872 lastBlack -= blackTimeRemaining;
16873 /* Black made time control */
16874 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16875 / WhitePlayer()->other->timeOdds;
16876 lastWhite = whiteTimeRemaining;
16881 DisplayBothClocks ()
16883 int wom = gameMode == EditPosition ?
16884 !blackPlaysFirst : WhiteOnMove(currentMove);
16885 DisplayWhiteClock(whiteTimeRemaining, wom);
16886 DisplayBlackClock(blackTimeRemaining, !wom);
16890 /* Timekeeping seems to be a portability nightmare. I think everyone
16891 has ftime(), but I'm really not sure, so I'm including some ifdefs
16892 to use other calls if you don't. Clocks will be less accurate if
16893 you have neither ftime nor gettimeofday.
16896 /* VS 2008 requires the #include outside of the function */
16897 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16898 #include <sys/timeb.h>
16901 /* Get the current time as a TimeMark */
16903 GetTimeMark (TimeMark *tm)
16905 #if HAVE_GETTIMEOFDAY
16907 struct timeval timeVal;
16908 struct timezone timeZone;
16910 gettimeofday(&timeVal, &timeZone);
16911 tm->sec = (long) timeVal.tv_sec;
16912 tm->ms = (int) (timeVal.tv_usec / 1000L);
16914 #else /*!HAVE_GETTIMEOFDAY*/
16917 // include <sys/timeb.h> / moved to just above start of function
16918 struct timeb timeB;
16921 tm->sec = (long) timeB.time;
16922 tm->ms = (int) timeB.millitm;
16924 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16925 tm->sec = (long) time(NULL);
16931 /* Return the difference in milliseconds between two
16932 time marks. We assume the difference will fit in a long!
16935 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16937 return 1000L*(tm2->sec - tm1->sec) +
16938 (long) (tm2->ms - tm1->ms);
16943 * Code to manage the game clocks.
16945 * In tournament play, black starts the clock and then white makes a move.
16946 * We give the human user a slight advantage if he is playing white---the
16947 * clocks don't run until he makes his first move, so it takes zero time.
16948 * Also, we don't account for network lag, so we could get out of sync
16949 * with GNU Chess's clock -- but then, referees are always right.
16952 static TimeMark tickStartTM;
16953 static long intendedTickLength;
16956 NextTickLength (long timeRemaining)
16958 long nominalTickLength, nextTickLength;
16960 if (timeRemaining > 0L && timeRemaining <= 10000L)
16961 nominalTickLength = 100L;
16963 nominalTickLength = 1000L;
16964 nextTickLength = timeRemaining % nominalTickLength;
16965 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16967 return nextTickLength;
16970 /* Adjust clock one minute up or down */
16972 AdjustClock (Boolean which, int dir)
16974 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16975 if(which) blackTimeRemaining += 60000*dir;
16976 else whiteTimeRemaining += 60000*dir;
16977 DisplayBothClocks();
16978 adjustedClock = TRUE;
16981 /* Stop clocks and reset to a fresh time control */
16985 (void) StopClockTimer();
16986 if (appData.icsActive) {
16987 whiteTimeRemaining = blackTimeRemaining = 0;
16988 } else if (searchTime) {
16989 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16990 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16991 } else { /* [HGM] correct new time quote for time odds */
16992 whiteTC = blackTC = fullTimeControlString;
16993 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16994 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16996 if (whiteFlag || blackFlag) {
16998 whiteFlag = blackFlag = FALSE;
17000 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17001 DisplayBothClocks();
17002 adjustedClock = FALSE;
17005 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17007 /* Decrement running clock by amount of time that has passed */
17011 long timeRemaining;
17012 long lastTickLength, fudge;
17015 if (!appData.clockMode) return;
17016 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17020 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17022 /* Fudge if we woke up a little too soon */
17023 fudge = intendedTickLength - lastTickLength;
17024 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17026 if (WhiteOnMove(forwardMostMove)) {
17027 if(whiteNPS >= 0) lastTickLength = 0;
17028 timeRemaining = whiteTimeRemaining -= lastTickLength;
17029 if(timeRemaining < 0 && !appData.icsActive) {
17030 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17031 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17032 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17033 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17036 DisplayWhiteClock(whiteTimeRemaining - fudge,
17037 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17039 if(blackNPS >= 0) lastTickLength = 0;
17040 timeRemaining = blackTimeRemaining -= lastTickLength;
17041 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17042 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17044 blackStartMove = forwardMostMove;
17045 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17048 DisplayBlackClock(blackTimeRemaining - fudge,
17049 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17051 if (CheckFlags()) return;
17053 if(twoBoards) { // count down secondary board's clocks as well
17054 activePartnerTime -= lastTickLength;
17056 if(activePartner == 'W')
17057 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17059 DisplayBlackClock(activePartnerTime, TRUE);
17064 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17065 StartClockTimer(intendedTickLength);
17067 /* if the time remaining has fallen below the alarm threshold, sound the
17068 * alarm. if the alarm has sounded and (due to a takeback or time control
17069 * with increment) the time remaining has increased to a level above the
17070 * threshold, reset the alarm so it can sound again.
17073 if (appData.icsActive && appData.icsAlarm) {
17075 /* make sure we are dealing with the user's clock */
17076 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17077 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17080 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17081 alarmSounded = FALSE;
17082 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17084 alarmSounded = TRUE;
17090 /* A player has just moved, so stop the previously running
17091 clock and (if in clock mode) start the other one.
17092 We redisplay both clocks in case we're in ICS mode, because
17093 ICS gives us an update to both clocks after every move.
17094 Note that this routine is called *after* forwardMostMove
17095 is updated, so the last fractional tick must be subtracted
17096 from the color that is *not* on move now.
17099 SwitchClocks (int newMoveNr)
17101 long lastTickLength;
17103 int flagged = FALSE;
17107 if (StopClockTimer() && appData.clockMode) {
17108 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17109 if (!WhiteOnMove(forwardMostMove)) {
17110 if(blackNPS >= 0) lastTickLength = 0;
17111 blackTimeRemaining -= lastTickLength;
17112 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17113 // if(pvInfoList[forwardMostMove].time == -1)
17114 pvInfoList[forwardMostMove].time = // use GUI time
17115 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17117 if(whiteNPS >= 0) lastTickLength = 0;
17118 whiteTimeRemaining -= lastTickLength;
17119 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17120 // if(pvInfoList[forwardMostMove].time == -1)
17121 pvInfoList[forwardMostMove].time =
17122 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17124 flagged = CheckFlags();
17126 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17127 CheckTimeControl();
17129 if (flagged || !appData.clockMode) return;
17131 switch (gameMode) {
17132 case MachinePlaysBlack:
17133 case MachinePlaysWhite:
17134 case BeginningOfGame:
17135 if (pausing) return;
17139 case PlayFromGameFile:
17147 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17148 if(WhiteOnMove(forwardMostMove))
17149 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17150 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17154 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17155 whiteTimeRemaining : blackTimeRemaining);
17156 StartClockTimer(intendedTickLength);
17160 /* Stop both clocks */
17164 long lastTickLength;
17167 if (!StopClockTimer()) return;
17168 if (!appData.clockMode) return;
17172 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17173 if (WhiteOnMove(forwardMostMove)) {
17174 if(whiteNPS >= 0) lastTickLength = 0;
17175 whiteTimeRemaining -= lastTickLength;
17176 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17178 if(blackNPS >= 0) lastTickLength = 0;
17179 blackTimeRemaining -= lastTickLength;
17180 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17185 /* Start clock of player on move. Time may have been reset, so
17186 if clock is already running, stop and restart it. */
17190 (void) StopClockTimer(); /* in case it was running already */
17191 DisplayBothClocks();
17192 if (CheckFlags()) return;
17194 if (!appData.clockMode) return;
17195 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17197 GetTimeMark(&tickStartTM);
17198 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17199 whiteTimeRemaining : blackTimeRemaining);
17201 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17202 whiteNPS = blackNPS = -1;
17203 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17204 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17205 whiteNPS = first.nps;
17206 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17207 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17208 blackNPS = first.nps;
17209 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17210 whiteNPS = second.nps;
17211 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17212 blackNPS = second.nps;
17213 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17215 StartClockTimer(intendedTickLength);
17219 TimeString (long ms)
17221 long second, minute, hour, day;
17223 static char buf[32];
17225 if (ms > 0 && ms <= 9900) {
17226 /* convert milliseconds to tenths, rounding up */
17227 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17229 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17233 /* convert milliseconds to seconds, rounding up */
17234 /* use floating point to avoid strangeness of integer division
17235 with negative dividends on many machines */
17236 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17243 day = second / (60 * 60 * 24);
17244 second = second % (60 * 60 * 24);
17245 hour = second / (60 * 60);
17246 second = second % (60 * 60);
17247 minute = second / 60;
17248 second = second % 60;
17251 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17252 sign, day, hour, minute, second);
17254 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17256 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17263 * This is necessary because some C libraries aren't ANSI C compliant yet.
17266 StrStr (char *string, char *match)
17270 length = strlen(match);
17272 for (i = strlen(string) - length; i >= 0; i--, string++)
17273 if (!strncmp(match, string, length))
17280 StrCaseStr (char *string, char *match)
17284 length = strlen(match);
17286 for (i = strlen(string) - length; i >= 0; i--, string++) {
17287 for (j = 0; j < length; j++) {
17288 if (ToLower(match[j]) != ToLower(string[j]))
17291 if (j == length) return string;
17299 StrCaseCmp (char *s1, char *s2)
17304 c1 = ToLower(*s1++);
17305 c2 = ToLower(*s2++);
17306 if (c1 > c2) return 1;
17307 if (c1 < c2) return -1;
17308 if (c1 == NULLCHAR) return 0;
17316 return isupper(c) ? tolower(c) : c;
17323 return islower(c) ? toupper(c) : c;
17325 #endif /* !_amigados */
17332 if ((ret = (char *) malloc(strlen(s) + 1)))
17334 safeStrCpy(ret, s, strlen(s)+1);
17340 StrSavePtr (char *s, char **savePtr)
17345 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17346 safeStrCpy(*savePtr, s, strlen(s)+1);
17358 clock = time((time_t *)NULL);
17359 tm = localtime(&clock);
17360 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17361 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17362 return StrSave(buf);
17367 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17369 int i, j, fromX, fromY, toX, toY;
17376 whiteToPlay = (gameMode == EditPosition) ?
17377 !blackPlaysFirst : (move % 2 == 0);
17380 /* Piece placement data */
17381 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17382 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17384 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17385 if (boards[move][i][j] == EmptySquare) {
17387 } else { ChessSquare piece = boards[move][i][j];
17388 if (emptycount > 0) {
17389 if(emptycount<10) /* [HGM] can be >= 10 */
17390 *p++ = '0' + emptycount;
17391 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17394 if(PieceToChar(piece) == '+') {
17395 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17397 piece = (ChessSquare)(DEMOTED piece);
17399 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17401 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17402 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17407 if (emptycount > 0) {
17408 if(emptycount<10) /* [HGM] can be >= 10 */
17409 *p++ = '0' + emptycount;
17410 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17417 /* [HGM] print Crazyhouse or Shogi holdings */
17418 if( gameInfo.holdingsWidth ) {
17419 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17421 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17422 piece = boards[move][i][BOARD_WIDTH-1];
17423 if( piece != EmptySquare )
17424 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17425 *p++ = PieceToChar(piece);
17427 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17428 piece = boards[move][BOARD_HEIGHT-i-1][0];
17429 if( piece != EmptySquare )
17430 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17431 *p++ = PieceToChar(piece);
17434 if( q == p ) *p++ = '-';
17440 *p++ = whiteToPlay ? 'w' : 'b';
17443 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17444 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17446 if(nrCastlingRights) {
17448 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17449 /* [HGM] write directly from rights */
17450 if(boards[move][CASTLING][2] != NoRights &&
17451 boards[move][CASTLING][0] != NoRights )
17452 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17453 if(boards[move][CASTLING][2] != NoRights &&
17454 boards[move][CASTLING][1] != NoRights )
17455 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17456 if(boards[move][CASTLING][5] != NoRights &&
17457 boards[move][CASTLING][3] != NoRights )
17458 *p++ = boards[move][CASTLING][3] + AAA;
17459 if(boards[move][CASTLING][5] != NoRights &&
17460 boards[move][CASTLING][4] != NoRights )
17461 *p++ = boards[move][CASTLING][4] + AAA;
17464 /* [HGM] write true castling rights */
17465 if( nrCastlingRights == 6 ) {
17467 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17468 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17469 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17470 boards[move][CASTLING][2] != NoRights );
17471 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17472 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17473 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17474 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17475 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17479 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17480 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17481 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17482 boards[move][CASTLING][5] != NoRights );
17483 if(gameInfo.variant == VariantSChess) {
17484 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17485 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17486 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17487 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17492 if (q == p) *p++ = '-'; /* No castling rights */
17496 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17497 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17498 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17499 /* En passant target square */
17500 if (move > backwardMostMove) {
17501 fromX = moveList[move - 1][0] - AAA;
17502 fromY = moveList[move - 1][1] - ONE;
17503 toX = moveList[move - 1][2] - AAA;
17504 toY = moveList[move - 1][3] - ONE;
17505 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17506 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17507 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17509 /* 2-square pawn move just happened */
17511 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17515 } else if(move == backwardMostMove) {
17516 // [HGM] perhaps we should always do it like this, and forget the above?
17517 if((signed char)boards[move][EP_STATUS] >= 0) {
17518 *p++ = boards[move][EP_STATUS] + AAA;
17519 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17531 { int i = 0, j=move;
17533 /* [HGM] find reversible plies */
17534 if (appData.debugMode) { int k;
17535 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17536 for(k=backwardMostMove; k<=forwardMostMove; k++)
17537 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17541 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17542 if( j == backwardMostMove ) i += initialRulePlies;
17543 sprintf(p, "%d ", i);
17544 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17546 /* Fullmove number */
17547 sprintf(p, "%d", (move / 2) + 1);
17548 } else *--p = NULLCHAR;
17550 return StrSave(buf);
17554 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17558 int emptycount, virgin[BOARD_FILES];
17563 /* Piece placement data */
17564 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17567 if (*p == '/' || *p == ' ' || *p == '[' ) {
17569 emptycount = gameInfo.boardWidth - j;
17570 while (emptycount--)
17571 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17572 if (*p == '/') p++;
17573 else if(autoSize) { // we stumbled unexpectedly into end of board
17574 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17575 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17577 appData.NrRanks = gameInfo.boardHeight - i; i=0;
17580 #if(BOARD_FILES >= 10)
17581 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17582 p++; emptycount=10;
17583 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17584 while (emptycount--)
17585 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17587 } else if (*p == '*') {
17588 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17589 } else if (isdigit(*p)) {
17590 emptycount = *p++ - '0';
17591 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17592 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17593 while (emptycount--)
17594 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17595 } else if (*p == '+' || isalpha(*p)) {
17596 if (j >= gameInfo.boardWidth) return FALSE;
17598 piece = CharToPiece(*++p);
17599 if(piece == EmptySquare) return FALSE; /* unknown piece */
17600 piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17601 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17602 } else piece = CharToPiece(*p++);
17604 if(piece==EmptySquare) return FALSE; /* unknown piece */
17605 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17606 piece = (ChessSquare) (PROMOTED piece);
17607 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17610 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17616 while (*p == '/' || *p == ' ') p++;
17618 if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17620 /* [HGM] by default clear Crazyhouse holdings, if present */
17621 if(gameInfo.holdingsWidth) {
17622 for(i=0; i<BOARD_HEIGHT; i++) {
17623 board[i][0] = EmptySquare; /* black holdings */
17624 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17625 board[i][1] = (ChessSquare) 0; /* black counts */
17626 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17630 /* [HGM] look for Crazyhouse holdings here */
17631 while(*p==' ') p++;
17632 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17634 if(*p == '-' ) p++; /* empty holdings */ else {
17635 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17636 /* if we would allow FEN reading to set board size, we would */
17637 /* have to add holdings and shift the board read so far here */
17638 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17640 if((int) piece >= (int) BlackPawn ) {
17641 i = (int)piece - (int)BlackPawn;
17642 i = PieceToNumber((ChessSquare)i);
17643 if( i >= gameInfo.holdingsSize ) return FALSE;
17644 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17645 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17647 i = (int)piece - (int)WhitePawn;
17648 i = PieceToNumber((ChessSquare)i);
17649 if( i >= gameInfo.holdingsSize ) return FALSE;
17650 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17651 board[i][BOARD_WIDTH-2]++; /* black holdings */
17658 while(*p == ' ') p++;
17662 if(appData.colorNickNames) {
17663 if( c == appData.colorNickNames[0] ) c = 'w'; else
17664 if( c == appData.colorNickNames[1] ) c = 'b';
17668 *blackPlaysFirst = FALSE;
17671 *blackPlaysFirst = TRUE;
17677 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17678 /* return the extra info in global variiables */
17680 /* set defaults in case FEN is incomplete */
17681 board[EP_STATUS] = EP_UNKNOWN;
17682 for(i=0; i<nrCastlingRights; i++ ) {
17683 board[CASTLING][i] =
17684 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17685 } /* assume possible unless obviously impossible */
17686 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17687 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17688 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17689 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17690 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17691 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17692 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17693 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17696 while(*p==' ') p++;
17697 if(nrCastlingRights) {
17698 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17699 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17700 /* castling indicator present, so default becomes no castlings */
17701 for(i=0; i<nrCastlingRights; i++ ) {
17702 board[CASTLING][i] = NoRights;
17705 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17706 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17707 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17708 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17709 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17711 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17712 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17713 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17715 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17716 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17717 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17718 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17719 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17720 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17723 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17724 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17725 board[CASTLING][2] = whiteKingFile;
17726 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17727 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17730 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17731 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17732 board[CASTLING][2] = whiteKingFile;
17733 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17734 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17737 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17738 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17739 board[CASTLING][5] = blackKingFile;
17740 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17741 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17744 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17745 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17746 board[CASTLING][5] = blackKingFile;
17747 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17748 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17751 default: /* FRC castlings */
17752 if(c >= 'a') { /* black rights */
17753 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17754 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17755 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17756 if(i == BOARD_RGHT) break;
17757 board[CASTLING][5] = i;
17759 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17760 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17762 board[CASTLING][3] = c;
17764 board[CASTLING][4] = c;
17765 } else { /* white rights */
17766 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17767 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17768 if(board[0][i] == WhiteKing) break;
17769 if(i == BOARD_RGHT) break;
17770 board[CASTLING][2] = i;
17771 c -= AAA - 'a' + 'A';
17772 if(board[0][c] >= WhiteKing) break;
17774 board[CASTLING][0] = c;
17776 board[CASTLING][1] = c;
17780 for(i=0; i<nrCastlingRights; i++)
17781 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17782 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17783 if (appData.debugMode) {
17784 fprintf(debugFP, "FEN castling rights:");
17785 for(i=0; i<nrCastlingRights; i++)
17786 fprintf(debugFP, " %d", board[CASTLING][i]);
17787 fprintf(debugFP, "\n");
17790 while(*p==' ') p++;
17793 /* read e.p. field in games that know e.p. capture */
17794 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17795 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17796 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17798 p++; board[EP_STATUS] = EP_NONE;
17800 char c = *p++ - AAA;
17802 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17803 if(*p >= '0' && *p <='9') p++;
17804 board[EP_STATUS] = c;
17809 if(sscanf(p, "%d", &i) == 1) {
17810 FENrulePlies = i; /* 50-move ply counter */
17811 /* (The move number is still ignored) */
17818 EditPositionPasteFEN (char *fen)
17821 Board initial_position;
17823 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17824 DisplayError(_("Bad FEN position in clipboard"), 0);
17827 int savedBlackPlaysFirst = blackPlaysFirst;
17828 EditPositionEvent();
17829 blackPlaysFirst = savedBlackPlaysFirst;
17830 CopyBoard(boards[0], initial_position);
17831 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17832 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17833 DisplayBothClocks();
17834 DrawPosition(FALSE, boards[currentMove]);
17839 static char cseq[12] = "\\ ";
17842 set_cont_sequence (char *new_seq)
17847 // handle bad attempts to set the sequence
17849 return 0; // acceptable error - no debug
17851 len = strlen(new_seq);
17852 ret = (len > 0) && (len < sizeof(cseq));
17854 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17855 else if (appData.debugMode)
17856 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17861 reformat a source message so words don't cross the width boundary. internal
17862 newlines are not removed. returns the wrapped size (no null character unless
17863 included in source message). If dest is NULL, only calculate the size required
17864 for the dest buffer. lp argument indicats line position upon entry, and it's
17865 passed back upon exit.
17868 wrap (char *dest, char *src, int count, int width, int *lp)
17870 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17872 cseq_len = strlen(cseq);
17873 old_line = line = *lp;
17874 ansi = len = clen = 0;
17876 for (i=0; i < count; i++)
17878 if (src[i] == '\033')
17881 // if we hit the width, back up
17882 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17884 // store i & len in case the word is too long
17885 old_i = i, old_len = len;
17887 // find the end of the last word
17888 while (i && src[i] != ' ' && src[i] != '\n')
17894 // word too long? restore i & len before splitting it
17895 if ((old_i-i+clen) >= width)
17902 if (i && src[i-1] == ' ')
17905 if (src[i] != ' ' && src[i] != '\n')
17912 // now append the newline and continuation sequence
17917 strncpy(dest+len, cseq, cseq_len);
17925 dest[len] = src[i];
17929 if (src[i] == '\n')
17934 if (dest && appData.debugMode)
17936 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17937 count, width, line, len, *lp);
17938 show_bytes(debugFP, src, count);
17939 fprintf(debugFP, "\ndest: ");
17940 show_bytes(debugFP, dest, len);
17941 fprintf(debugFP, "\n");
17943 *lp = dest ? line : old_line;
17948 // [HGM] vari: routines for shelving variations
17949 Boolean modeRestore = FALSE;
17952 PushInner (int firstMove, int lastMove)
17954 int i, j, nrMoves = lastMove - firstMove;
17956 // push current tail of game on stack
17957 savedResult[storedGames] = gameInfo.result;
17958 savedDetails[storedGames] = gameInfo.resultDetails;
17959 gameInfo.resultDetails = NULL;
17960 savedFirst[storedGames] = firstMove;
17961 savedLast [storedGames] = lastMove;
17962 savedFramePtr[storedGames] = framePtr;
17963 framePtr -= nrMoves; // reserve space for the boards
17964 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17965 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17966 for(j=0; j<MOVE_LEN; j++)
17967 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17968 for(j=0; j<2*MOVE_LEN; j++)
17969 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17970 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17971 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17972 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17973 pvInfoList[firstMove+i-1].depth = 0;
17974 commentList[framePtr+i] = commentList[firstMove+i];
17975 commentList[firstMove+i] = NULL;
17979 forwardMostMove = firstMove; // truncate game so we can start variation
17983 PushTail (int firstMove, int lastMove)
17985 if(appData.icsActive) { // only in local mode
17986 forwardMostMove = currentMove; // mimic old ICS behavior
17989 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17991 PushInner(firstMove, lastMove);
17992 if(storedGames == 1) GreyRevert(FALSE);
17993 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17997 PopInner (Boolean annotate)
18000 char buf[8000], moveBuf[20];
18002 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18003 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18004 nrMoves = savedLast[storedGames] - currentMove;
18007 if(!WhiteOnMove(currentMove))
18008 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18009 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18010 for(i=currentMove; i<forwardMostMove; i++) {
18012 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18013 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18014 strcat(buf, moveBuf);
18015 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18016 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18020 for(i=1; i<=nrMoves; i++) { // copy last variation back
18021 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18022 for(j=0; j<MOVE_LEN; j++)
18023 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18024 for(j=0; j<2*MOVE_LEN; j++)
18025 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18026 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18027 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18028 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18029 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18030 commentList[currentMove+i] = commentList[framePtr+i];
18031 commentList[framePtr+i] = NULL;
18033 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18034 framePtr = savedFramePtr[storedGames];
18035 gameInfo.result = savedResult[storedGames];
18036 if(gameInfo.resultDetails != NULL) {
18037 free(gameInfo.resultDetails);
18039 gameInfo.resultDetails = savedDetails[storedGames];
18040 forwardMostMove = currentMove + nrMoves;
18044 PopTail (Boolean annotate)
18046 if(appData.icsActive) return FALSE; // only in local mode
18047 if(!storedGames) return FALSE; // sanity
18048 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18050 PopInner(annotate);
18051 if(currentMove < forwardMostMove) ForwardEvent(); else
18052 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18054 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18060 { // remove all shelved variations
18062 for(i=0; i<storedGames; i++) {
18063 if(savedDetails[i])
18064 free(savedDetails[i]);
18065 savedDetails[i] = NULL;
18067 for(i=framePtr; i<MAX_MOVES; i++) {
18068 if(commentList[i]) free(commentList[i]);
18069 commentList[i] = NULL;
18071 framePtr = MAX_MOVES-1;
18076 LoadVariation (int index, char *text)
18077 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18078 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18079 int level = 0, move;
18081 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18082 // first find outermost bracketing variation
18083 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18084 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18085 if(*p == '{') wait = '}'; else
18086 if(*p == '[') wait = ']'; else
18087 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18088 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18090 if(*p == wait) wait = NULLCHAR; // closing ]} found
18093 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18094 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18095 end[1] = NULLCHAR; // clip off comment beyond variation
18096 ToNrEvent(currentMove-1);
18097 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18098 // kludge: use ParsePV() to append variation to game
18099 move = currentMove;
18100 ParsePV(start, TRUE, TRUE);
18101 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18102 ClearPremoveHighlights();
18104 ToNrEvent(currentMove+1);
18110 char *p, *q, buf[MSG_SIZ];
18111 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18112 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18113 ParseArgsFromString(buf);
18114 ActivateTheme(TRUE); // also redo colors
18118 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18121 q = appData.themeNames;
18122 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18123 if(appData.useBitmaps) {
18124 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18125 appData.liteBackTextureFile, appData.darkBackTextureFile,
18126 appData.liteBackTextureMode,
18127 appData.darkBackTextureMode );
18129 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18130 Col2Text(2), // lightSquareColor
18131 Col2Text(3) ); // darkSquareColor
18133 if(appData.useBorder) {
18134 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18137 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18139 if(appData.useFont) {
18140 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18141 appData.renderPiecesWithFont,
18142 appData.fontToPieceTable,
18143 Col2Text(9), // appData.fontBackColorWhite
18144 Col2Text(10) ); // appData.fontForeColorBlack
18146 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18147 appData.pieceDirectory);
18148 if(!appData.pieceDirectory[0])
18149 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18150 Col2Text(0), // whitePieceColor
18151 Col2Text(1) ); // blackPieceColor
18153 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18154 Col2Text(4), // highlightSquareColor
18155 Col2Text(5) ); // premoveHighlightColor
18156 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18157 if(insert != q) insert[-1] = NULLCHAR;
18158 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18161 ActivateTheme(FALSE);