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(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5062 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5063 moveList[moveNum][5], moveList[moveNum][6] - '0',
5064 moveList[moveNum][5], moveList[moveNum][6] - '0',
5065 moveList[moveNum][2], moveList[moveNum][3] - '0');
5066 SendToProgram(buf, cps);
5068 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5069 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5070 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5071 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5072 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5074 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5075 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5076 SendToProgram(buf, cps);
5078 else SendToProgram(moveList[moveNum], cps);
5079 /* End of additions by Tord */
5082 /* [HGM] setting up the opening has brought engine in force mode! */
5083 /* Send 'go' if we are in a mode where machine should play. */
5084 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5085 (gameMode == TwoMachinesPlay ||
5087 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5089 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5090 SendToProgram("go\n", cps);
5091 if (appData.debugMode) {
5092 fprintf(debugFP, "(extra)\n");
5095 setboardSpoiledMachineBlack = 0;
5099 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5101 char user_move[MSG_SIZ];
5104 if(gameInfo.variant == VariantSChess && promoChar) {
5105 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5106 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5107 } else suffix[0] = NULLCHAR;
5111 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5112 (int)moveType, fromX, fromY, toX, toY);
5113 DisplayError(user_move + strlen("say "), 0);
5115 case WhiteKingSideCastle:
5116 case BlackKingSideCastle:
5117 case WhiteQueenSideCastleWild:
5118 case BlackQueenSideCastleWild:
5120 case WhiteHSideCastleFR:
5121 case BlackHSideCastleFR:
5123 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5125 case WhiteQueenSideCastle:
5126 case BlackQueenSideCastle:
5127 case WhiteKingSideCastleWild:
5128 case BlackKingSideCastleWild:
5130 case WhiteASideCastleFR:
5131 case BlackASideCastleFR:
5133 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5135 case WhiteNonPromotion:
5136 case BlackNonPromotion:
5137 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5139 case WhitePromotion:
5140 case BlackPromotion:
5141 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5142 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5143 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5144 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5145 PieceToChar(WhiteFerz));
5146 else if(gameInfo.variant == VariantGreat)
5147 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5148 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5149 PieceToChar(WhiteMan));
5151 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5152 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5158 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5159 ToUpper(PieceToChar((ChessSquare) fromX)),
5160 AAA + toX, ONE + toY);
5162 case IllegalMove: /* could be a variant we don't quite understand */
5163 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5165 case WhiteCapturesEnPassant:
5166 case BlackCapturesEnPassant:
5167 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5168 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5171 SendToICS(user_move);
5172 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5173 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5178 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5179 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5180 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5181 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5182 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5185 if(gameMode != IcsExamining) { // is this ever not the case?
5186 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5188 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5189 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5190 } else { // on FICS we must first go to general examine mode
5191 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5193 if(gameInfo.variant != VariantNormal) {
5194 // try figure out wild number, as xboard names are not always valid on ICS
5195 for(i=1; i<=36; i++) {
5196 snprintf(buf, MSG_SIZ, "wild/%d", i);
5197 if(StringToVariant(buf) == gameInfo.variant) break;
5199 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5200 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5201 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5202 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5203 SendToICS(ics_prefix);
5205 if(startedFromSetupPosition || backwardMostMove != 0) {
5206 fen = PositionToFEN(backwardMostMove, NULL, 1);
5207 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5208 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5210 } else { // FICS: everything has to set by separate bsetup commands
5211 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5212 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5214 if(!WhiteOnMove(backwardMostMove)) {
5215 SendToICS("bsetup tomove black\n");
5217 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5218 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5220 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5221 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5223 i = boards[backwardMostMove][EP_STATUS];
5224 if(i >= 0) { // set e.p.
5225 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5231 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5232 SendToICS("bsetup done\n"); // switch to normal examining.
5234 for(i = backwardMostMove; i<last; i++) {
5236 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5237 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5238 int len = strlen(moveList[i]);
5239 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5240 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5244 SendToICS(ics_prefix);
5245 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5248 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5251 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5253 if (rf == DROP_RANK) {
5254 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5255 sprintf(move, "%c@%c%c\n",
5256 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5258 if (promoChar == 'x' || promoChar == NULLCHAR) {
5259 sprintf(move, "%c%c%c%c\n",
5260 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5261 if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5263 sprintf(move, "%c%c%c%c%c\n",
5264 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5270 ProcessICSInitScript (FILE *f)
5274 while (fgets(buf, MSG_SIZ, f)) {
5275 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5282 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5283 static ClickType lastClickType;
5288 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5289 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5290 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5291 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5292 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5293 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5296 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5297 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5298 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5299 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5300 if(!step) step = -1;
5301 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5302 appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion) ||
5303 IS_SHOGI(gameInfo.variant) && promoSweep != CHUPROMOTED last && last != CHUPROMOTED promoSweep && last != promoSweep);
5305 int victim = boards[currentMove][toY][toX];
5306 boards[currentMove][toY][toX] = promoSweep;
5307 DrawPosition(FALSE, boards[currentMove]);
5308 boards[currentMove][toY][toX] = victim;
5310 ChangeDragPiece(promoSweep);
5314 PromoScroll (int x, int y)
5318 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5319 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5320 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5321 if(!step) return FALSE;
5322 lastX = x; lastY = y;
5323 if((promoSweep < BlackPawn) == flipView) step = -step;
5324 if(step > 0) selectFlag = 1;
5325 if(!selectFlag) Sweep(step);
5330 NextPiece (int step)
5332 ChessSquare piece = boards[currentMove][toY][toX];
5335 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5336 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5337 if(!step) step = -1;
5338 } while(PieceToChar(pieceSweep) == '.');
5339 boards[currentMove][toY][toX] = pieceSweep;
5340 DrawPosition(FALSE, boards[currentMove]);
5341 boards[currentMove][toY][toX] = piece;
5343 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5345 AlphaRank (char *move, int n)
5347 // char *p = move, c; int x, y;
5349 if (appData.debugMode) {
5350 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5354 move[2]>='0' && move[2]<='9' &&
5355 move[3]>='a' && move[3]<='x' ) {
5357 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5358 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5360 if(move[0]>='0' && move[0]<='9' &&
5361 move[1]>='a' && move[1]<='x' &&
5362 move[2]>='0' && move[2]<='9' &&
5363 move[3]>='a' && move[3]<='x' ) {
5364 /* input move, Shogi -> normal */
5365 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5366 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5367 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5368 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5371 move[3]>='0' && move[3]<='9' &&
5372 move[2]>='a' && move[2]<='x' ) {
5374 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5375 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5378 move[0]>='a' && move[0]<='x' &&
5379 move[3]>='0' && move[3]<='9' &&
5380 move[2]>='a' && move[2]<='x' ) {
5381 /* output move, normal -> Shogi */
5382 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5383 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5384 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5385 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5386 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5388 if (appData.debugMode) {
5389 fprintf(debugFP, " out = '%s'\n", move);
5393 char yy_textstr[8000];
5395 /* Parser for moves from gnuchess, ICS, or user typein box */
5397 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5399 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5401 switch (*moveType) {
5402 case WhitePromotion:
5403 case BlackPromotion:
5404 case WhiteNonPromotion:
5405 case BlackNonPromotion:
5408 case WhiteCapturesEnPassant:
5409 case BlackCapturesEnPassant:
5410 case WhiteKingSideCastle:
5411 case WhiteQueenSideCastle:
5412 case BlackKingSideCastle:
5413 case BlackQueenSideCastle:
5414 case WhiteKingSideCastleWild:
5415 case WhiteQueenSideCastleWild:
5416 case BlackKingSideCastleWild:
5417 case BlackQueenSideCastleWild:
5418 /* Code added by Tord: */
5419 case WhiteHSideCastleFR:
5420 case WhiteASideCastleFR:
5421 case BlackHSideCastleFR:
5422 case BlackASideCastleFR:
5423 /* End of code added by Tord */
5424 case IllegalMove: /* bug or odd chess variant */
5425 *fromX = currentMoveString[0] - AAA;
5426 *fromY = currentMoveString[1] - ONE;
5427 *toX = currentMoveString[2] - AAA;
5428 *toY = currentMoveString[3] - ONE;
5429 *promoChar = currentMoveString[4];
5430 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5431 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5432 if (appData.debugMode) {
5433 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5435 *fromX = *fromY = *toX = *toY = 0;
5438 if (appData.testLegality) {
5439 return (*moveType != IllegalMove);
5441 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5442 killX < 0 && // [HGM] lion: if this is a double move we are less critical
5443 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5448 *fromX = *moveType == WhiteDrop ?
5449 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5450 (int) CharToPiece(ToLower(currentMoveString[0]));
5452 *toX = currentMoveString[2] - AAA;
5453 *toY = currentMoveString[3] - ONE;
5454 *promoChar = NULLCHAR;
5458 case ImpossibleMove:
5468 if (appData.debugMode) {
5469 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5472 *fromX = *fromY = *toX = *toY = 0;
5473 *promoChar = NULLCHAR;
5478 Boolean pushed = FALSE;
5479 char *lastParseAttempt;
5482 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5483 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5484 int fromX, fromY, toX, toY; char promoChar;
5489 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5490 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5491 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5494 endPV = forwardMostMove;
5496 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5497 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5498 lastParseAttempt = pv;
5499 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5500 if(!valid && nr == 0 &&
5501 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5502 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5503 // Hande case where played move is different from leading PV move
5504 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5505 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5506 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5507 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5508 endPV += 2; // if position different, keep this
5509 moveList[endPV-1][0] = fromX + AAA;
5510 moveList[endPV-1][1] = fromY + ONE;
5511 moveList[endPV-1][2] = toX + AAA;
5512 moveList[endPV-1][3] = toY + ONE;
5513 parseList[endPV-1][0] = NULLCHAR;
5514 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5517 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5518 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5519 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5520 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5521 valid++; // allow comments in PV
5525 if(endPV+1 > framePtr) break; // no space, truncate
5528 CopyBoard(boards[endPV], boards[endPV-1]);
5529 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5530 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5531 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5532 CoordsToAlgebraic(boards[endPV - 1],
5533 PosFlags(endPV - 1),
5534 fromY, fromX, toY, toX, promoChar,
5535 parseList[endPV - 1]);
5537 if(atEnd == 2) return; // used hidden, for PV conversion
5538 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5539 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5540 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5541 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5542 DrawPosition(TRUE, boards[currentMove]);
5546 MultiPV (ChessProgramState *cps)
5547 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5549 for(i=0; i<cps->nrOptions; i++)
5550 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5555 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5558 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5560 int startPV, multi, lineStart, origIndex = index;
5561 char *p, buf2[MSG_SIZ];
5562 ChessProgramState *cps = (pane ? &second : &first);
5564 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5565 lastX = x; lastY = y;
5566 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5567 lineStart = startPV = index;
5568 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5569 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5571 do{ while(buf[index] && buf[index] != '\n') index++;
5572 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5574 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5575 int n = cps->option[multi].value;
5576 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5577 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5578 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5579 cps->option[multi].value = n;
5582 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5583 ExcludeClick(origIndex - lineStart);
5586 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5587 *start = startPV; *end = index-1;
5588 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5595 static char buf[10*MSG_SIZ];
5596 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5598 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5599 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5600 for(i = forwardMostMove; i<endPV; i++){
5601 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5602 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5605 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5606 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5607 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5613 LoadPV (int x, int y)
5614 { // called on right mouse click to load PV
5615 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5616 lastX = x; lastY = y;
5617 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5625 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5626 if(endPV < 0) return;
5627 if(appData.autoCopyPV) CopyFENToClipboard();
5629 if(extendGame && currentMove > forwardMostMove) {
5630 Boolean saveAnimate = appData.animate;
5632 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5633 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5634 } else storedGames--; // abandon shelved tail of original game
5637 forwardMostMove = currentMove;
5638 currentMove = oldFMM;
5639 appData.animate = FALSE;
5640 ToNrEvent(forwardMostMove);
5641 appData.animate = saveAnimate;
5643 currentMove = forwardMostMove;
5644 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5645 ClearPremoveHighlights();
5646 DrawPosition(TRUE, boards[currentMove]);
5650 MovePV (int x, int y, int h)
5651 { // step through PV based on mouse coordinates (called on mouse move)
5652 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5654 // we must somehow check if right button is still down (might be released off board!)
5655 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5656 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5657 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5659 lastX = x; lastY = y;
5661 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5662 if(endPV < 0) return;
5663 if(y < margin) step = 1; else
5664 if(y > h - margin) step = -1;
5665 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5666 currentMove += step;
5667 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5668 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5669 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5670 DrawPosition(FALSE, boards[currentMove]);
5674 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5675 // All positions will have equal probability, but the current method will not provide a unique
5676 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5682 int piecesLeft[(int)BlackPawn];
5683 int seed, nrOfShuffles;
5686 GetPositionNumber ()
5687 { // sets global variable seed
5690 seed = appData.defaultFrcPosition;
5691 if(seed < 0) { // randomize based on time for negative FRC position numbers
5692 for(i=0; i<50; i++) seed += random();
5693 seed = random() ^ random() >> 8 ^ random() << 8;
5694 if(seed<0) seed = -seed;
5699 put (Board board, int pieceType, int rank, int n, int shade)
5700 // put the piece on the (n-1)-th empty squares of the given shade
5704 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5705 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5706 board[rank][i] = (ChessSquare) pieceType;
5707 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5709 piecesLeft[pieceType]--;
5718 AddOnePiece (Board board, int pieceType, int rank, int shade)
5719 // calculate where the next piece goes, (any empty square), and put it there
5723 i = seed % squaresLeft[shade];
5724 nrOfShuffles *= squaresLeft[shade];
5725 seed /= squaresLeft[shade];
5726 put(board, pieceType, rank, i, shade);
5730 AddTwoPieces (Board board, int pieceType, int rank)
5731 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5733 int i, n=squaresLeft[ANY], j=n-1, k;
5735 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5736 i = seed % k; // pick one
5739 while(i >= j) i -= j--;
5740 j = n - 1 - j; i += j;
5741 put(board, pieceType, rank, j, ANY);
5742 put(board, pieceType, rank, i, ANY);
5746 SetUpShuffle (Board board, int number)
5750 GetPositionNumber(); nrOfShuffles = 1;
5752 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5753 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5754 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5756 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5758 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5759 p = (int) board[0][i];
5760 if(p < (int) BlackPawn) piecesLeft[p] ++;
5761 board[0][i] = EmptySquare;
5764 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5765 // shuffles restricted to allow normal castling put KRR first
5766 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5767 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5768 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5769 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5770 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5771 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5772 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5773 put(board, WhiteRook, 0, 0, ANY);
5774 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5777 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5778 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5779 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5780 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5781 while(piecesLeft[p] >= 2) {
5782 AddOnePiece(board, p, 0, LITE);
5783 AddOnePiece(board, p, 0, DARK);
5785 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5788 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5789 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5790 // but we leave King and Rooks for last, to possibly obey FRC restriction
5791 if(p == (int)WhiteRook) continue;
5792 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5793 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5796 // now everything is placed, except perhaps King (Unicorn) and Rooks
5798 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5799 // Last King gets castling rights
5800 while(piecesLeft[(int)WhiteUnicorn]) {
5801 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5802 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5805 while(piecesLeft[(int)WhiteKing]) {
5806 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5807 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5812 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5813 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5816 // Only Rooks can be left; simply place them all
5817 while(piecesLeft[(int)WhiteRook]) {
5818 i = put(board, WhiteRook, 0, 0, ANY);
5819 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5822 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5824 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5827 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5828 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5831 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5835 SetCharTable (char *table, const char * map)
5836 /* [HGM] moved here from winboard.c because of its general usefulness */
5837 /* Basically a safe strcpy that uses the last character as King */
5839 int result = FALSE; int NrPieces;
5841 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5842 && NrPieces >= 12 && !(NrPieces&1)) {
5843 int i; /* [HGM] Accept even length from 12 to 34 */
5845 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5846 for( i=0; i<NrPieces/2-1; i++ ) {
5848 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5850 table[(int) WhiteKing] = map[NrPieces/2-1];
5851 table[(int) BlackKing] = map[NrPieces-1];
5860 Prelude (Board board)
5861 { // [HGM] superchess: random selection of exo-pieces
5862 int i, j, k; ChessSquare p;
5863 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5865 GetPositionNumber(); // use FRC position number
5867 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5868 SetCharTable(pieceToChar, appData.pieceToCharTable);
5869 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5870 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5873 j = seed%4; seed /= 4;
5874 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5875 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5876 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5877 j = seed%3 + (seed%3 >= j); seed /= 3;
5878 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5879 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5880 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5881 j = seed%3; seed /= 3;
5882 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5883 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5884 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5885 j = seed%2 + (seed%2 >= j); seed /= 2;
5886 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5887 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5888 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5889 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5890 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5891 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5892 put(board, exoPieces[0], 0, 0, ANY);
5893 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5897 InitPosition (int redraw)
5899 ChessSquare (* pieces)[BOARD_FILES];
5900 int i, j, pawnRow=1, pieceRows=1, overrule,
5901 oldx = gameInfo.boardWidth,
5902 oldy = gameInfo.boardHeight,
5903 oldh = gameInfo.holdingsWidth;
5906 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5908 /* [AS] Initialize pv info list [HGM] and game status */
5910 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5911 pvInfoList[i].depth = 0;
5912 boards[i][EP_STATUS] = EP_NONE;
5913 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5916 initialRulePlies = 0; /* 50-move counter start */
5918 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5919 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5923 /* [HGM] logic here is completely changed. In stead of full positions */
5924 /* the initialized data only consist of the two backranks. The switch */
5925 /* selects which one we will use, which is than copied to the Board */
5926 /* initialPosition, which for the rest is initialized by Pawns and */
5927 /* empty squares. This initial position is then copied to boards[0], */
5928 /* possibly after shuffling, so that it remains available. */
5930 gameInfo.holdingsWidth = 0; /* default board sizes */
5931 gameInfo.boardWidth = 8;
5932 gameInfo.boardHeight = 8;
5933 gameInfo.holdingsSize = 0;
5934 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5935 for(i=0; i<BOARD_FILES-2; i++)
5936 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5937 initialPosition[EP_STATUS] = EP_NONE;
5938 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5939 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5940 SetCharTable(pieceNickName, appData.pieceNickNames);
5941 else SetCharTable(pieceNickName, "............");
5944 switch (gameInfo.variant) {
5945 case VariantFischeRandom:
5946 shuffleOpenings = TRUE;
5949 case VariantShatranj:
5950 pieces = ShatranjArray;
5951 nrCastlingRights = 0;
5952 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5955 pieces = makrukArray;
5956 nrCastlingRights = 0;
5957 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5960 pieces = aseanArray;
5961 nrCastlingRights = 0;
5962 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5964 case VariantTwoKings:
5965 pieces = twoKingsArray;
5968 pieces = GrandArray;
5969 nrCastlingRights = 0;
5970 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5971 gameInfo.boardWidth = 10;
5972 gameInfo.boardHeight = 10;
5973 gameInfo.holdingsSize = 7;
5975 case VariantCapaRandom:
5976 shuffleOpenings = TRUE;
5977 case VariantCapablanca:
5978 pieces = CapablancaArray;
5979 gameInfo.boardWidth = 10;
5980 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5983 pieces = GothicArray;
5984 gameInfo.boardWidth = 10;
5985 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5988 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5989 gameInfo.holdingsSize = 7;
5990 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5993 pieces = JanusArray;
5994 gameInfo.boardWidth = 10;
5995 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5996 nrCastlingRights = 6;
5997 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5998 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5999 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6000 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6001 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6002 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6005 pieces = FalconArray;
6006 gameInfo.boardWidth = 10;
6007 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6009 case VariantXiangqi:
6010 pieces = XiangqiArray;
6011 gameInfo.boardWidth = 9;
6012 gameInfo.boardHeight = 10;
6013 nrCastlingRights = 0;
6014 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6017 pieces = ShogiArray;
6018 gameInfo.boardWidth = 9;
6019 gameInfo.boardHeight = 9;
6020 gameInfo.holdingsSize = 7;
6021 nrCastlingRights = 0;
6022 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6025 pieces = ChuArray; pieceRows = 3;
6026 gameInfo.boardWidth = 12;
6027 gameInfo.boardHeight = 12;
6028 nrCastlingRights = 0;
6029 SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6030 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6032 case VariantCourier:
6033 pieces = CourierArray;
6034 gameInfo.boardWidth = 12;
6035 nrCastlingRights = 0;
6036 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6038 case VariantKnightmate:
6039 pieces = KnightmateArray;
6040 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6042 case VariantSpartan:
6043 pieces = SpartanArray;
6044 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6048 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6051 pieces = fairyArray;
6052 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6055 pieces = GreatArray;
6056 gameInfo.boardWidth = 10;
6057 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6058 gameInfo.holdingsSize = 8;
6062 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6063 gameInfo.holdingsSize = 8;
6064 startedFromSetupPosition = TRUE;
6066 case VariantCrazyhouse:
6067 case VariantBughouse:
6069 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6070 gameInfo.holdingsSize = 5;
6072 case VariantWildCastle:
6074 /* !!?shuffle with kings guaranteed to be on d or e file */
6075 shuffleOpenings = 1;
6077 case VariantNoCastle:
6079 nrCastlingRights = 0;
6080 /* !!?unconstrained back-rank shuffle */
6081 shuffleOpenings = 1;
6086 if(appData.NrFiles >= 0) {
6087 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6088 gameInfo.boardWidth = appData.NrFiles;
6090 if(appData.NrRanks >= 0) {
6091 gameInfo.boardHeight = appData.NrRanks;
6093 if(appData.holdingsSize >= 0) {
6094 i = appData.holdingsSize;
6095 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6096 gameInfo.holdingsSize = i;
6098 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6099 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6100 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6102 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6103 if(pawnRow < 1) pawnRow = 1;
6104 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6105 if(gameInfo.variant == VariantChu) pawnRow = 3;
6107 /* User pieceToChar list overrules defaults */
6108 if(appData.pieceToCharTable != NULL)
6109 SetCharTable(pieceToChar, appData.pieceToCharTable);
6111 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6113 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6114 s = (ChessSquare) 0; /* account holding counts in guard band */
6115 for( i=0; i<BOARD_HEIGHT; i++ )
6116 initialPosition[i][j] = s;
6118 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6119 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6120 initialPosition[pawnRow][j] = WhitePawn;
6121 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6122 if(gameInfo.variant == VariantXiangqi) {
6124 initialPosition[pawnRow][j] =
6125 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6126 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6127 initialPosition[2][j] = WhiteCannon;
6128 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6132 if(gameInfo.variant == VariantChu) {
6133 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6134 initialPosition[pawnRow+1][j] = WhiteCobra,
6135 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6136 for(i=1; i<pieceRows; i++) {
6137 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6138 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6141 if(gameInfo.variant == VariantGrand) {
6142 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6143 initialPosition[0][j] = WhiteRook;
6144 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6147 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
6149 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6152 initialPosition[1][j] = WhiteBishop;
6153 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6155 initialPosition[1][j] = WhiteRook;
6156 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6159 if( nrCastlingRights == -1) {
6160 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6161 /* This sets default castling rights from none to normal corners */
6162 /* Variants with other castling rights must set them themselves above */
6163 nrCastlingRights = 6;
6165 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6166 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6167 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6168 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6169 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6170 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6173 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6174 if(gameInfo.variant == VariantGreat) { // promotion commoners
6175 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6176 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6177 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6178 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6180 if( gameInfo.variant == VariantSChess ) {
6181 initialPosition[1][0] = BlackMarshall;
6182 initialPosition[2][0] = BlackAngel;
6183 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6184 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6185 initialPosition[1][1] = initialPosition[2][1] =
6186 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6188 if (appData.debugMode) {
6189 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6191 if(shuffleOpenings) {
6192 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6193 startedFromSetupPosition = TRUE;
6195 if(startedFromPositionFile) {
6196 /* [HGM] loadPos: use PositionFile for every new game */
6197 CopyBoard(initialPosition, filePosition);
6198 for(i=0; i<nrCastlingRights; i++)
6199 initialRights[i] = filePosition[CASTLING][i];
6200 startedFromSetupPosition = TRUE;
6203 CopyBoard(boards[0], initialPosition);
6205 if(oldx != gameInfo.boardWidth ||
6206 oldy != gameInfo.boardHeight ||
6207 oldv != gameInfo.variant ||
6208 oldh != gameInfo.holdingsWidth
6210 InitDrawingSizes(-2 ,0);
6212 oldv = gameInfo.variant;
6214 DrawPosition(TRUE, boards[currentMove]);
6218 SendBoard (ChessProgramState *cps, int moveNum)
6220 char message[MSG_SIZ];
6222 if (cps->useSetboard) {
6223 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6224 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6225 SendToProgram(message, cps);
6230 int i, j, left=0, right=BOARD_WIDTH;
6231 /* Kludge to set black to move, avoiding the troublesome and now
6232 * deprecated "black" command.
6234 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6235 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6237 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6239 SendToProgram("edit\n", cps);
6240 SendToProgram("#\n", cps);
6241 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6242 bp = &boards[moveNum][i][left];
6243 for (j = left; j < right; j++, bp++) {
6244 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6245 if ((int) *bp < (int) BlackPawn) {
6246 if(j == BOARD_RGHT+1)
6247 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6248 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6249 if(message[0] == '+' || message[0] == '~') {
6250 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6251 PieceToChar((ChessSquare)(DEMOTED *bp)),
6254 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6255 message[1] = BOARD_RGHT - 1 - j + '1';
6256 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6258 SendToProgram(message, cps);
6263 SendToProgram("c\n", cps);
6264 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6265 bp = &boards[moveNum][i][left];
6266 for (j = left; j < right; j++, bp++) {
6267 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6268 if (((int) *bp != (int) EmptySquare)
6269 && ((int) *bp >= (int) BlackPawn)) {
6270 if(j == BOARD_LEFT-2)
6271 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6272 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6274 if(message[0] == '+' || message[0] == '~') {
6275 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6276 PieceToChar((ChessSquare)(DEMOTED *bp)),
6279 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6280 message[1] = BOARD_RGHT - 1 - j + '1';
6281 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6283 SendToProgram(message, cps);
6288 SendToProgram(".\n", cps);
6290 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6293 char exclusionHeader[MSG_SIZ];
6294 int exCnt, excludePtr;
6295 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6296 static Exclusion excluTab[200];
6297 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6303 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6304 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6310 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6311 excludePtr = 24; exCnt = 0;
6316 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6317 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6318 char buf[2*MOVE_LEN], *p;
6319 Exclusion *e = excluTab;
6321 for(i=0; i<exCnt; i++)
6322 if(e[i].ff == fromX && e[i].fr == fromY &&
6323 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6324 if(i == exCnt) { // was not in exclude list; add it
6325 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6326 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6327 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6330 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6331 excludePtr++; e[i].mark = excludePtr++;
6332 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6335 exclusionHeader[e[i].mark] = state;
6339 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6340 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6344 if((signed char)promoChar == -1) { // kludge to indicate best move
6345 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6346 return 1; // if unparsable, abort
6348 // update exclusion map (resolving toggle by consulting existing state)
6349 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6351 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6352 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6353 excludeMap[k] |= 1<<j;
6354 else excludeMap[k] &= ~(1<<j);
6356 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6358 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6359 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6361 return (state == '+');
6365 ExcludeClick (int index)
6368 Exclusion *e = excluTab;
6369 if(index < 25) { // none, best or tail clicked
6370 if(index < 13) { // none: include all
6371 WriteMap(0); // clear map
6372 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6373 SendToBoth("include all\n"); // and inform engine
6374 } else if(index > 18) { // tail
6375 if(exclusionHeader[19] == '-') { // tail was excluded
6376 SendToBoth("include all\n");
6377 WriteMap(0); // clear map completely
6378 // now re-exclude selected moves
6379 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6380 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6381 } else { // tail was included or in mixed state
6382 SendToBoth("exclude all\n");
6383 WriteMap(0xFF); // fill map completely
6384 // now re-include selected moves
6385 j = 0; // count them
6386 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6387 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6388 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6391 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6394 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6395 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6396 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6403 DefaultPromoChoice (int white)
6406 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6407 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6408 result = WhiteFerz; // no choice
6409 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6410 result= WhiteKing; // in Suicide Q is the last thing we want
6411 else if(gameInfo.variant == VariantSpartan)
6412 result = white ? WhiteQueen : WhiteAngel;
6413 else result = WhiteQueen;
6414 if(!white) result = WHITE_TO_BLACK result;
6418 static int autoQueen; // [HGM] oneclick
6421 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6423 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6424 /* [HGM] add Shogi promotions */
6425 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6430 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6431 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6433 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6434 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6437 piece = boards[currentMove][fromY][fromX];
6438 if(gameInfo.variant == VariantChu) {
6439 int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6440 promotionZoneSize = BOARD_HEIGHT/3;
6441 highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6442 } else if(gameInfo.variant == VariantShogi) {
6443 promotionZoneSize = BOARD_HEIGHT/3;
6444 highestPromotingPiece = (int)WhiteAlfil;
6445 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6446 promotionZoneSize = 3;
6449 // Treat Lance as Pawn when it is not representing Amazon
6450 if(gameInfo.variant != VariantSuper) {
6451 if(piece == WhiteLance) piece = WhitePawn; else
6452 if(piece == BlackLance) piece = BlackPawn;
6455 // next weed out all moves that do not touch the promotion zone at all
6456 if((int)piece >= BlackPawn) {
6457 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6459 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6461 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6462 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6465 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6467 // weed out mandatory Shogi promotions
6468 if(gameInfo.variant == VariantShogi) {
6469 if(piece >= BlackPawn) {
6470 if(toY == 0 && piece == BlackPawn ||
6471 toY == 0 && piece == BlackQueen ||
6472 toY <= 1 && piece == BlackKnight) {
6477 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6478 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6479 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6486 // weed out obviously illegal Pawn moves
6487 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6488 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6489 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6490 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6491 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6492 // note we are not allowed to test for valid (non-)capture, due to premove
6495 // we either have a choice what to promote to, or (in Shogi) whether to promote
6496 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6497 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6498 *promoChoice = PieceToChar(BlackFerz); // no choice
6501 // no sense asking what we must promote to if it is going to explode...
6502 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6503 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6506 // give caller the default choice even if we will not make it
6507 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6508 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6509 if( sweepSelect && gameInfo.variant != VariantGreat
6510 && gameInfo.variant != VariantGrand
6511 && gameInfo.variant != VariantSuper) return FALSE;
6512 if(autoQueen) return FALSE; // predetermined
6514 // suppress promotion popup on illegal moves that are not premoves
6515 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6516 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6517 if(appData.testLegality && !premove) {
6518 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6519 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) ? '+' : NULLCHAR);
6520 if(moveType != WhitePromotion && moveType != BlackPromotion)
6528 InPalace (int row, int column)
6529 { /* [HGM] for Xiangqi */
6530 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6531 column < (BOARD_WIDTH + 4)/2 &&
6532 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6537 PieceForSquare (int x, int y)
6539 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6542 return boards[currentMove][y][x];
6546 OKToStartUserMove (int x, int y)
6548 ChessSquare from_piece;
6551 if (matchMode) return FALSE;
6552 if (gameMode == EditPosition) return TRUE;
6554 if (x >= 0 && y >= 0)
6555 from_piece = boards[currentMove][y][x];
6557 from_piece = EmptySquare;
6559 if (from_piece == EmptySquare) return FALSE;
6561 white_piece = (int)from_piece >= (int)WhitePawn &&
6562 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6566 case TwoMachinesPlay:
6574 case MachinePlaysWhite:
6575 case IcsPlayingBlack:
6576 if (appData.zippyPlay) return FALSE;
6578 DisplayMoveError(_("You are playing Black"));
6583 case MachinePlaysBlack:
6584 case IcsPlayingWhite:
6585 if (appData.zippyPlay) return FALSE;
6587 DisplayMoveError(_("You are playing White"));
6592 case PlayFromGameFile:
6593 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6595 if (!white_piece && WhiteOnMove(currentMove)) {
6596 DisplayMoveError(_("It is White's turn"));
6599 if (white_piece && !WhiteOnMove(currentMove)) {
6600 DisplayMoveError(_("It is Black's turn"));
6603 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6604 /* Editing correspondence game history */
6605 /* Could disallow this or prompt for confirmation */
6610 case BeginningOfGame:
6611 if (appData.icsActive) return FALSE;
6612 if (!appData.noChessProgram) {
6614 DisplayMoveError(_("You are playing White"));
6621 if (!white_piece && WhiteOnMove(currentMove)) {
6622 DisplayMoveError(_("It is White's turn"));
6625 if (white_piece && !WhiteOnMove(currentMove)) {
6626 DisplayMoveError(_("It is Black's turn"));
6635 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6636 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6637 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6638 && gameMode != AnalyzeFile && gameMode != Training) {
6639 DisplayMoveError(_("Displayed position is not current"));
6646 OnlyMove (int *x, int *y, Boolean captures)
6648 DisambiguateClosure cl;
6649 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6651 case MachinePlaysBlack:
6652 case IcsPlayingWhite:
6653 case BeginningOfGame:
6654 if(!WhiteOnMove(currentMove)) return FALSE;
6656 case MachinePlaysWhite:
6657 case IcsPlayingBlack:
6658 if(WhiteOnMove(currentMove)) return FALSE;
6665 cl.pieceIn = EmptySquare;
6670 cl.promoCharIn = NULLCHAR;
6671 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6672 if( cl.kind == NormalMove ||
6673 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6674 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6675 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6682 if(cl.kind != ImpossibleMove) return FALSE;
6683 cl.pieceIn = EmptySquare;
6688 cl.promoCharIn = NULLCHAR;
6689 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6690 if( cl.kind == NormalMove ||
6691 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6692 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6693 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6698 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6704 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6705 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6706 int lastLoadGameUseList = FALSE;
6707 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6708 ChessMove lastLoadGameStart = EndOfFile;
6712 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6716 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6718 /* Check if the user is playing in turn. This is complicated because we
6719 let the user "pick up" a piece before it is his turn. So the piece he
6720 tried to pick up may have been captured by the time he puts it down!
6721 Therefore we use the color the user is supposed to be playing in this
6722 test, not the color of the piece that is currently on the starting
6723 square---except in EditGame mode, where the user is playing both
6724 sides; fortunately there the capture race can't happen. (It can
6725 now happen in IcsExamining mode, but that's just too bad. The user
6726 will get a somewhat confusing message in that case.)
6731 case TwoMachinesPlay:
6735 /* We switched into a game mode where moves are not accepted,
6736 perhaps while the mouse button was down. */
6739 case MachinePlaysWhite:
6740 /* User is moving for Black */
6741 if (WhiteOnMove(currentMove)) {
6742 DisplayMoveError(_("It is White's turn"));
6747 case MachinePlaysBlack:
6748 /* User is moving for White */
6749 if (!WhiteOnMove(currentMove)) {
6750 DisplayMoveError(_("It is Black's turn"));
6755 case PlayFromGameFile:
6756 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6759 case BeginningOfGame:
6762 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6763 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6764 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6765 /* User is moving for Black */
6766 if (WhiteOnMove(currentMove)) {
6767 DisplayMoveError(_("It is White's turn"));
6771 /* User is moving for White */
6772 if (!WhiteOnMove(currentMove)) {
6773 DisplayMoveError(_("It is Black's turn"));
6779 case IcsPlayingBlack:
6780 /* User is moving for Black */
6781 if (WhiteOnMove(currentMove)) {
6782 if (!appData.premove) {
6783 DisplayMoveError(_("It is White's turn"));
6784 } else if (toX >= 0 && toY >= 0) {
6787 premoveFromX = fromX;
6788 premoveFromY = fromY;
6789 premovePromoChar = promoChar;
6791 if (appData.debugMode)
6792 fprintf(debugFP, "Got premove: fromX %d,"
6793 "fromY %d, toX %d, toY %d\n",
6794 fromX, fromY, toX, toY);
6800 case IcsPlayingWhite:
6801 /* User is moving for White */
6802 if (!WhiteOnMove(currentMove)) {
6803 if (!appData.premove) {
6804 DisplayMoveError(_("It is Black's turn"));
6805 } else if (toX >= 0 && toY >= 0) {
6808 premoveFromX = fromX;
6809 premoveFromY = fromY;
6810 premovePromoChar = promoChar;
6812 if (appData.debugMode)
6813 fprintf(debugFP, "Got premove: fromX %d,"
6814 "fromY %d, toX %d, toY %d\n",
6815 fromX, fromY, toX, toY);
6825 /* EditPosition, empty square, or different color piece;
6826 click-click move is possible */
6827 if (toX == -2 || toY == -2) {
6828 boards[0][fromY][fromX] = EmptySquare;
6829 DrawPosition(FALSE, boards[currentMove]);
6831 } else if (toX >= 0 && toY >= 0) {
6832 boards[0][toY][toX] = boards[0][fromY][fromX];
6833 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6834 if(boards[0][fromY][0] != EmptySquare) {
6835 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6836 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6839 if(fromX == BOARD_RGHT+1) {
6840 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6841 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6842 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6845 boards[0][fromY][fromX] = gatingPiece;
6846 DrawPosition(FALSE, boards[currentMove]);
6852 if(toX < 0 || toY < 0) return;
6853 pup = boards[currentMove][toY][toX];
6855 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6856 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6857 if( pup != EmptySquare ) return;
6858 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6859 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6860 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6861 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6862 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6863 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6864 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6868 /* [HGM] always test for legality, to get promotion info */
6869 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6870 fromY, fromX, toY, toX, promoChar);
6872 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6874 /* [HGM] but possibly ignore an IllegalMove result */
6875 if (appData.testLegality) {
6876 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6877 DisplayMoveError(_("Illegal move"));
6882 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6883 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6884 ClearPremoveHighlights(); // was included
6885 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6889 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6892 /* Common tail of UserMoveEvent and DropMenuEvent */
6894 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6898 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6899 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6900 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6901 if(WhiteOnMove(currentMove)) {
6902 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6904 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6908 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6909 move type in caller when we know the move is a legal promotion */
6910 if(moveType == NormalMove && promoChar)
6911 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6913 /* [HGM] <popupFix> The following if has been moved here from
6914 UserMoveEvent(). Because it seemed to belong here (why not allow
6915 piece drops in training games?), and because it can only be
6916 performed after it is known to what we promote. */
6917 if (gameMode == Training) {
6918 /* compare the move played on the board to the next move in the
6919 * game. If they match, display the move and the opponent's response.
6920 * If they don't match, display an error message.
6924 CopyBoard(testBoard, boards[currentMove]);
6925 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6927 if (CompareBoards(testBoard, boards[currentMove+1])) {
6928 ForwardInner(currentMove+1);
6930 /* Autoplay the opponent's response.
6931 * if appData.animate was TRUE when Training mode was entered,
6932 * the response will be animated.
6934 saveAnimate = appData.animate;
6935 appData.animate = animateTraining;
6936 ForwardInner(currentMove+1);
6937 appData.animate = saveAnimate;
6939 /* check for the end of the game */
6940 if (currentMove >= forwardMostMove) {
6941 gameMode = PlayFromGameFile;
6943 SetTrainingModeOff();
6944 DisplayInformation(_("End of game"));
6947 DisplayError(_("Incorrect move"), 0);
6952 /* Ok, now we know that the move is good, so we can kill
6953 the previous line in Analysis Mode */
6954 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6955 && currentMove < forwardMostMove) {
6956 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6957 else forwardMostMove = currentMove;
6962 /* If we need the chess program but it's dead, restart it */
6963 ResurrectChessProgram();
6965 /* A user move restarts a paused game*/
6969 thinkOutput[0] = NULLCHAR;
6971 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6973 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6974 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6978 if (gameMode == BeginningOfGame) {
6979 if (appData.noChessProgram) {
6980 gameMode = EditGame;
6984 gameMode = MachinePlaysBlack;
6987 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6989 if (first.sendName) {
6990 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6991 SendToProgram(buf, &first);
6998 /* Relay move to ICS or chess engine */
6999 if (appData.icsActive) {
7000 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7001 gameMode == IcsExamining) {
7002 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7003 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7005 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7007 // also send plain move, in case ICS does not understand atomic claims
7008 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7012 if (first.sendTime && (gameMode == BeginningOfGame ||
7013 gameMode == MachinePlaysWhite ||
7014 gameMode == MachinePlaysBlack)) {
7015 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7017 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7018 // [HGM] book: if program might be playing, let it use book
7019 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7020 first.maybeThinking = TRUE;
7021 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7022 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7023 SendBoard(&first, currentMove+1);
7024 if(second.analyzing) {
7025 if(!second.useSetboard) SendToProgram("undo\n", &second);
7026 SendBoard(&second, currentMove+1);
7029 SendMoveToProgram(forwardMostMove-1, &first);
7030 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7032 if (currentMove == cmailOldMove + 1) {
7033 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7037 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7041 if(appData.testLegality)
7042 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7048 if (WhiteOnMove(currentMove)) {
7049 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7051 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7055 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7060 case MachinePlaysBlack:
7061 case MachinePlaysWhite:
7062 /* disable certain menu options while machine is thinking */
7063 SetMachineThinkingEnables();
7070 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7071 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7073 if(bookHit) { // [HGM] book: simulate book reply
7074 static char bookMove[MSG_SIZ]; // a bit generous?
7076 programStats.nodes = programStats.depth = programStats.time =
7077 programStats.score = programStats.got_only_move = 0;
7078 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7080 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7081 strcat(bookMove, bookHit);
7082 HandleMachineMove(bookMove, &first);
7088 MarkByFEN(char *fen)
7091 if(!appData.markers || !appData.highlightDragging) return;
7092 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7093 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7097 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7098 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7099 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7100 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7101 if(*fen == 'T') marker[r][f++] = 0; else
7102 if(*fen == 'Y') marker[r][f++] = 1; else
7103 if(*fen == 'G') marker[r][f++] = 3; else
7104 if(*fen == 'B') marker[r][f++] = 4; else
7105 if(*fen == 'C') marker[r][f++] = 5; else
7106 if(*fen == 'M') marker[r][f++] = 6; else
7107 if(*fen == 'W') marker[r][f++] = 7; else
7108 if(*fen == 'D') marker[r][f++] = 8; else
7109 if(*fen == 'R') marker[r][f++] = 2; else {
7110 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7113 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7117 DrawPosition(TRUE, NULL);
7120 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7123 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7125 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7126 Markers *m = (Markers *) closure;
7127 if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7128 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7129 || kind == WhiteCapturesEnPassant
7130 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7131 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7135 MarkTargetSquares (int clear)
7138 if(clear) { // no reason to ever suppress clearing
7139 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;
7140 if(!sum) return; // nothing was cleared,no redraw needed
7143 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7144 !appData.testLegality || gameMode == EditPosition) return;
7145 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7146 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7147 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7149 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7152 DrawPosition(FALSE, NULL);
7156 Explode (Board board, int fromX, int fromY, int toX, int toY)
7158 if(gameInfo.variant == VariantAtomic &&
7159 (board[toY][toX] != EmptySquare || // capture?
7160 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7161 board[fromY][fromX] == BlackPawn )
7163 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7169 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7172 CanPromote (ChessSquare piece, int y)
7174 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7175 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7176 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7177 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7178 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7179 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7180 return (piece == BlackPawn && y == 1 ||
7181 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7182 piece == BlackLance && y == 1 ||
7183 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7187 HoverEvent (int xPix, int yPix, int x, int y)
7189 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7191 if(dragging == 2) DragPieceMove(xPix, yPix); // [HGM] lion: drag without button for second leg
7192 if(!first.highlight) return;
7193 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7194 if(x == oldX && y == oldY) return; // only do something if we enter new square
7195 oldFromX = fromX; oldFromY = fromY;
7196 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7197 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7198 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7199 else if(oldX != x || oldY != y) {
7200 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7201 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7202 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7203 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7205 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7206 SendToProgram(buf, &first);
7209 // SetHighlights(fromX, fromY, x, y);
7213 void ReportClick(char *action, int x, int y)
7215 char buf[MSG_SIZ]; // Inform engine of what user does
7217 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7218 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7219 if(!first.highlight || gameMode == EditPosition) return;
7220 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7221 SendToProgram(buf, &first);
7225 LeftClick (ClickType clickType, int xPix, int yPix)
7228 Boolean saveAnimate;
7229 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7230 char promoChoice = NULLCHAR;
7232 static TimeMark lastClickTime, prevClickTime;
7234 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7236 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7238 if (clickType == Press) ErrorPopDown();
7239 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7241 x = EventToSquare(xPix, BOARD_WIDTH);
7242 y = EventToSquare(yPix, BOARD_HEIGHT);
7243 if (!flipView && y >= 0) {
7244 y = BOARD_HEIGHT - 1 - y;
7246 if (flipView && x >= 0) {
7247 x = BOARD_WIDTH - 1 - x;
7250 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7251 defaultPromoChoice = promoSweep;
7252 promoSweep = EmptySquare; // terminate sweep
7253 promoDefaultAltered = TRUE;
7254 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7257 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7258 if(clickType == Release) return; // ignore upclick of click-click destination
7259 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7260 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7261 if(gameInfo.holdingsWidth &&
7262 (WhiteOnMove(currentMove)
7263 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7264 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7265 // click in right holdings, for determining promotion piece
7266 ChessSquare p = boards[currentMove][y][x];
7267 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7268 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7269 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7270 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7275 DrawPosition(FALSE, boards[currentMove]);
7279 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7280 if(clickType == Press
7281 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7282 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7283 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7286 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7287 // could be static click on premove from-square: abort premove
7289 ClearPremoveHighlights();
7292 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7293 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7295 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7296 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7297 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7298 defaultPromoChoice = DefaultPromoChoice(side);
7301 autoQueen = appData.alwaysPromoteToQueen;
7305 gatingPiece = EmptySquare;
7306 if (clickType != Press) {
7307 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7308 DragPieceEnd(xPix, yPix); dragging = 0;
7309 DrawPosition(FALSE, NULL);
7313 doubleClick = FALSE;
7314 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7315 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7317 fromX = x; fromY = y; toX = toY = killX = killY = -1;
7318 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7319 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7320 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7322 if (OKToStartUserMove(fromX, fromY)) {
7324 ReportClick("lift", x, y);
7325 MarkTargetSquares(0);
7326 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7327 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7328 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7329 promoSweep = defaultPromoChoice;
7330 selectFlag = 0; lastX = xPix; lastY = yPix;
7331 Sweep(0); // Pawn that is going to promote: preview promotion piece
7332 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7334 if (appData.highlightDragging) {
7335 SetHighlights(fromX, fromY, -1, -1);
7339 } else fromX = fromY = -1;
7345 if (clickType == Press && gameMode != EditPosition) {
7350 // ignore off-board to clicks
7351 if(y < 0 || x < 0) return;
7353 /* Check if clicking again on the same color piece */
7354 fromP = boards[currentMove][fromY][fromX];
7355 toP = boards[currentMove][y][x];
7356 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7357 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7358 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7359 WhitePawn <= toP && toP <= WhiteKing &&
7360 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7361 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7362 (BlackPawn <= fromP && fromP <= BlackKing &&
7363 BlackPawn <= toP && toP <= BlackKing &&
7364 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7365 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7366 /* Clicked again on same color piece -- changed his mind */
7367 second = (x == fromX && y == fromY);
7369 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7370 second = FALSE; // first double-click rather than scond click
7371 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7373 promoDefaultAltered = FALSE;
7374 MarkTargetSquares(1);
7375 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7376 if (appData.highlightDragging) {
7377 SetHighlights(x, y, -1, -1);
7381 if (OKToStartUserMove(x, y)) {
7382 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7383 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7384 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7385 gatingPiece = boards[currentMove][fromY][fromX];
7386 else gatingPiece = doubleClick ? fromP : EmptySquare;
7388 fromY = y; dragging = 1;
7389 ReportClick("lift", x, y);
7390 MarkTargetSquares(0);
7391 DragPieceBegin(xPix, yPix, FALSE);
7392 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7393 promoSweep = defaultPromoChoice;
7394 selectFlag = 0; lastX = xPix; lastY = yPix;
7395 Sweep(0); // Pawn that is going to promote: preview promotion piece
7399 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7402 // ignore clicks on holdings
7403 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7406 if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7407 DragPieceEnd(xPix, yPix); dragging = 0;
7409 // a deferred attempt to click-click move an empty square on top of a piece
7410 boards[currentMove][y][x] = EmptySquare;
7412 DrawPosition(FALSE, boards[currentMove]);
7413 fromX = fromY = -1; clearFlag = 0;
7416 if (appData.animateDragging) {
7417 /* Undo animation damage if any */
7418 DrawPosition(FALSE, NULL);
7420 if (second || sweepSelecting) {
7421 /* Second up/down in same square; just abort move */
7422 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7423 second = sweepSelecting = 0;
7425 gatingPiece = EmptySquare;
7426 MarkTargetSquares(1);
7429 ClearPremoveHighlights();
7431 /* First upclick in same square; start click-click mode */
7432 SetHighlights(x, y, -1, -1);
7439 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7440 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7441 DisplayMessage(_("only marked squares are legal"),"");
7442 DrawPosition(TRUE, NULL);
7443 return; // ignore to-click
7446 /* we now have a different from- and (possibly off-board) to-square */
7447 /* Completed move */
7448 if(!sweepSelecting) {
7453 saveAnimate = appData.animate;
7454 if (clickType == Press) {
7455 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7456 // must be Edit Position mode with empty-square selected
7457 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7458 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7461 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7465 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7466 killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7468 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7469 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7470 if(appData.sweepSelect) {
7471 ChessSquare piece = boards[currentMove][fromY][fromX];
7472 promoSweep = defaultPromoChoice;
7473 if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7474 selectFlag = 0; lastX = xPix; lastY = yPix;
7475 Sweep(0); // Pawn that is going to promote: preview promotion piece
7477 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7478 MarkTargetSquares(1);
7480 return; // promo popup appears on up-click
7482 /* Finish clickclick move */
7483 if (appData.animate || appData.highlightLastMove) {
7484 SetHighlights(fromX, fromY, toX, toY);
7488 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7490 if (appData.animate || appData.highlightLastMove) {
7491 SetHighlights(fromX, fromY, toX, toY);
7497 // [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
7498 /* Finish drag move */
7499 if (appData.highlightLastMove) {
7500 SetHighlights(fromX, fromY, toX, toY);
7505 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7506 dragging *= 2; // flag button-less dragging if we are dragging
7507 MarkTargetSquares(1);
7508 if(x == killX && y == killY) killX = killY = -1; else {
7509 killX = x; killY = y; //remeber this square as intermediate
7510 MarkTargetSquares(0);
7511 ReportClick("put", x, y); // and inform engine
7512 ReportClick("lift", x, y);
7516 DragPieceEnd(xPix, yPix); dragging = 0;
7517 /* Don't animate move and drag both */
7518 appData.animate = FALSE;
7521 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7522 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7523 ChessSquare piece = boards[currentMove][fromY][fromX];
7524 if(gameMode == EditPosition && piece != EmptySquare &&
7525 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7528 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7529 n = PieceToNumber(piece - (int)BlackPawn);
7530 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7531 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7532 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7534 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7535 n = PieceToNumber(piece);
7536 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7537 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7538 boards[currentMove][n][BOARD_WIDTH-2]++;
7540 boards[currentMove][fromY][fromX] = EmptySquare;
7544 MarkTargetSquares(1);
7545 DrawPosition(TRUE, boards[currentMove]);
7549 // off-board moves should not be highlighted
7550 if(x < 0 || y < 0) ClearHighlights();
7551 else ReportClick("put", x, y);
7553 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7555 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7556 SetHighlights(fromX, fromY, toX, toY);
7557 MarkTargetSquares(1);
7558 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7559 // [HGM] super: promotion to captured piece selected from holdings
7560 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7561 promotionChoice = TRUE;
7562 // kludge follows to temporarily execute move on display, without promoting yet
7563 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7564 boards[currentMove][toY][toX] = p;
7565 DrawPosition(FALSE, boards[currentMove]);
7566 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7567 boards[currentMove][toY][toX] = q;
7568 DisplayMessage("Click in holdings to choose piece", "");
7573 int oldMove = currentMove;
7574 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7575 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7576 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7577 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7578 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7579 DrawPosition(TRUE, boards[currentMove]);
7580 MarkTargetSquares(1);
7583 appData.animate = saveAnimate;
7584 if (appData.animate || appData.animateDragging) {
7585 /* Undo animation damage if needed */
7586 DrawPosition(FALSE, NULL);
7591 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7592 { // front-end-free part taken out of PieceMenuPopup
7593 int whichMenu; int xSqr, ySqr;
7595 if(seekGraphUp) { // [HGM] seekgraph
7596 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7597 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7601 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7602 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7603 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7604 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7605 if(action == Press) {
7606 originalFlip = flipView;
7607 flipView = !flipView; // temporarily flip board to see game from partners perspective
7608 DrawPosition(TRUE, partnerBoard);
7609 DisplayMessage(partnerStatus, "");
7611 } else if(action == Release) {
7612 flipView = originalFlip;
7613 DrawPosition(TRUE, boards[currentMove]);
7619 xSqr = EventToSquare(x, BOARD_WIDTH);
7620 ySqr = EventToSquare(y, BOARD_HEIGHT);
7621 if (action == Release) {
7622 if(pieceSweep != EmptySquare) {
7623 EditPositionMenuEvent(pieceSweep, toX, toY);
7624 pieceSweep = EmptySquare;
7625 } else UnLoadPV(); // [HGM] pv
7627 if (action != Press) return -2; // return code to be ignored
7630 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7632 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7633 if (xSqr < 0 || ySqr < 0) return -1;
7634 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7635 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7636 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7637 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7641 if(!appData.icsEngineAnalyze) return -1;
7642 case IcsPlayingWhite:
7643 case IcsPlayingBlack:
7644 if(!appData.zippyPlay) goto noZip;
7647 case MachinePlaysWhite:
7648 case MachinePlaysBlack:
7649 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7650 if (!appData.dropMenu) {
7652 return 2; // flag front-end to grab mouse events
7654 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7655 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7658 if (xSqr < 0 || ySqr < 0) return -1;
7659 if (!appData.dropMenu || appData.testLegality &&
7660 gameInfo.variant != VariantBughouse &&
7661 gameInfo.variant != VariantCrazyhouse) return -1;
7662 whichMenu = 1; // drop menu
7668 if (((*fromX = xSqr) < 0) ||
7669 ((*fromY = ySqr) < 0)) {
7670 *fromX = *fromY = -1;
7674 *fromX = BOARD_WIDTH - 1 - *fromX;
7676 *fromY = BOARD_HEIGHT - 1 - *fromY;
7682 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7684 // char * hint = lastHint;
7685 FrontEndProgramStats stats;
7687 stats.which = cps == &first ? 0 : 1;
7688 stats.depth = cpstats->depth;
7689 stats.nodes = cpstats->nodes;
7690 stats.score = cpstats->score;
7691 stats.time = cpstats->time;
7692 stats.pv = cpstats->movelist;
7693 stats.hint = lastHint;
7694 stats.an_move_index = 0;
7695 stats.an_move_count = 0;
7697 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7698 stats.hint = cpstats->move_name;
7699 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7700 stats.an_move_count = cpstats->nr_moves;
7703 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
7705 SetProgramStats( &stats );
7709 ClearEngineOutputPane (int which)
7711 static FrontEndProgramStats dummyStats;
7712 dummyStats.which = which;
7713 dummyStats.pv = "#";
7714 SetProgramStats( &dummyStats );
7717 #define MAXPLAYERS 500
7720 TourneyStandings (int display)
7722 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7723 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7724 char result, *p, *names[MAXPLAYERS];
7726 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7727 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7728 names[0] = p = strdup(appData.participants);
7729 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7731 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7733 while(result = appData.results[nr]) {
7734 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7735 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7736 wScore = bScore = 0;
7738 case '+': wScore = 2; break;
7739 case '-': bScore = 2; break;
7740 case '=': wScore = bScore = 1; break;
7742 case '*': return strdup("busy"); // tourney not finished
7750 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7751 for(w=0; w<nPlayers; w++) {
7753 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7754 ranking[w] = b; points[w] = bScore; score[b] = -2;
7756 p = malloc(nPlayers*34+1);
7757 for(w=0; w<nPlayers && w<display; w++)
7758 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7764 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7765 { // count all piece types
7767 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7768 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7769 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7772 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7773 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7774 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7775 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7776 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7777 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7782 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7784 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7785 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7787 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7788 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7789 if(myPawns == 2 && nMine == 3) // KPP
7790 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7791 if(myPawns == 1 && nMine == 2) // KP
7792 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7793 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7794 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7795 if(myPawns) return FALSE;
7796 if(pCnt[WhiteRook+side])
7797 return pCnt[BlackRook-side] ||
7798 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7799 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7800 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7801 if(pCnt[WhiteCannon+side]) {
7802 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7803 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7805 if(pCnt[WhiteKnight+side])
7806 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7811 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7813 VariantClass v = gameInfo.variant;
7815 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7816 if(v == VariantShatranj) return TRUE; // always winnable through baring
7817 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7818 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7820 if(v == VariantXiangqi) {
7821 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7823 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7824 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7825 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7826 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7827 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7828 if(stale) // we have at least one last-rank P plus perhaps C
7829 return majors // KPKX
7830 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7832 return pCnt[WhiteFerz+side] // KCAK
7833 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7834 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7835 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7837 } else if(v == VariantKnightmate) {
7838 if(nMine == 1) return FALSE;
7839 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7840 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7841 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7843 if(nMine == 1) return FALSE; // bare King
7844 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
7845 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7846 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7847 // by now we have King + 1 piece (or multiple Bishops on the same color)
7848 if(pCnt[WhiteKnight+side])
7849 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7850 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7851 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7853 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7854 if(pCnt[WhiteAlfil+side])
7855 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7856 if(pCnt[WhiteWazir+side])
7857 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7864 CompareWithRights (Board b1, Board b2)
7867 if(!CompareBoards(b1, b2)) return FALSE;
7868 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7869 /* compare castling rights */
7870 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7871 rights++; /* King lost rights, while rook still had them */
7872 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7873 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7874 rights++; /* but at least one rook lost them */
7876 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7878 if( b1[CASTLING][5] != NoRights ) {
7879 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7886 Adjudicate (ChessProgramState *cps)
7887 { // [HGM] some adjudications useful with buggy engines
7888 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7889 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7890 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7891 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7892 int k, drop, count = 0; static int bare = 1;
7893 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7894 Boolean canAdjudicate = !appData.icsActive;
7896 // most tests only when we understand the game, i.e. legality-checking on
7897 if( appData.testLegality )
7898 { /* [HGM] Some more adjudications for obstinate engines */
7899 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7900 static int moveCount = 6;
7902 char *reason = NULL;
7904 /* Count what is on board. */
7905 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7907 /* Some material-based adjudications that have to be made before stalemate test */
7908 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7909 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7910 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7911 if(canAdjudicate && appData.checkMates) {
7913 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7914 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7915 "Xboard adjudication: King destroyed", GE_XBOARD );
7920 /* Bare King in Shatranj (loses) or Losers (wins) */
7921 if( nrW == 1 || nrB == 1) {
7922 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7923 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7924 if(canAdjudicate && appData.checkMates) {
7926 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7927 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7928 "Xboard adjudication: Bare king", GE_XBOARD );
7932 if( gameInfo.variant == VariantShatranj && --bare < 0)
7934 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7935 if(canAdjudicate && appData.checkMates) {
7936 /* but only adjudicate if adjudication enabled */
7938 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7939 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7940 "Xboard adjudication: Bare king", GE_XBOARD );
7947 // don't wait for engine to announce game end if we can judge ourselves
7948 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7950 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7951 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7952 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7953 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7956 reason = "Xboard adjudication: 3rd check";
7957 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7967 reason = "Xboard adjudication: Stalemate";
7968 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7969 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7970 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7971 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7972 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7973 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7974 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7975 EP_CHECKMATE : EP_WINS);
7976 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7977 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7981 reason = "Xboard adjudication: Checkmate";
7982 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7983 if(gameInfo.variant == VariantShogi) {
7984 if(forwardMostMove > backwardMostMove
7985 && moveList[forwardMostMove-1][1] == '@'
7986 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7987 reason = "XBoard adjudication: pawn-drop mate";
7988 boards[forwardMostMove][EP_STATUS] = EP_WINS;
7994 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7996 result = GameIsDrawn; break;
7998 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8000 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8004 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8006 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8007 GameEnds( result, reason, GE_XBOARD );
8011 /* Next absolutely insufficient mating material. */
8012 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8013 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8014 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8016 /* always flag draws, for judging claims */
8017 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8019 if(canAdjudicate && appData.materialDraws) {
8020 /* but only adjudicate them if adjudication enabled */
8021 if(engineOpponent) {
8022 SendToProgram("force\n", engineOpponent); // suppress reply
8023 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8025 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8030 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8031 if(gameInfo.variant == VariantXiangqi ?
8032 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8034 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8035 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8036 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8037 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8039 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8040 { /* if the first 3 moves do not show a tactical win, declare draw */
8041 if(engineOpponent) {
8042 SendToProgram("force\n", engineOpponent); // suppress reply
8043 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8045 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8048 } else moveCount = 6;
8051 // Repetition draws and 50-move rule can be applied independently of legality testing
8053 /* Check for rep-draws */
8055 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8056 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8057 for(k = forwardMostMove-2;
8058 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8059 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8060 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8063 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8064 /* compare castling rights */
8065 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8066 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8067 rights++; /* King lost rights, while rook still had them */
8068 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8069 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8070 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8071 rights++; /* but at least one rook lost them */
8073 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8074 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8076 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8077 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8078 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8081 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8082 && appData.drawRepeats > 1) {
8083 /* adjudicate after user-specified nr of repeats */
8084 int result = GameIsDrawn;
8085 char *details = "XBoard adjudication: repetition draw";
8086 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8087 // [HGM] xiangqi: check for forbidden perpetuals
8088 int m, ourPerpetual = 1, hisPerpetual = 1;
8089 for(m=forwardMostMove; m>k; m-=2) {
8090 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8091 ourPerpetual = 0; // the current mover did not always check
8092 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8093 hisPerpetual = 0; // the opponent did not always check
8095 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8096 ourPerpetual, hisPerpetual);
8097 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8098 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8099 details = "Xboard adjudication: perpetual checking";
8101 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8102 break; // (or we would have caught him before). Abort repetition-checking loop.
8104 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8105 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8107 details = "Xboard adjudication: repetition";
8109 } else // it must be XQ
8110 // Now check for perpetual chases
8111 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8112 hisPerpetual = PerpetualChase(k, forwardMostMove);
8113 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8114 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8115 static char resdet[MSG_SIZ];
8116 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8118 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8120 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8121 break; // Abort repetition-checking loop.
8123 // if neither of us is checking or chasing all the time, or both are, it is draw
8125 if(engineOpponent) {
8126 SendToProgram("force\n", engineOpponent); // suppress reply
8127 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8129 GameEnds( result, details, GE_XBOARD );
8132 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8133 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8137 /* Now we test for 50-move draws. Determine ply count */
8138 count = forwardMostMove;
8139 /* look for last irreversble move */
8140 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8142 /* if we hit starting position, add initial plies */
8143 if( count == backwardMostMove )
8144 count -= initialRulePlies;
8145 count = forwardMostMove - count;
8146 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8147 // adjust reversible move counter for checks in Xiangqi
8148 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8149 if(i < backwardMostMove) i = backwardMostMove;
8150 while(i <= forwardMostMove) {
8151 lastCheck = inCheck; // check evasion does not count
8152 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8153 if(inCheck || lastCheck) count--; // check does not count
8158 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8159 /* this is used to judge if draw claims are legal */
8160 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8161 if(engineOpponent) {
8162 SendToProgram("force\n", engineOpponent); // suppress reply
8163 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8165 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8169 /* if draw offer is pending, treat it as a draw claim
8170 * when draw condition present, to allow engines a way to
8171 * claim draws before making their move to avoid a race
8172 * condition occurring after their move
8174 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8176 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8177 p = "Draw claim: 50-move rule";
8178 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8179 p = "Draw claim: 3-fold repetition";
8180 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8181 p = "Draw claim: insufficient mating material";
8182 if( p != NULL && canAdjudicate) {
8183 if(engineOpponent) {
8184 SendToProgram("force\n", engineOpponent); // suppress reply
8185 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8187 GameEnds( GameIsDrawn, p, GE_XBOARD );
8192 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8193 if(engineOpponent) {
8194 SendToProgram("force\n", engineOpponent); // suppress reply
8195 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8197 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8204 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8205 { // [HGM] book: this routine intercepts moves to simulate book replies
8206 char *bookHit = NULL;
8208 //first determine if the incoming move brings opponent into his book
8209 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8210 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8211 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8212 if(bookHit != NULL && !cps->bookSuspend) {
8213 // make sure opponent is not going to reply after receiving move to book position
8214 SendToProgram("force\n", cps);
8215 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8217 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8218 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8219 // now arrange restart after book miss
8221 // after a book hit we never send 'go', and the code after the call to this routine
8222 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8223 char buf[MSG_SIZ], *move = bookHit;
8225 int fromX, fromY, toX, toY;
8229 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8230 &fromX, &fromY, &toX, &toY, &promoChar)) {
8231 (void) CoordsToAlgebraic(boards[forwardMostMove],
8232 PosFlags(forwardMostMove),
8233 fromY, fromX, toY, toX, promoChar, move);
8235 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8239 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8240 SendToProgram(buf, cps);
8241 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8242 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8243 SendToProgram("go\n", cps);
8244 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8245 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8246 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8247 SendToProgram("go\n", cps);
8248 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8250 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8254 LoadError (char *errmess, ChessProgramState *cps)
8255 { // unloads engine and switches back to -ncp mode if it was first
8256 if(cps->initDone) return FALSE;
8257 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8258 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8261 appData.noChessProgram = TRUE;
8262 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8263 gameMode = BeginningOfGame; ModeHighlight();
8266 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8267 DisplayMessage("", ""); // erase waiting message
8268 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8273 ChessProgramState *savedState;
8275 DeferredBookMove (void)
8277 if(savedState->lastPing != savedState->lastPong)
8278 ScheduleDelayedEvent(DeferredBookMove, 10);
8280 HandleMachineMove(savedMessage, savedState);
8283 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8284 static ChessProgramState *stalledEngine;
8285 static char stashedInputMove[MSG_SIZ];
8288 HandleMachineMove (char *message, ChessProgramState *cps)
8290 static char firstLeg[20];
8291 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8292 char realname[MSG_SIZ];
8293 int fromX, fromY, toX, toY;
8295 char promoChar, roar;
8297 int machineWhite, oldError;
8300 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8301 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8302 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8303 DisplayError(_("Invalid pairing from pairing engine"), 0);
8306 pairingReceived = 1;
8308 return; // Skim the pairing messages here.
8311 oldError = cps->userError; cps->userError = 0;
8313 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8315 * Kludge to ignore BEL characters
8317 while (*message == '\007') message++;
8320 * [HGM] engine debug message: ignore lines starting with '#' character
8322 if(cps->debug && *message == '#') return;
8325 * Look for book output
8327 if (cps == &first && bookRequested) {
8328 if (message[0] == '\t' || message[0] == ' ') {
8329 /* Part of the book output is here; append it */
8330 strcat(bookOutput, message);
8331 strcat(bookOutput, " \n");
8333 } else if (bookOutput[0] != NULLCHAR) {
8334 /* All of book output has arrived; display it */
8335 char *p = bookOutput;
8336 while (*p != NULLCHAR) {
8337 if (*p == '\t') *p = ' ';
8340 DisplayInformation(bookOutput);
8341 bookRequested = FALSE;
8342 /* Fall through to parse the current output */
8347 * Look for machine move.
8349 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8350 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8352 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8353 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8354 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8355 stalledEngine = cps;
8356 if(appData.ponderNextMove) { // bring opponent out of ponder
8357 if(gameMode == TwoMachinesPlay) {
8358 if(cps->other->pause)
8359 PauseEngine(cps->other);
8361 SendToProgram("easy\n", cps->other);
8368 /* This method is only useful on engines that support ping */
8369 if (cps->lastPing != cps->lastPong) {
8370 if (gameMode == BeginningOfGame) {
8371 /* Extra move from before last new; ignore */
8372 if (appData.debugMode) {
8373 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8376 if (appData.debugMode) {
8377 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8378 cps->which, gameMode);
8381 SendToProgram("undo\n", cps);
8387 case BeginningOfGame:
8388 /* Extra move from before last reset; ignore */
8389 if (appData.debugMode) {
8390 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8397 /* Extra move after we tried to stop. The mode test is
8398 not a reliable way of detecting this problem, but it's
8399 the best we can do on engines that don't support ping.
8401 if (appData.debugMode) {
8402 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8403 cps->which, gameMode);
8405 SendToProgram("undo\n", cps);
8408 case MachinePlaysWhite:
8409 case IcsPlayingWhite:
8410 machineWhite = TRUE;
8413 case MachinePlaysBlack:
8414 case IcsPlayingBlack:
8415 machineWhite = FALSE;
8418 case TwoMachinesPlay:
8419 machineWhite = (cps->twoMachinesColor[0] == 'w');
8422 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8423 if (appData.debugMode) {
8425 "Ignoring move out of turn by %s, gameMode %d"
8426 ", forwardMost %d\n",
8427 cps->which, gameMode, forwardMostMove);
8432 if(cps->alphaRank) AlphaRank(machineMove, 4);
8434 // [HGM] lion: (some very limited) support for Alien protocol
8436 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8437 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8439 } else if(firstLeg[0]) { // there was a previous leg;
8440 // only support case where same piece makes two step (and don't even test that!)
8441 char buf[20], *p = machineMove+1, *q = buf+1, f;
8442 safeStrCpy(buf, machineMove, 20);
8443 while(isdigit(*q)) q++; // find start of to-square
8444 safeStrCpy(machineMove, firstLeg, 20);
8445 while(isdigit(*p)) p++;
8446 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8447 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8448 firstLeg[0] = NULLCHAR;
8451 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8452 &fromX, &fromY, &toX, &toY, &promoChar)) {
8453 /* Machine move could not be parsed; ignore it. */
8454 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8455 machineMove, _(cps->which));
8456 DisplayMoveError(buf1);
8457 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8458 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8459 if (gameMode == TwoMachinesPlay) {
8460 GameEnds(machineWhite ? BlackWins : WhiteWins,
8466 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8467 /* So we have to redo legality test with true e.p. status here, */
8468 /* to make sure an illegal e.p. capture does not slip through, */
8469 /* to cause a forfeit on a justified illegal-move complaint */
8470 /* of the opponent. */
8471 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8473 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8474 fromY, fromX, toY, toX, promoChar);
8475 if(moveType == IllegalMove) {
8476 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8477 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8478 GameEnds(machineWhite ? BlackWins : WhiteWins,
8481 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8482 /* [HGM] Kludge to handle engines that send FRC-style castling
8483 when they shouldn't (like TSCP-Gothic) */
8485 case WhiteASideCastleFR:
8486 case BlackASideCastleFR:
8488 currentMoveString[2]++;
8490 case WhiteHSideCastleFR:
8491 case BlackHSideCastleFR:
8493 currentMoveString[2]--;
8495 default: ; // nothing to do, but suppresses warning of pedantic compilers
8498 hintRequested = FALSE;
8499 lastHint[0] = NULLCHAR;
8500 bookRequested = FALSE;
8501 /* Program may be pondering now */
8502 cps->maybeThinking = TRUE;
8503 if (cps->sendTime == 2) cps->sendTime = 1;
8504 if (cps->offeredDraw) cps->offeredDraw--;
8506 /* [AS] Save move info*/
8507 pvInfoList[ forwardMostMove ].score = programStats.score;
8508 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8509 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8511 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8513 /* Test suites abort the 'game' after one move */
8514 if(*appData.finger) {
8516 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8517 if(!f) f = fopen(appData.finger, "w");
8518 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8519 else { DisplayFatalError("Bad output file", errno, 0); return; }
8521 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8524 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8525 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8528 while( count < adjudicateLossPlies ) {
8529 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8532 score = -score; /* Flip score for winning side */
8535 if( score > adjudicateLossThreshold ) {
8542 if( count >= adjudicateLossPlies ) {
8543 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8545 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8546 "Xboard adjudication",
8553 if(Adjudicate(cps)) {
8554 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8555 return; // [HGM] adjudicate: for all automatic game ends
8559 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8561 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8562 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8564 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8566 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8568 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8569 char buf[3*MSG_SIZ];
8571 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8572 programStats.score / 100.,
8574 programStats.time / 100.,
8575 (unsigned int)programStats.nodes,
8576 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8577 programStats.movelist);
8579 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8584 /* [AS] Clear stats for next move */
8585 ClearProgramStats();
8586 thinkOutput[0] = NULLCHAR;
8587 hiddenThinkOutputState = 0;
8590 if (gameMode == TwoMachinesPlay) {
8591 /* [HGM] relaying draw offers moved to after reception of move */
8592 /* and interpreting offer as claim if it brings draw condition */
8593 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8594 SendToProgram("draw\n", cps->other);
8596 if (cps->other->sendTime) {
8597 SendTimeRemaining(cps->other,
8598 cps->other->twoMachinesColor[0] == 'w');
8600 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8601 if (firstMove && !bookHit) {
8603 if (cps->other->useColors) {
8604 SendToProgram(cps->other->twoMachinesColor, cps->other);
8606 SendToProgram("go\n", cps->other);
8608 cps->other->maybeThinking = TRUE;
8611 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8613 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8615 if (!pausing && appData.ringBellAfterMoves) {
8616 if(!roar) RingBell();
8620 * Reenable menu items that were disabled while
8621 * machine was thinking
8623 if (gameMode != TwoMachinesPlay)
8624 SetUserThinkingEnables();
8626 // [HGM] book: after book hit opponent has received move and is now in force mode
8627 // force the book reply into it, and then fake that it outputted this move by jumping
8628 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8630 static char bookMove[MSG_SIZ]; // a bit generous?
8632 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8633 strcat(bookMove, bookHit);
8636 programStats.nodes = programStats.depth = programStats.time =
8637 programStats.score = programStats.got_only_move = 0;
8638 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8640 if(cps->lastPing != cps->lastPong) {
8641 savedMessage = message; // args for deferred call
8643 ScheduleDelayedEvent(DeferredBookMove, 10);
8652 /* Set special modes for chess engines. Later something general
8653 * could be added here; for now there is just one kludge feature,
8654 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8655 * when "xboard" is given as an interactive command.
8657 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8658 cps->useSigint = FALSE;
8659 cps->useSigterm = FALSE;
8661 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8662 ParseFeatures(message+8, cps);
8663 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8666 if (!strncmp(message, "setup ", 6) &&
8667 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8668 ) { // [HGM] allow first engine to define opening position
8669 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8670 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8672 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8673 if(startedFromSetupPosition) return;
8674 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8676 while(message[s] && message[s++] != ' ');
8677 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8678 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8679 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8680 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8681 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8682 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8685 ParseFEN(boards[0], &dummy, message+s, FALSE);
8686 DrawPosition(TRUE, boards[0]);
8687 startedFromSetupPosition = TRUE;
8690 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8691 * want this, I was asked to put it in, and obliged.
8693 if (!strncmp(message, "setboard ", 9)) {
8694 Board initial_position;
8696 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8698 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8699 DisplayError(_("Bad FEN received from engine"), 0);
8703 CopyBoard(boards[0], initial_position);
8704 initialRulePlies = FENrulePlies;
8705 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8706 else gameMode = MachinePlaysBlack;
8707 DrawPosition(FALSE, boards[currentMove]);
8713 * Look for communication commands
8715 if (!strncmp(message, "telluser ", 9)) {
8716 if(message[9] == '\\' && message[10] == '\\')
8717 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8719 DisplayNote(message + 9);
8722 if (!strncmp(message, "tellusererror ", 14)) {
8724 if(message[14] == '\\' && message[15] == '\\')
8725 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8727 DisplayError(message + 14, 0);
8730 if (!strncmp(message, "tellopponent ", 13)) {
8731 if (appData.icsActive) {
8733 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8737 DisplayNote(message + 13);
8741 if (!strncmp(message, "tellothers ", 11)) {
8742 if (appData.icsActive) {
8744 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8747 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8750 if (!strncmp(message, "tellall ", 8)) {
8751 if (appData.icsActive) {
8753 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8757 DisplayNote(message + 8);
8761 if (strncmp(message, "warning", 7) == 0) {
8762 /* Undocumented feature, use tellusererror in new code */
8763 DisplayError(message, 0);
8766 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8767 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8768 strcat(realname, " query");
8769 AskQuestion(realname, buf2, buf1, cps->pr);
8772 /* Commands from the engine directly to ICS. We don't allow these to be
8773 * sent until we are logged on. Crafty kibitzes have been known to
8774 * interfere with the login process.
8777 if (!strncmp(message, "tellics ", 8)) {
8778 SendToICS(message + 8);
8782 if (!strncmp(message, "tellicsnoalias ", 15)) {
8783 SendToICS(ics_prefix);
8784 SendToICS(message + 15);
8788 /* The following are for backward compatibility only */
8789 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8790 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8791 SendToICS(ics_prefix);
8797 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8800 if(!strncmp(message, "highlight ", 10)) {
8801 if(appData.testLegality && appData.markers) return;
8802 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8805 if(!strncmp(message, "click ", 6)) {
8806 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8807 if(appData.testLegality || !appData.oneClick) return;
8808 sscanf(message+6, "%c%d%c", &f, &y, &c);
8809 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8810 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8811 x = x*squareSize + (x+1)*lineGap + squareSize/2;
8812 y = y*squareSize + (y+1)*lineGap + squareSize/2;
8813 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8814 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8815 LeftClick(Release, lastLeftX, lastLeftY);
8816 controlKey = (c == ',');
8817 LeftClick(Press, x, y);
8818 LeftClick(Release, x, y);
8819 first.highlight = f;
8823 * If the move is illegal, cancel it and redraw the board.
8824 * Also deal with other error cases. Matching is rather loose
8825 * here to accommodate engines written before the spec.
8827 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8828 strncmp(message, "Error", 5) == 0) {
8829 if (StrStr(message, "name") ||
8830 StrStr(message, "rating") || StrStr(message, "?") ||
8831 StrStr(message, "result") || StrStr(message, "board") ||
8832 StrStr(message, "bk") || StrStr(message, "computer") ||
8833 StrStr(message, "variant") || StrStr(message, "hint") ||
8834 StrStr(message, "random") || StrStr(message, "depth") ||
8835 StrStr(message, "accepted")) {
8838 if (StrStr(message, "protover")) {
8839 /* Program is responding to input, so it's apparently done
8840 initializing, and this error message indicates it is
8841 protocol version 1. So we don't need to wait any longer
8842 for it to initialize and send feature commands. */
8843 FeatureDone(cps, 1);
8844 cps->protocolVersion = 1;
8847 cps->maybeThinking = FALSE;
8849 if (StrStr(message, "draw")) {
8850 /* Program doesn't have "draw" command */
8851 cps->sendDrawOffers = 0;
8854 if (cps->sendTime != 1 &&
8855 (StrStr(message, "time") || StrStr(message, "otim"))) {
8856 /* Program apparently doesn't have "time" or "otim" command */
8860 if (StrStr(message, "analyze")) {
8861 cps->analysisSupport = FALSE;
8862 cps->analyzing = FALSE;
8863 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8864 EditGameEvent(); // [HGM] try to preserve loaded game
8865 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8866 DisplayError(buf2, 0);
8869 if (StrStr(message, "(no matching move)st")) {
8870 /* Special kludge for GNU Chess 4 only */
8871 cps->stKludge = TRUE;
8872 SendTimeControl(cps, movesPerSession, timeControl,
8873 timeIncrement, appData.searchDepth,
8877 if (StrStr(message, "(no matching move)sd")) {
8878 /* Special kludge for GNU Chess 4 only */
8879 cps->sdKludge = TRUE;
8880 SendTimeControl(cps, movesPerSession, timeControl,
8881 timeIncrement, appData.searchDepth,
8885 if (!StrStr(message, "llegal")) {
8888 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8889 gameMode == IcsIdle) return;
8890 if (forwardMostMove <= backwardMostMove) return;
8891 if (pausing) PauseEvent();
8892 if(appData.forceIllegal) {
8893 // [HGM] illegal: machine refused move; force position after move into it
8894 SendToProgram("force\n", cps);
8895 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8896 // we have a real problem now, as SendBoard will use the a2a3 kludge
8897 // when black is to move, while there might be nothing on a2 or black
8898 // might already have the move. So send the board as if white has the move.
8899 // But first we must change the stm of the engine, as it refused the last move
8900 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8901 if(WhiteOnMove(forwardMostMove)) {
8902 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8903 SendBoard(cps, forwardMostMove); // kludgeless board
8905 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8906 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8907 SendBoard(cps, forwardMostMove+1); // kludgeless board
8909 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8910 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8911 gameMode == TwoMachinesPlay)
8912 SendToProgram("go\n", cps);
8915 if (gameMode == PlayFromGameFile) {
8916 /* Stop reading this game file */
8917 gameMode = EditGame;
8920 /* [HGM] illegal-move claim should forfeit game when Xboard */
8921 /* only passes fully legal moves */
8922 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8923 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8924 "False illegal-move claim", GE_XBOARD );
8925 return; // do not take back move we tested as valid
8927 currentMove = forwardMostMove-1;
8928 DisplayMove(currentMove-1); /* before DisplayMoveError */
8929 SwitchClocks(forwardMostMove-1); // [HGM] race
8930 DisplayBothClocks();
8931 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8932 parseList[currentMove], _(cps->which));
8933 DisplayMoveError(buf1);
8934 DrawPosition(FALSE, boards[currentMove]);
8936 SetUserThinkingEnables();
8939 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8940 /* Program has a broken "time" command that
8941 outputs a string not ending in newline.
8947 * If chess program startup fails, exit with an error message.
8948 * Attempts to recover here are futile. [HGM] Well, we try anyway
8950 if ((StrStr(message, "unknown host") != NULL)
8951 || (StrStr(message, "No remote directory") != NULL)
8952 || (StrStr(message, "not found") != NULL)
8953 || (StrStr(message, "No such file") != NULL)
8954 || (StrStr(message, "can't alloc") != NULL)
8955 || (StrStr(message, "Permission denied") != NULL)) {
8957 cps->maybeThinking = FALSE;
8958 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8959 _(cps->which), cps->program, cps->host, message);
8960 RemoveInputSource(cps->isr);
8961 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8962 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8963 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8969 * Look for hint output
8971 if (sscanf(message, "Hint: %s", buf1) == 1) {
8972 if (cps == &first && hintRequested) {
8973 hintRequested = FALSE;
8974 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8975 &fromX, &fromY, &toX, &toY, &promoChar)) {
8976 (void) CoordsToAlgebraic(boards[forwardMostMove],
8977 PosFlags(forwardMostMove),
8978 fromY, fromX, toY, toX, promoChar, buf1);
8979 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8980 DisplayInformation(buf2);
8982 /* Hint move could not be parsed!? */
8983 snprintf(buf2, sizeof(buf2),
8984 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8985 buf1, _(cps->which));
8986 DisplayError(buf2, 0);
8989 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8995 * Ignore other messages if game is not in progress
8997 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8998 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9001 * look for win, lose, draw, or draw offer
9003 if (strncmp(message, "1-0", 3) == 0) {
9004 char *p, *q, *r = "";
9005 p = strchr(message, '{');
9013 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9015 } else if (strncmp(message, "0-1", 3) == 0) {
9016 char *p, *q, *r = "";
9017 p = strchr(message, '{');
9025 /* Kludge for Arasan 4.1 bug */
9026 if (strcmp(r, "Black resigns") == 0) {
9027 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9030 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9032 } else if (strncmp(message, "1/2", 3) == 0) {
9033 char *p, *q, *r = "";
9034 p = strchr(message, '{');
9043 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9046 } else if (strncmp(message, "White resign", 12) == 0) {
9047 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9049 } else if (strncmp(message, "Black resign", 12) == 0) {
9050 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9052 } else if (strncmp(message, "White matches", 13) == 0 ||
9053 strncmp(message, "Black matches", 13) == 0 ) {
9054 /* [HGM] ignore GNUShogi noises */
9056 } else if (strncmp(message, "White", 5) == 0 &&
9057 message[5] != '(' &&
9058 StrStr(message, "Black") == NULL) {
9059 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9061 } else if (strncmp(message, "Black", 5) == 0 &&
9062 message[5] != '(') {
9063 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9065 } else if (strcmp(message, "resign") == 0 ||
9066 strcmp(message, "computer resigns") == 0) {
9068 case MachinePlaysBlack:
9069 case IcsPlayingBlack:
9070 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9072 case MachinePlaysWhite:
9073 case IcsPlayingWhite:
9074 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9076 case TwoMachinesPlay:
9077 if (cps->twoMachinesColor[0] == 'w')
9078 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9080 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9087 } else if (strncmp(message, "opponent mates", 14) == 0) {
9089 case MachinePlaysBlack:
9090 case IcsPlayingBlack:
9091 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9093 case MachinePlaysWhite:
9094 case IcsPlayingWhite:
9095 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9097 case TwoMachinesPlay:
9098 if (cps->twoMachinesColor[0] == 'w')
9099 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9101 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9108 } else if (strncmp(message, "computer mates", 14) == 0) {
9110 case MachinePlaysBlack:
9111 case IcsPlayingBlack:
9112 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9114 case MachinePlaysWhite:
9115 case IcsPlayingWhite:
9116 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9118 case TwoMachinesPlay:
9119 if (cps->twoMachinesColor[0] == 'w')
9120 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9122 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9129 } else if (strncmp(message, "checkmate", 9) == 0) {
9130 if (WhiteOnMove(forwardMostMove)) {
9131 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9133 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9136 } else if (strstr(message, "Draw") != NULL ||
9137 strstr(message, "game is a draw") != NULL) {
9138 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9140 } else if (strstr(message, "offer") != NULL &&
9141 strstr(message, "draw") != NULL) {
9143 if (appData.zippyPlay && first.initDone) {
9144 /* Relay offer to ICS */
9145 SendToICS(ics_prefix);
9146 SendToICS("draw\n");
9149 cps->offeredDraw = 2; /* valid until this engine moves twice */
9150 if (gameMode == TwoMachinesPlay) {
9151 if (cps->other->offeredDraw) {
9152 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9153 /* [HGM] in two-machine mode we delay relaying draw offer */
9154 /* until after we also have move, to see if it is really claim */
9156 } else if (gameMode == MachinePlaysWhite ||
9157 gameMode == MachinePlaysBlack) {
9158 if (userOfferedDraw) {
9159 DisplayInformation(_("Machine accepts your draw offer"));
9160 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9162 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9169 * Look for thinking output
9171 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9172 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9174 int plylev, mvleft, mvtot, curscore, time;
9175 char mvname[MOVE_LEN];
9179 int prefixHint = FALSE;
9180 mvname[0] = NULLCHAR;
9183 case MachinePlaysBlack:
9184 case IcsPlayingBlack:
9185 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9187 case MachinePlaysWhite:
9188 case IcsPlayingWhite:
9189 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9194 case IcsObserving: /* [DM] icsEngineAnalyze */
9195 if (!appData.icsEngineAnalyze) ignore = TRUE;
9197 case TwoMachinesPlay:
9198 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9208 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9210 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9211 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9213 if (plyext != ' ' && plyext != '\t') {
9217 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9218 if( cps->scoreIsAbsolute &&
9219 ( gameMode == MachinePlaysBlack ||
9220 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9221 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9222 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9223 !WhiteOnMove(currentMove)
9226 curscore = -curscore;
9229 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9231 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9234 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9235 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9236 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9237 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9238 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9239 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9243 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9244 DisplayError(_("failed writing PV"), 0);
9247 tempStats.depth = plylev;
9248 tempStats.nodes = nodes;
9249 tempStats.time = time;
9250 tempStats.score = curscore;
9251 tempStats.got_only_move = 0;
9253 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9256 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9257 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9258 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9259 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9260 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9261 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9262 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9263 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9266 /* Buffer overflow protection */
9267 if (pv[0] != NULLCHAR) {
9268 if (strlen(pv) >= sizeof(tempStats.movelist)
9269 && appData.debugMode) {
9271 "PV is too long; using the first %u bytes.\n",
9272 (unsigned) sizeof(tempStats.movelist) - 1);
9275 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9277 sprintf(tempStats.movelist, " no PV\n");
9280 if (tempStats.seen_stat) {
9281 tempStats.ok_to_send = 1;
9284 if (strchr(tempStats.movelist, '(') != NULL) {
9285 tempStats.line_is_book = 1;
9286 tempStats.nr_moves = 0;
9287 tempStats.moves_left = 0;
9289 tempStats.line_is_book = 0;
9292 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9293 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9295 SendProgramStatsToFrontend( cps, &tempStats );
9298 [AS] Protect the thinkOutput buffer from overflow... this
9299 is only useful if buf1 hasn't overflowed first!
9301 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9303 (gameMode == TwoMachinesPlay ?
9304 ToUpper(cps->twoMachinesColor[0]) : ' '),
9305 ((double) curscore) / 100.0,
9306 prefixHint ? lastHint : "",
9307 prefixHint ? " " : "" );
9309 if( buf1[0] != NULLCHAR ) {
9310 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9312 if( strlen(pv) > max_len ) {
9313 if( appData.debugMode) {
9314 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9316 pv[max_len+1] = '\0';
9319 strcat( thinkOutput, pv);
9322 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9323 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9324 DisplayMove(currentMove - 1);
9328 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9329 /* crafty (9.25+) says "(only move) <move>"
9330 * if there is only 1 legal move
9332 sscanf(p, "(only move) %s", buf1);
9333 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9334 sprintf(programStats.movelist, "%s (only move)", buf1);
9335 programStats.depth = 1;
9336 programStats.nr_moves = 1;
9337 programStats.moves_left = 1;
9338 programStats.nodes = 1;
9339 programStats.time = 1;
9340 programStats.got_only_move = 1;
9342 /* Not really, but we also use this member to
9343 mean "line isn't going to change" (Crafty
9344 isn't searching, so stats won't change) */
9345 programStats.line_is_book = 1;
9347 SendProgramStatsToFrontend( cps, &programStats );
9349 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9350 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9351 DisplayMove(currentMove - 1);
9354 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9355 &time, &nodes, &plylev, &mvleft,
9356 &mvtot, mvname) >= 5) {
9357 /* The stat01: line is from Crafty (9.29+) in response
9358 to the "." command */
9359 programStats.seen_stat = 1;
9360 cps->maybeThinking = TRUE;
9362 if (programStats.got_only_move || !appData.periodicUpdates)
9365 programStats.depth = plylev;
9366 programStats.time = time;
9367 programStats.nodes = nodes;
9368 programStats.moves_left = mvleft;
9369 programStats.nr_moves = mvtot;
9370 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9371 programStats.ok_to_send = 1;
9372 programStats.movelist[0] = '\0';
9374 SendProgramStatsToFrontend( cps, &programStats );
9378 } else if (strncmp(message,"++",2) == 0) {
9379 /* Crafty 9.29+ outputs this */
9380 programStats.got_fail = 2;
9383 } else if (strncmp(message,"--",2) == 0) {
9384 /* Crafty 9.29+ outputs this */
9385 programStats.got_fail = 1;
9388 } else if (thinkOutput[0] != NULLCHAR &&
9389 strncmp(message, " ", 4) == 0) {
9390 unsigned message_len;
9393 while (*p && *p == ' ') p++;
9395 message_len = strlen( p );
9397 /* [AS] Avoid buffer overflow */
9398 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9399 strcat(thinkOutput, " ");
9400 strcat(thinkOutput, p);
9403 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9404 strcat(programStats.movelist, " ");
9405 strcat(programStats.movelist, p);
9408 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9409 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9410 DisplayMove(currentMove - 1);
9418 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9419 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9421 ChessProgramStats cpstats;
9423 if (plyext != ' ' && plyext != '\t') {
9427 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9428 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9429 curscore = -curscore;
9432 cpstats.depth = plylev;
9433 cpstats.nodes = nodes;
9434 cpstats.time = time;
9435 cpstats.score = curscore;
9436 cpstats.got_only_move = 0;
9437 cpstats.movelist[0] = '\0';
9439 if (buf1[0] != NULLCHAR) {
9440 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9443 cpstats.ok_to_send = 0;
9444 cpstats.line_is_book = 0;
9445 cpstats.nr_moves = 0;
9446 cpstats.moves_left = 0;
9448 SendProgramStatsToFrontend( cps, &cpstats );
9455 /* Parse a game score from the character string "game", and
9456 record it as the history of the current game. The game
9457 score is NOT assumed to start from the standard position.
9458 The display is not updated in any way.
9461 ParseGameHistory (char *game)
9464 int fromX, fromY, toX, toY, boardIndex;
9469 if (appData.debugMode)
9470 fprintf(debugFP, "Parsing game history: %s\n", game);
9472 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9473 gameInfo.site = StrSave(appData.icsHost);
9474 gameInfo.date = PGNDate();
9475 gameInfo.round = StrSave("-");
9477 /* Parse out names of players */
9478 while (*game == ' ') game++;
9480 while (*game != ' ') *p++ = *game++;
9482 gameInfo.white = StrSave(buf);
9483 while (*game == ' ') game++;
9485 while (*game != ' ' && *game != '\n') *p++ = *game++;
9487 gameInfo.black = StrSave(buf);
9490 boardIndex = blackPlaysFirst ? 1 : 0;
9493 yyboardindex = boardIndex;
9494 moveType = (ChessMove) Myylex();
9496 case IllegalMove: /* maybe suicide chess, etc. */
9497 if (appData.debugMode) {
9498 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9499 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9500 setbuf(debugFP, NULL);
9502 case WhitePromotion:
9503 case BlackPromotion:
9504 case WhiteNonPromotion:
9505 case BlackNonPromotion:
9508 case WhiteCapturesEnPassant:
9509 case BlackCapturesEnPassant:
9510 case WhiteKingSideCastle:
9511 case WhiteQueenSideCastle:
9512 case BlackKingSideCastle:
9513 case BlackQueenSideCastle:
9514 case WhiteKingSideCastleWild:
9515 case WhiteQueenSideCastleWild:
9516 case BlackKingSideCastleWild:
9517 case BlackQueenSideCastleWild:
9519 case WhiteHSideCastleFR:
9520 case WhiteASideCastleFR:
9521 case BlackHSideCastleFR:
9522 case BlackASideCastleFR:
9524 fromX = currentMoveString[0] - AAA;
9525 fromY = currentMoveString[1] - ONE;
9526 toX = currentMoveString[2] - AAA;
9527 toY = currentMoveString[3] - ONE;
9528 promoChar = currentMoveString[4];
9532 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9533 fromX = moveType == WhiteDrop ?
9534 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9535 (int) CharToPiece(ToLower(currentMoveString[0]));
9537 toX = currentMoveString[2] - AAA;
9538 toY = currentMoveString[3] - ONE;
9539 promoChar = NULLCHAR;
9543 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9544 if (appData.debugMode) {
9545 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9546 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9547 setbuf(debugFP, NULL);
9549 DisplayError(buf, 0);
9551 case ImpossibleMove:
9553 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9554 if (appData.debugMode) {
9555 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9556 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9557 setbuf(debugFP, NULL);
9559 DisplayError(buf, 0);
9562 if (boardIndex < backwardMostMove) {
9563 /* Oops, gap. How did that happen? */
9564 DisplayError(_("Gap in move list"), 0);
9567 backwardMostMove = blackPlaysFirst ? 1 : 0;
9568 if (boardIndex > forwardMostMove) {
9569 forwardMostMove = boardIndex;
9573 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9574 strcat(parseList[boardIndex-1], " ");
9575 strcat(parseList[boardIndex-1], yy_text);
9587 case GameUnfinished:
9588 if (gameMode == IcsExamining) {
9589 if (boardIndex < backwardMostMove) {
9590 /* Oops, gap. How did that happen? */
9593 backwardMostMove = blackPlaysFirst ? 1 : 0;
9596 gameInfo.result = moveType;
9597 p = strchr(yy_text, '{');
9598 if (p == NULL) p = strchr(yy_text, '(');
9601 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9603 q = strchr(p, *p == '{' ? '}' : ')');
9604 if (q != NULL) *q = NULLCHAR;
9607 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9608 gameInfo.resultDetails = StrSave(p);
9611 if (boardIndex >= forwardMostMove &&
9612 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9613 backwardMostMove = blackPlaysFirst ? 1 : 0;
9616 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9617 fromY, fromX, toY, toX, promoChar,
9618 parseList[boardIndex]);
9619 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9620 /* currentMoveString is set as a side-effect of yylex */
9621 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9622 strcat(moveList[boardIndex], "\n");
9624 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9625 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9631 if(gameInfo.variant != VariantShogi)
9632 strcat(parseList[boardIndex - 1], "+");
9636 strcat(parseList[boardIndex - 1], "#");
9643 /* Apply a move to the given board */
9645 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9647 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9648 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9650 /* [HGM] compute & store e.p. status and castling rights for new position */
9651 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9653 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9654 oldEP = (signed char)board[EP_STATUS];
9655 board[EP_STATUS] = EP_NONE;
9657 if (fromY == DROP_RANK) {
9659 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9660 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9663 piece = board[toY][toX] = (ChessSquare) fromX;
9668 if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9669 victim = board[killY][killX],
9670 board[killY][killX] = EmptySquare,
9671 board[EP_STATUS] = EP_CAPTURE;
9673 if( board[toY][toX] != EmptySquare ) {
9674 board[EP_STATUS] = EP_CAPTURE;
9675 if( (fromX != toX || fromY != toY) && // not igui!
9676 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9677 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
9678 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9682 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9683 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9684 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9686 if( board[fromY][fromX] == WhitePawn ) {
9687 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9688 board[EP_STATUS] = EP_PAWN_MOVE;
9690 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9691 gameInfo.variant != VariantBerolina || toX < fromX)
9692 board[EP_STATUS] = toX | berolina;
9693 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9694 gameInfo.variant != VariantBerolina || toX > fromX)
9695 board[EP_STATUS] = toX;
9698 if( board[fromY][fromX] == BlackPawn ) {
9699 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9700 board[EP_STATUS] = EP_PAWN_MOVE;
9701 if( toY-fromY== -2) {
9702 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9703 gameInfo.variant != VariantBerolina || toX < fromX)
9704 board[EP_STATUS] = toX | berolina;
9705 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9706 gameInfo.variant != VariantBerolina || toX > fromX)
9707 board[EP_STATUS] = toX;
9711 for(i=0; i<nrCastlingRights; i++) {
9712 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9713 board[CASTLING][i] == toX && castlingRank[i] == toY
9714 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9717 if(gameInfo.variant == VariantSChess) { // update virginity
9718 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9719 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9720 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9721 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9724 if (fromX == toX && fromY == toY) return;
9726 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9727 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9728 if(gameInfo.variant == VariantKnightmate)
9729 king += (int) WhiteUnicorn - (int) WhiteKing;
9731 /* Code added by Tord: */
9732 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9733 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9734 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9735 board[fromY][fromX] = EmptySquare;
9736 board[toY][toX] = EmptySquare;
9737 if((toX > fromX) != (piece == WhiteRook)) {
9738 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9740 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9742 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9743 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9744 board[fromY][fromX] = EmptySquare;
9745 board[toY][toX] = EmptySquare;
9746 if((toX > fromX) != (piece == BlackRook)) {
9747 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9749 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9751 /* End of code added by Tord */
9753 } else if (board[fromY][fromX] == king
9754 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9755 && toY == fromY && toX > fromX+1) {
9756 board[fromY][fromX] = EmptySquare;
9757 board[toY][toX] = king;
9758 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9759 board[fromY][BOARD_RGHT-1] = EmptySquare;
9760 } else if (board[fromY][fromX] == king
9761 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9762 && toY == fromY && toX < fromX-1) {
9763 board[fromY][fromX] = EmptySquare;
9764 board[toY][toX] = king;
9765 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9766 board[fromY][BOARD_LEFT] = EmptySquare;
9767 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9768 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9769 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9771 /* white pawn promotion */
9772 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9773 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9774 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9775 board[fromY][fromX] = EmptySquare;
9776 } else if ((fromY >= BOARD_HEIGHT>>1)
9777 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9779 && gameInfo.variant != VariantXiangqi
9780 && gameInfo.variant != VariantBerolina
9781 && (board[fromY][fromX] == WhitePawn)
9782 && (board[toY][toX] == EmptySquare)) {
9783 board[fromY][fromX] = EmptySquare;
9784 board[toY][toX] = WhitePawn;
9785 captured = board[toY - 1][toX];
9786 board[toY - 1][toX] = EmptySquare;
9787 } else if ((fromY == BOARD_HEIGHT-4)
9789 && gameInfo.variant == VariantBerolina
9790 && (board[fromY][fromX] == WhitePawn)
9791 && (board[toY][toX] == EmptySquare)) {
9792 board[fromY][fromX] = EmptySquare;
9793 board[toY][toX] = WhitePawn;
9794 if(oldEP & EP_BEROLIN_A) {
9795 captured = board[fromY][fromX-1];
9796 board[fromY][fromX-1] = EmptySquare;
9797 }else{ captured = board[fromY][fromX+1];
9798 board[fromY][fromX+1] = EmptySquare;
9800 } else if (board[fromY][fromX] == king
9801 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9802 && toY == fromY && toX > fromX+1) {
9803 board[fromY][fromX] = EmptySquare;
9804 board[toY][toX] = king;
9805 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9806 board[fromY][BOARD_RGHT-1] = EmptySquare;
9807 } else if (board[fromY][fromX] == king
9808 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9809 && toY == fromY && toX < fromX-1) {
9810 board[fromY][fromX] = EmptySquare;
9811 board[toY][toX] = king;
9812 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9813 board[fromY][BOARD_LEFT] = EmptySquare;
9814 } else if (fromY == 7 && fromX == 3
9815 && board[fromY][fromX] == BlackKing
9816 && toY == 7 && toX == 5) {
9817 board[fromY][fromX] = EmptySquare;
9818 board[toY][toX] = BlackKing;
9819 board[fromY][7] = EmptySquare;
9820 board[toY][4] = BlackRook;
9821 } else if (fromY == 7 && fromX == 3
9822 && board[fromY][fromX] == BlackKing
9823 && toY == 7 && toX == 1) {
9824 board[fromY][fromX] = EmptySquare;
9825 board[toY][toX] = BlackKing;
9826 board[fromY][0] = EmptySquare;
9827 board[toY][2] = BlackRook;
9828 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9829 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9830 && toY < promoRank && promoChar
9832 /* black pawn promotion */
9833 board[toY][toX] = CharToPiece(ToLower(promoChar));
9834 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9835 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9836 board[fromY][fromX] = EmptySquare;
9837 } else if ((fromY < BOARD_HEIGHT>>1)
9838 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9840 && gameInfo.variant != VariantXiangqi
9841 && gameInfo.variant != VariantBerolina
9842 && (board[fromY][fromX] == BlackPawn)
9843 && (board[toY][toX] == EmptySquare)) {
9844 board[fromY][fromX] = EmptySquare;
9845 board[toY][toX] = BlackPawn;
9846 captured = board[toY + 1][toX];
9847 board[toY + 1][toX] = EmptySquare;
9848 } else if ((fromY == 3)
9850 && gameInfo.variant == VariantBerolina
9851 && (board[fromY][fromX] == BlackPawn)
9852 && (board[toY][toX] == EmptySquare)) {
9853 board[fromY][fromX] = EmptySquare;
9854 board[toY][toX] = BlackPawn;
9855 if(oldEP & EP_BEROLIN_A) {
9856 captured = board[fromY][fromX-1];
9857 board[fromY][fromX-1] = EmptySquare;
9858 }else{ captured = board[fromY][fromX+1];
9859 board[fromY][fromX+1] = EmptySquare;
9862 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9863 board[fromY][fromX] = EmptySquare;
9864 board[toY][toX] = piece;
9868 if (gameInfo.holdingsWidth != 0) {
9870 /* !!A lot more code needs to be written to support holdings */
9871 /* [HGM] OK, so I have written it. Holdings are stored in the */
9872 /* penultimate board files, so they are automaticlly stored */
9873 /* in the game history. */
9874 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9875 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9876 /* Delete from holdings, by decreasing count */
9877 /* and erasing image if necessary */
9878 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9879 if(p < (int) BlackPawn) { /* white drop */
9880 p -= (int)WhitePawn;
9881 p = PieceToNumber((ChessSquare)p);
9882 if(p >= gameInfo.holdingsSize) p = 0;
9883 if(--board[p][BOARD_WIDTH-2] <= 0)
9884 board[p][BOARD_WIDTH-1] = EmptySquare;
9885 if((int)board[p][BOARD_WIDTH-2] < 0)
9886 board[p][BOARD_WIDTH-2] = 0;
9887 } else { /* black drop */
9888 p -= (int)BlackPawn;
9889 p = PieceToNumber((ChessSquare)p);
9890 if(p >= gameInfo.holdingsSize) p = 0;
9891 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9892 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9893 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9894 board[BOARD_HEIGHT-1-p][1] = 0;
9897 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9898 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9899 /* [HGM] holdings: Add to holdings, if holdings exist */
9900 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9901 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9902 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9905 if (p >= (int) BlackPawn) {
9906 p -= (int)BlackPawn;
9907 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9908 /* in Shogi restore piece to its original first */
9909 captured = (ChessSquare) (DEMOTED captured);
9912 p = PieceToNumber((ChessSquare)p);
9913 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9914 board[p][BOARD_WIDTH-2]++;
9915 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9917 p -= (int)WhitePawn;
9918 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9919 captured = (ChessSquare) (DEMOTED captured);
9922 p = PieceToNumber((ChessSquare)p);
9923 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9924 board[BOARD_HEIGHT-1-p][1]++;
9925 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9928 } else if (gameInfo.variant == VariantAtomic) {
9929 if (captured != EmptySquare) {
9931 for (y = toY-1; y <= toY+1; y++) {
9932 for (x = toX-1; x <= toX+1; x++) {
9933 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9934 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9935 board[y][x] = EmptySquare;
9939 board[toY][toX] = EmptySquare;
9942 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9943 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9945 if(promoChar == '+') {
9946 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9947 board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9948 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9949 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9950 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9951 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9952 board[toY][toX] = newPiece;
9954 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9955 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9956 // [HGM] superchess: take promotion piece out of holdings
9957 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9958 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9959 if(!--board[k][BOARD_WIDTH-2])
9960 board[k][BOARD_WIDTH-1] = EmptySquare;
9962 if(!--board[BOARD_HEIGHT-1-k][1])
9963 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9968 /* Updates forwardMostMove */
9970 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9972 int x = toX, y = toY;
9973 char *s = parseList[forwardMostMove];
9974 ChessSquare p = boards[forwardMostMove][toY][toX];
9975 // forwardMostMove++; // [HGM] bare: moved downstream
9977 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
9978 (void) CoordsToAlgebraic(boards[forwardMostMove],
9979 PosFlags(forwardMostMove),
9980 fromY, fromX, y, x, promoChar,
9982 if(killX >= 0 && killY >= 0)
9983 sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
9985 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9986 int timeLeft; static int lastLoadFlag=0; int king, piece;
9987 piece = boards[forwardMostMove][fromY][fromX];
9988 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9989 if(gameInfo.variant == VariantKnightmate)
9990 king += (int) WhiteUnicorn - (int) WhiteKing;
9991 if(forwardMostMove == 0) {
9992 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9993 fprintf(serverMoves, "%s;", UserName());
9994 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9995 fprintf(serverMoves, "%s;", second.tidy);
9996 fprintf(serverMoves, "%s;", first.tidy);
9997 if(gameMode == MachinePlaysWhite)
9998 fprintf(serverMoves, "%s;", UserName());
9999 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10000 fprintf(serverMoves, "%s;", second.tidy);
10001 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10002 lastLoadFlag = loadFlag;
10004 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10005 // print castling suffix
10006 if( toY == fromY && piece == king ) {
10008 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10010 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10013 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10014 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10015 boards[forwardMostMove][toY][toX] == EmptySquare
10016 && fromX != toX && fromY != toY)
10017 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10018 // promotion suffix
10019 if(promoChar != NULLCHAR) {
10020 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10021 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10022 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10023 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10026 char buf[MOVE_LEN*2], *p; int len;
10027 fprintf(serverMoves, "/%d/%d",
10028 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10029 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10030 else timeLeft = blackTimeRemaining/1000;
10031 fprintf(serverMoves, "/%d", timeLeft);
10032 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10033 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10034 if(p = strchr(buf, '=')) *p = NULLCHAR;
10035 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10036 fprintf(serverMoves, "/%s", buf);
10038 fflush(serverMoves);
10041 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10042 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10045 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10046 if (commentList[forwardMostMove+1] != NULL) {
10047 free(commentList[forwardMostMove+1]);
10048 commentList[forwardMostMove+1] = NULL;
10050 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10051 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10052 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10053 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10054 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10055 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10056 adjustedClock = FALSE;
10057 gameInfo.result = GameUnfinished;
10058 if (gameInfo.resultDetails != NULL) {
10059 free(gameInfo.resultDetails);
10060 gameInfo.resultDetails = NULL;
10062 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10063 moveList[forwardMostMove - 1]);
10064 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10070 if(gameInfo.variant != VariantShogi)
10071 strcat(parseList[forwardMostMove - 1], "+");
10075 strcat(parseList[forwardMostMove - 1], "#");
10080 /* Updates currentMove if not pausing */
10082 ShowMove (int fromX, int fromY, int toX, int toY)
10084 int instant = (gameMode == PlayFromGameFile) ?
10085 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10086 if(appData.noGUI) return;
10087 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10089 if (forwardMostMove == currentMove + 1) {
10090 AnimateMove(boards[forwardMostMove - 1],
10091 fromX, fromY, toX, toY);
10094 currentMove = forwardMostMove;
10097 killX = killY = -1; // [HGM] lion: used up
10099 if (instant) return;
10101 DisplayMove(currentMove - 1);
10102 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10103 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10104 SetHighlights(fromX, fromY, toX, toY);
10107 DrawPosition(FALSE, boards[currentMove]);
10108 DisplayBothClocks();
10109 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10113 SendEgtPath (ChessProgramState *cps)
10114 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10115 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10117 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10120 char c, *q = name+1, *r, *s;
10122 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10123 while(*p && *p != ',') *q++ = *p++;
10124 *q++ = ':'; *q = 0;
10125 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10126 strcmp(name, ",nalimov:") == 0 ) {
10127 // take nalimov path from the menu-changeable option first, if it is defined
10128 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10129 SendToProgram(buf,cps); // send egtbpath command for nalimov
10131 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10132 (s = StrStr(appData.egtFormats, name)) != NULL) {
10133 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10134 s = r = StrStr(s, ":") + 1; // beginning of path info
10135 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10136 c = *r; *r = 0; // temporarily null-terminate path info
10137 *--q = 0; // strip of trailig ':' from name
10138 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10140 SendToProgram(buf,cps); // send egtbpath command for this format
10142 if(*p == ',') p++; // read away comma to position for next format name
10147 NonStandardBoardSize ()
10149 /* [HGM] Awkward testing. Should really be a table */
10150 int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10151 if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10152 if( gameInfo.variant == VariantXiangqi )
10153 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10154 if( gameInfo.variant == VariantShogi )
10155 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10156 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10157 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10158 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10159 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10160 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10161 if( gameInfo.variant == VariantCourier )
10162 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10163 if( gameInfo.variant == VariantSuper )
10164 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10165 if( gameInfo.variant == VariantGreat )
10166 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10167 if( gameInfo.variant == VariantSChess )
10168 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10169 if( gameInfo.variant == VariantGrand )
10170 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10171 if( gameInfo.variant == VariantChu )
10172 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
10177 InitChessProgram (ChessProgramState *cps, int setup)
10178 /* setup needed to setup FRC opening position */
10180 char buf[MSG_SIZ], b[MSG_SIZ];
10181 if (appData.noChessProgram) return;
10182 hintRequested = FALSE;
10183 bookRequested = FALSE;
10185 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10186 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10187 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10188 if(cps->memSize) { /* [HGM] memory */
10189 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10190 SendToProgram(buf, cps);
10192 SendEgtPath(cps); /* [HGM] EGT */
10193 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10194 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10195 SendToProgram(buf, cps);
10198 SendToProgram(cps->initString, cps);
10199 if (gameInfo.variant != VariantNormal &&
10200 gameInfo.variant != VariantLoadable
10201 /* [HGM] also send variant if board size non-standard */
10202 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10204 char *v = VariantName(gameInfo.variant);
10205 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10206 /* [HGM] in protocol 1 we have to assume all variants valid */
10207 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10208 DisplayFatalError(buf, 0, 1);
10212 if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10213 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10214 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10215 /* [HGM] varsize: try first if this defiant size variant is specifically known */
10216 if(StrStr(cps->variants, b) == NULL) {
10217 // specific sized variant not known, check if general sizing allowed
10218 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10219 if(StrStr(cps->variants, "boardsize") == NULL) {
10220 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10221 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10222 DisplayFatalError(buf, 0, 1);
10225 /* [HGM] here we really should compare with the maximum supported board size */
10228 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10229 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10230 SendToProgram(buf, cps);
10232 currentlyInitializedVariant = gameInfo.variant;
10234 /* [HGM] send opening position in FRC to first engine */
10236 SendToProgram("force\n", cps);
10238 /* engine is now in force mode! Set flag to wake it up after first move. */
10239 setboardSpoiledMachineBlack = 1;
10242 if (cps->sendICS) {
10243 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10244 SendToProgram(buf, cps);
10246 cps->maybeThinking = FALSE;
10247 cps->offeredDraw = 0;
10248 if (!appData.icsActive) {
10249 SendTimeControl(cps, movesPerSession, timeControl,
10250 timeIncrement, appData.searchDepth,
10253 if (appData.showThinking
10254 // [HGM] thinking: four options require thinking output to be sent
10255 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10257 SendToProgram("post\n", cps);
10259 SendToProgram("hard\n", cps);
10260 if (!appData.ponderNextMove) {
10261 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10262 it without being sure what state we are in first. "hard"
10263 is not a toggle, so that one is OK.
10265 SendToProgram("easy\n", cps);
10267 if (cps->usePing) {
10268 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10269 SendToProgram(buf, cps);
10271 cps->initDone = TRUE;
10272 ClearEngineOutputPane(cps == &second);
10277 ResendOptions (ChessProgramState *cps)
10278 { // send the stored value of the options
10281 Option *opt = cps->option;
10282 for(i=0; i<cps->nrOptions; i++, opt++) {
10283 switch(opt->type) {
10287 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10290 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10293 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10299 SendToProgram(buf, cps);
10304 StartChessProgram (ChessProgramState *cps)
10309 if (appData.noChessProgram) return;
10310 cps->initDone = FALSE;
10312 if (strcmp(cps->host, "localhost") == 0) {
10313 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10314 } else if (*appData.remoteShell == NULLCHAR) {
10315 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10317 if (*appData.remoteUser == NULLCHAR) {
10318 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10321 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10322 cps->host, appData.remoteUser, cps->program);
10324 err = StartChildProcess(buf, "", &cps->pr);
10328 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10329 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10330 if(cps != &first) return;
10331 appData.noChessProgram = TRUE;
10334 // DisplayFatalError(buf, err, 1);
10335 // cps->pr = NoProc;
10336 // cps->isr = NULL;
10340 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10341 if (cps->protocolVersion > 1) {
10342 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10343 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10344 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10345 cps->comboCnt = 0; // and values of combo boxes
10347 SendToProgram(buf, cps);
10348 if(cps->reload) ResendOptions(cps);
10350 SendToProgram("xboard\n", cps);
10355 TwoMachinesEventIfReady P((void))
10357 static int curMess = 0;
10358 if (first.lastPing != first.lastPong) {
10359 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10360 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10363 if (second.lastPing != second.lastPong) {
10364 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10365 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10368 DisplayMessage("", ""); curMess = 0;
10369 TwoMachinesEvent();
10373 MakeName (char *template)
10377 static char buf[MSG_SIZ];
10381 clock = time((time_t *)NULL);
10382 tm = localtime(&clock);
10384 while(*p++ = *template++) if(p[-1] == '%') {
10385 switch(*template++) {
10386 case 0: *p = 0; return buf;
10387 case 'Y': i = tm->tm_year+1900; break;
10388 case 'y': i = tm->tm_year-100; break;
10389 case 'M': i = tm->tm_mon+1; break;
10390 case 'd': i = tm->tm_mday; break;
10391 case 'h': i = tm->tm_hour; break;
10392 case 'm': i = tm->tm_min; break;
10393 case 's': i = tm->tm_sec; break;
10396 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10402 CountPlayers (char *p)
10405 while(p = strchr(p, '\n')) p++, n++; // count participants
10410 WriteTourneyFile (char *results, FILE *f)
10411 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10412 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10413 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10414 // create a file with tournament description
10415 fprintf(f, "-participants {%s}\n", appData.participants);
10416 fprintf(f, "-seedBase %d\n", appData.seedBase);
10417 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10418 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10419 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10420 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10421 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10422 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10423 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10424 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10425 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10426 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10427 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10428 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10429 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10430 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10431 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10432 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10433 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10434 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10435 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10436 fprintf(f, "-smpCores %d\n", appData.smpCores);
10438 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10440 fprintf(f, "-mps %d\n", appData.movesPerSession);
10441 fprintf(f, "-tc %s\n", appData.timeControl);
10442 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10444 fprintf(f, "-results \"%s\"\n", results);
10449 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10452 Substitute (char *participants, int expunge)
10454 int i, changed, changes=0, nPlayers=0;
10455 char *p, *q, *r, buf[MSG_SIZ];
10456 if(participants == NULL) return;
10457 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10458 r = p = participants; q = appData.participants;
10459 while(*p && *p == *q) {
10460 if(*p == '\n') r = p+1, nPlayers++;
10463 if(*p) { // difference
10464 while(*p && *p++ != '\n');
10465 while(*q && *q++ != '\n');
10466 changed = nPlayers;
10467 changes = 1 + (strcmp(p, q) != 0);
10469 if(changes == 1) { // a single engine mnemonic was changed
10470 q = r; while(*q) nPlayers += (*q++ == '\n');
10471 p = buf; while(*r && (*p = *r++) != '\n') p++;
10473 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10474 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10475 if(mnemonic[i]) { // The substitute is valid
10477 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10478 flock(fileno(f), LOCK_EX);
10479 ParseArgsFromFile(f);
10480 fseek(f, 0, SEEK_SET);
10481 FREE(appData.participants); appData.participants = participants;
10482 if(expunge) { // erase results of replaced engine
10483 int len = strlen(appData.results), w, b, dummy;
10484 for(i=0; i<len; i++) {
10485 Pairing(i, nPlayers, &w, &b, &dummy);
10486 if((w == changed || b == changed) && appData.results[i] == '*') {
10487 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10492 for(i=0; i<len; i++) {
10493 Pairing(i, nPlayers, &w, &b, &dummy);
10494 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10497 WriteTourneyFile(appData.results, f);
10498 fclose(f); // release lock
10501 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10503 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10504 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10505 free(participants);
10510 CheckPlayers (char *participants)
10513 char buf[MSG_SIZ], *p;
10514 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10515 while(p = strchr(participants, '\n')) {
10517 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10519 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10521 DisplayError(buf, 0);
10525 participants = p + 1;
10531 CreateTourney (char *name)
10534 if(matchMode && strcmp(name, appData.tourneyFile)) {
10535 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10537 if(name[0] == NULLCHAR) {
10538 if(appData.participants[0])
10539 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10542 f = fopen(name, "r");
10543 if(f) { // file exists
10544 ASSIGN(appData.tourneyFile, name);
10545 ParseArgsFromFile(f); // parse it
10547 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10548 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10549 DisplayError(_("Not enough participants"), 0);
10552 if(CheckPlayers(appData.participants)) return 0;
10553 ASSIGN(appData.tourneyFile, name);
10554 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10555 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10558 appData.noChessProgram = FALSE;
10559 appData.clockMode = TRUE;
10565 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10567 char buf[MSG_SIZ], *p, *q;
10568 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10569 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10570 skip = !all && group[0]; // if group requested, we start in skip mode
10571 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10572 p = names; q = buf; header = 0;
10573 while(*p && *p != '\n') *q++ = *p++;
10575 if(*p == '\n') p++;
10576 if(buf[0] == '#') {
10577 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10578 depth++; // we must be entering a new group
10579 if(all) continue; // suppress printing group headers when complete list requested
10581 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10583 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10584 if(engineList[i]) free(engineList[i]);
10585 engineList[i] = strdup(buf);
10586 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10587 if(engineMnemonic[i]) free(engineMnemonic[i]);
10588 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10590 sscanf(q + 8, "%s", buf + strlen(buf));
10593 engineMnemonic[i] = strdup(buf);
10596 engineList[i] = engineMnemonic[i] = NULL;
10600 // following implemented as macro to avoid type limitations
10601 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10604 SwapEngines (int n)
10605 { // swap settings for first engine and other engine (so far only some selected options)
10610 SWAP(chessProgram, p)
10612 SWAP(hasOwnBookUCI, h)
10613 SWAP(protocolVersion, h)
10615 SWAP(scoreIsAbsolute, h)
10620 SWAP(engOptions, p)
10621 SWAP(engInitString, p)
10622 SWAP(computerString, p)
10624 SWAP(fenOverride, p)
10626 SWAP(accumulateTC, h)
10631 GetEngineLine (char *s, int n)
10635 extern char *icsNames;
10636 if(!s || !*s) return 0;
10637 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10638 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10639 if(!mnemonic[i]) return 0;
10640 if(n == 11) return 1; // just testing if there was a match
10641 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10642 if(n == 1) SwapEngines(n);
10643 ParseArgsFromString(buf);
10644 if(n == 1) SwapEngines(n);
10645 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10646 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10647 ParseArgsFromString(buf);
10653 SetPlayer (int player, char *p)
10654 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10656 char buf[MSG_SIZ], *engineName;
10657 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10658 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10659 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10661 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10662 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10663 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10664 ParseArgsFromString(buf);
10665 } else { // no engine with this nickname is installed!
10666 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10667 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10668 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10670 DisplayError(buf, 0);
10677 char *recentEngines;
10680 RecentEngineEvent (int nr)
10683 // SwapEngines(1); // bump first to second
10684 // ReplaceEngine(&second, 1); // and load it there
10685 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10686 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10687 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10688 ReplaceEngine(&first, 0);
10689 FloatToFront(&appData.recentEngineList, command[n]);
10694 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10695 { // determine players from game number
10696 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10698 if(appData.tourneyType == 0) {
10699 roundsPerCycle = (nPlayers - 1) | 1;
10700 pairingsPerRound = nPlayers / 2;
10701 } else if(appData.tourneyType > 0) {
10702 roundsPerCycle = nPlayers - appData.tourneyType;
10703 pairingsPerRound = appData.tourneyType;
10705 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10706 gamesPerCycle = gamesPerRound * roundsPerCycle;
10707 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10708 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10709 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10710 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10711 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10712 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10714 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10715 if(appData.roundSync) *syncInterval = gamesPerRound;
10717 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10719 if(appData.tourneyType == 0) {
10720 if(curPairing == (nPlayers-1)/2 ) {
10721 *whitePlayer = curRound;
10722 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10724 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10725 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10726 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10727 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10729 } else if(appData.tourneyType > 1) {
10730 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10731 *whitePlayer = curRound + appData.tourneyType;
10732 } else if(appData.tourneyType > 0) {
10733 *whitePlayer = curPairing;
10734 *blackPlayer = curRound + appData.tourneyType;
10737 // take care of white/black alternation per round.
10738 // For cycles and games this is already taken care of by default, derived from matchGame!
10739 return curRound & 1;
10743 NextTourneyGame (int nr, int *swapColors)
10744 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10746 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10748 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10749 tf = fopen(appData.tourneyFile, "r");
10750 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10751 ParseArgsFromFile(tf); fclose(tf);
10752 InitTimeControls(); // TC might be altered from tourney file
10754 nPlayers = CountPlayers(appData.participants); // count participants
10755 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10756 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10759 p = q = appData.results;
10760 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10761 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10762 DisplayMessage(_("Waiting for other game(s)"),"");
10763 waitingForGame = TRUE;
10764 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10767 waitingForGame = FALSE;
10770 if(appData.tourneyType < 0) {
10771 if(nr>=0 && !pairingReceived) {
10773 if(pairing.pr == NoProc) {
10774 if(!appData.pairingEngine[0]) {
10775 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10778 StartChessProgram(&pairing); // starts the pairing engine
10780 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10781 SendToProgram(buf, &pairing);
10782 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10783 SendToProgram(buf, &pairing);
10784 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10786 pairingReceived = 0; // ... so we continue here
10788 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10789 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10790 matchGame = 1; roundNr = nr / syncInterval + 1;
10793 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10795 // redefine engines, engine dir, etc.
10796 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10797 if(first.pr == NoProc) {
10798 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10799 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10801 if(second.pr == NoProc) {
10803 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10804 SwapEngines(1); // and make that valid for second engine by swapping
10805 InitEngine(&second, 1);
10807 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10808 UpdateLogos(FALSE); // leave display to ModeHiglight()
10814 { // performs game initialization that does not invoke engines, and then tries to start the game
10815 int res, firstWhite, swapColors = 0;
10816 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10817 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
10819 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10820 if(strcmp(buf, currentDebugFile)) { // name has changed
10821 FILE *f = fopen(buf, "w");
10822 if(f) { // if opening the new file failed, just keep using the old one
10823 ASSIGN(currentDebugFile, buf);
10827 if(appData.serverFileName) {
10828 if(serverFP) fclose(serverFP);
10829 serverFP = fopen(appData.serverFileName, "w");
10830 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10831 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10835 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10836 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10837 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10838 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10839 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10840 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10841 Reset(FALSE, first.pr != NoProc);
10842 res = LoadGameOrPosition(matchGame); // setup game
10843 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10844 if(!res) return; // abort when bad game/pos file
10845 TwoMachinesEvent();
10849 UserAdjudicationEvent (int result)
10851 ChessMove gameResult = GameIsDrawn;
10854 gameResult = WhiteWins;
10856 else if( result < 0 ) {
10857 gameResult = BlackWins;
10860 if( gameMode == TwoMachinesPlay ) {
10861 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10866 // [HGM] save: calculate checksum of game to make games easily identifiable
10868 StringCheckSum (char *s)
10871 if(s==NULL) return 0;
10872 while(*s) i = i*259 + *s++;
10880 for(i=backwardMostMove; i<forwardMostMove; i++) {
10881 sum += pvInfoList[i].depth;
10882 sum += StringCheckSum(parseList[i]);
10883 sum += StringCheckSum(commentList[i]);
10886 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10887 return sum + StringCheckSum(commentList[i]);
10888 } // end of save patch
10891 GameEnds (ChessMove result, char *resultDetails, int whosays)
10893 GameMode nextGameMode;
10895 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10897 if(endingGame) return; /* [HGM] crash: forbid recursion */
10899 if(twoBoards) { // [HGM] dual: switch back to one board
10900 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10901 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10903 if (appData.debugMode) {
10904 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10905 result, resultDetails ? resultDetails : "(null)", whosays);
10908 fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10910 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10912 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10913 /* If we are playing on ICS, the server decides when the
10914 game is over, but the engine can offer to draw, claim
10918 if (appData.zippyPlay && first.initDone) {
10919 if (result == GameIsDrawn) {
10920 /* In case draw still needs to be claimed */
10921 SendToICS(ics_prefix);
10922 SendToICS("draw\n");
10923 } else if (StrCaseStr(resultDetails, "resign")) {
10924 SendToICS(ics_prefix);
10925 SendToICS("resign\n");
10929 endingGame = 0; /* [HGM] crash */
10933 /* If we're loading the game from a file, stop */
10934 if (whosays == GE_FILE) {
10935 (void) StopLoadGameTimer();
10939 /* Cancel draw offers */
10940 first.offeredDraw = second.offeredDraw = 0;
10942 /* If this is an ICS game, only ICS can really say it's done;
10943 if not, anyone can. */
10944 isIcsGame = (gameMode == IcsPlayingWhite ||
10945 gameMode == IcsPlayingBlack ||
10946 gameMode == IcsObserving ||
10947 gameMode == IcsExamining);
10949 if (!isIcsGame || whosays == GE_ICS) {
10950 /* OK -- not an ICS game, or ICS said it was done */
10952 if (!isIcsGame && !appData.noChessProgram)
10953 SetUserThinkingEnables();
10955 /* [HGM] if a machine claims the game end we verify this claim */
10956 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10957 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10959 ChessMove trueResult = (ChessMove) -1;
10961 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10962 first.twoMachinesColor[0] :
10963 second.twoMachinesColor[0] ;
10965 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10966 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10967 /* [HGM] verify: engine mate claims accepted if they were flagged */
10968 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10970 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10971 /* [HGM] verify: engine mate claims accepted if they were flagged */
10972 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10974 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10975 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10978 // now verify win claims, but not in drop games, as we don't understand those yet
10979 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10980 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10981 (result == WhiteWins && claimer == 'w' ||
10982 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10983 if (appData.debugMode) {
10984 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10985 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10987 if(result != trueResult) {
10988 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10989 result = claimer == 'w' ? BlackWins : WhiteWins;
10990 resultDetails = buf;
10993 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10994 && (forwardMostMove <= backwardMostMove ||
10995 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10996 (claimer=='b')==(forwardMostMove&1))
10998 /* [HGM] verify: draws that were not flagged are false claims */
10999 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11000 result = claimer == 'w' ? BlackWins : WhiteWins;
11001 resultDetails = buf;
11003 /* (Claiming a loss is accepted no questions asked!) */
11004 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11005 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11006 result = GameUnfinished;
11007 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11009 /* [HGM] bare: don't allow bare King to win */
11010 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11011 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11012 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11013 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11014 && result != GameIsDrawn)
11015 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11016 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11017 int p = (signed char)boards[forwardMostMove][i][j] - color;
11018 if(p >= 0 && p <= (int)WhiteKing) k++;
11020 if (appData.debugMode) {
11021 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11022 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11025 result = GameIsDrawn;
11026 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11027 resultDetails = buf;
11033 if(serverMoves != NULL && !loadFlag) { char c = '=';
11034 if(result==WhiteWins) c = '+';
11035 if(result==BlackWins) c = '-';
11036 if(resultDetails != NULL)
11037 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11039 if (resultDetails != NULL) {
11040 gameInfo.result = result;
11041 gameInfo.resultDetails = StrSave(resultDetails);
11043 /* display last move only if game was not loaded from file */
11044 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11045 DisplayMove(currentMove - 1);
11047 if (forwardMostMove != 0) {
11048 if (gameMode != PlayFromGameFile && gameMode != EditGame
11049 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11051 if (*appData.saveGameFile != NULLCHAR) {
11052 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11053 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11055 SaveGameToFile(appData.saveGameFile, TRUE);
11056 } else if (appData.autoSaveGames) {
11057 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11059 if (*appData.savePositionFile != NULLCHAR) {
11060 SavePositionToFile(appData.savePositionFile);
11062 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11066 /* Tell program how game ended in case it is learning */
11067 /* [HGM] Moved this to after saving the PGN, just in case */
11068 /* engine died and we got here through time loss. In that */
11069 /* case we will get a fatal error writing the pipe, which */
11070 /* would otherwise lose us the PGN. */
11071 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11072 /* output during GameEnds should never be fatal anymore */
11073 if (gameMode == MachinePlaysWhite ||
11074 gameMode == MachinePlaysBlack ||
11075 gameMode == TwoMachinesPlay ||
11076 gameMode == IcsPlayingWhite ||
11077 gameMode == IcsPlayingBlack ||
11078 gameMode == BeginningOfGame) {
11080 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11082 if (first.pr != NoProc) {
11083 SendToProgram(buf, &first);
11085 if (second.pr != NoProc &&
11086 gameMode == TwoMachinesPlay) {
11087 SendToProgram(buf, &second);
11092 if (appData.icsActive) {
11093 if (appData.quietPlay &&
11094 (gameMode == IcsPlayingWhite ||
11095 gameMode == IcsPlayingBlack)) {
11096 SendToICS(ics_prefix);
11097 SendToICS("set shout 1\n");
11099 nextGameMode = IcsIdle;
11100 ics_user_moved = FALSE;
11101 /* clean up premove. It's ugly when the game has ended and the
11102 * premove highlights are still on the board.
11105 gotPremove = FALSE;
11106 ClearPremoveHighlights();
11107 DrawPosition(FALSE, boards[currentMove]);
11109 if (whosays == GE_ICS) {
11112 if (gameMode == IcsPlayingWhite)
11114 else if(gameMode == IcsPlayingBlack)
11115 PlayIcsLossSound();
11118 if (gameMode == IcsPlayingBlack)
11120 else if(gameMode == IcsPlayingWhite)
11121 PlayIcsLossSound();
11124 PlayIcsDrawSound();
11127 PlayIcsUnfinishedSound();
11130 if(appData.quitNext) { ExitEvent(0); return; }
11131 } else if (gameMode == EditGame ||
11132 gameMode == PlayFromGameFile ||
11133 gameMode == AnalyzeMode ||
11134 gameMode == AnalyzeFile) {
11135 nextGameMode = gameMode;
11137 nextGameMode = EndOfGame;
11142 nextGameMode = gameMode;
11145 if (appData.noChessProgram) {
11146 gameMode = nextGameMode;
11148 endingGame = 0; /* [HGM] crash */
11153 /* Put first chess program into idle state */
11154 if (first.pr != NoProc &&
11155 (gameMode == MachinePlaysWhite ||
11156 gameMode == MachinePlaysBlack ||
11157 gameMode == TwoMachinesPlay ||
11158 gameMode == IcsPlayingWhite ||
11159 gameMode == IcsPlayingBlack ||
11160 gameMode == BeginningOfGame)) {
11161 SendToProgram("force\n", &first);
11162 if (first.usePing) {
11164 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11165 SendToProgram(buf, &first);
11168 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11169 /* Kill off first chess program */
11170 if (first.isr != NULL)
11171 RemoveInputSource(first.isr);
11174 if (first.pr != NoProc) {
11176 DoSleep( appData.delayBeforeQuit );
11177 SendToProgram("quit\n", &first);
11178 DoSleep( appData.delayAfterQuit );
11179 DestroyChildProcess(first.pr, first.useSigterm);
11180 first.reload = TRUE;
11184 if (second.reuse) {
11185 /* Put second chess program into idle state */
11186 if (second.pr != NoProc &&
11187 gameMode == TwoMachinesPlay) {
11188 SendToProgram("force\n", &second);
11189 if (second.usePing) {
11191 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11192 SendToProgram(buf, &second);
11195 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11196 /* Kill off second chess program */
11197 if (second.isr != NULL)
11198 RemoveInputSource(second.isr);
11201 if (second.pr != NoProc) {
11202 DoSleep( appData.delayBeforeQuit );
11203 SendToProgram("quit\n", &second);
11204 DoSleep( appData.delayAfterQuit );
11205 DestroyChildProcess(second.pr, second.useSigterm);
11206 second.reload = TRUE;
11208 second.pr = NoProc;
11211 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11212 char resChar = '=';
11216 if (first.twoMachinesColor[0] == 'w') {
11219 second.matchWins++;
11224 if (first.twoMachinesColor[0] == 'b') {
11227 second.matchWins++;
11230 case GameUnfinished:
11236 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11237 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11238 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11239 ReserveGame(nextGame, resChar); // sets nextGame
11240 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11241 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11242 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11244 if (nextGame <= appData.matchGames && !abortMatch) {
11245 gameMode = nextGameMode;
11246 matchGame = nextGame; // this will be overruled in tourney mode!
11247 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11248 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11249 endingGame = 0; /* [HGM] crash */
11252 gameMode = nextGameMode;
11253 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11254 first.tidy, second.tidy,
11255 first.matchWins, second.matchWins,
11256 appData.matchGames - (first.matchWins + second.matchWins));
11257 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11258 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11259 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11260 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11261 first.twoMachinesColor = "black\n";
11262 second.twoMachinesColor = "white\n";
11264 first.twoMachinesColor = "white\n";
11265 second.twoMachinesColor = "black\n";
11269 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11270 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11272 gameMode = nextGameMode;
11274 endingGame = 0; /* [HGM] crash */
11275 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11276 if(matchMode == TRUE) { // match through command line: exit with or without popup
11278 ToNrEvent(forwardMostMove);
11279 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11281 } else DisplayFatalError(buf, 0, 0);
11282 } else { // match through menu; just stop, with or without popup
11283 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11286 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11287 } else DisplayNote(buf);
11289 if(ranking) free(ranking);
11293 /* Assumes program was just initialized (initString sent).
11294 Leaves program in force mode. */
11296 FeedMovesToProgram (ChessProgramState *cps, int upto)
11300 if (appData.debugMode)
11301 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11302 startedFromSetupPosition ? "position and " : "",
11303 backwardMostMove, upto, cps->which);
11304 if(currentlyInitializedVariant != gameInfo.variant) {
11306 // [HGM] variantswitch: make engine aware of new variant
11307 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11308 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11309 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11310 SendToProgram(buf, cps);
11311 currentlyInitializedVariant = gameInfo.variant;
11313 SendToProgram("force\n", cps);
11314 if (startedFromSetupPosition) {
11315 SendBoard(cps, backwardMostMove);
11316 if (appData.debugMode) {
11317 fprintf(debugFP, "feedMoves\n");
11320 for (i = backwardMostMove; i < upto; i++) {
11321 SendMoveToProgram(i, cps);
11327 ResurrectChessProgram ()
11329 /* The chess program may have exited.
11330 If so, restart it and feed it all the moves made so far. */
11331 static int doInit = 0;
11333 if (appData.noChessProgram) return 1;
11335 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11336 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11337 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11338 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11340 if (first.pr != NoProc) return 1;
11341 StartChessProgram(&first);
11343 InitChessProgram(&first, FALSE);
11344 FeedMovesToProgram(&first, currentMove);
11346 if (!first.sendTime) {
11347 /* can't tell gnuchess what its clock should read,
11348 so we bow to its notion. */
11350 timeRemaining[0][currentMove] = whiteTimeRemaining;
11351 timeRemaining[1][currentMove] = blackTimeRemaining;
11354 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11355 appData.icsEngineAnalyze) && first.analysisSupport) {
11356 SendToProgram("analyze\n", &first);
11357 first.analyzing = TRUE;
11363 * Button procedures
11366 Reset (int redraw, int init)
11370 if (appData.debugMode) {
11371 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11372 redraw, init, gameMode);
11374 CleanupTail(); // [HGM] vari: delete any stored variations
11375 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11376 pausing = pauseExamInvalid = FALSE;
11377 startedFromSetupPosition = blackPlaysFirst = FALSE;
11379 whiteFlag = blackFlag = FALSE;
11380 userOfferedDraw = FALSE;
11381 hintRequested = bookRequested = FALSE;
11382 first.maybeThinking = FALSE;
11383 second.maybeThinking = FALSE;
11384 first.bookSuspend = FALSE; // [HGM] book
11385 second.bookSuspend = FALSE;
11386 thinkOutput[0] = NULLCHAR;
11387 lastHint[0] = NULLCHAR;
11388 ClearGameInfo(&gameInfo);
11389 gameInfo.variant = StringToVariant(appData.variant);
11390 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11391 ics_user_moved = ics_clock_paused = FALSE;
11392 ics_getting_history = H_FALSE;
11394 white_holding[0] = black_holding[0] = NULLCHAR;
11395 ClearProgramStats();
11396 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11400 flipView = appData.flipView;
11401 ClearPremoveHighlights();
11402 gotPremove = FALSE;
11403 alarmSounded = FALSE;
11404 killX = killY = -1; // [HGM] lion
11406 GameEnds(EndOfFile, NULL, GE_PLAYER);
11407 if(appData.serverMovesName != NULL) {
11408 /* [HGM] prepare to make moves file for broadcasting */
11409 clock_t t = clock();
11410 if(serverMoves != NULL) fclose(serverMoves);
11411 serverMoves = fopen(appData.serverMovesName, "r");
11412 if(serverMoves != NULL) {
11413 fclose(serverMoves);
11414 /* delay 15 sec before overwriting, so all clients can see end */
11415 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11417 serverMoves = fopen(appData.serverMovesName, "w");
11421 gameMode = BeginningOfGame;
11423 if(appData.icsActive) gameInfo.variant = VariantNormal;
11424 currentMove = forwardMostMove = backwardMostMove = 0;
11425 MarkTargetSquares(1);
11426 InitPosition(redraw);
11427 for (i = 0; i < MAX_MOVES; i++) {
11428 if (commentList[i] != NULL) {
11429 free(commentList[i]);
11430 commentList[i] = NULL;
11434 timeRemaining[0][0] = whiteTimeRemaining;
11435 timeRemaining[1][0] = blackTimeRemaining;
11437 if (first.pr == NoProc) {
11438 StartChessProgram(&first);
11441 InitChessProgram(&first, startedFromSetupPosition);
11444 DisplayMessage("", "");
11445 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11446 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11447 ClearMap(); // [HGM] exclude: invalidate map
11451 AutoPlayGameLoop ()
11454 if (!AutoPlayOneMove())
11456 if (matchMode || appData.timeDelay == 0)
11458 if (appData.timeDelay < 0)
11460 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11468 ReloadGame(1); // next game
11474 int fromX, fromY, toX, toY;
11476 if (appData.debugMode) {
11477 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11480 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11483 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11484 pvInfoList[currentMove].depth = programStats.depth;
11485 pvInfoList[currentMove].score = programStats.score;
11486 pvInfoList[currentMove].time = 0;
11487 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11488 else { // append analysis of final position as comment
11490 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11491 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11493 programStats.depth = 0;
11496 if (currentMove >= forwardMostMove) {
11497 if(gameMode == AnalyzeFile) {
11498 if(appData.loadGameIndex == -1) {
11499 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11500 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11502 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11505 // gameMode = EndOfGame;
11506 // ModeHighlight();
11508 /* [AS] Clear current move marker at the end of a game */
11509 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11514 toX = moveList[currentMove][2] - AAA;
11515 toY = moveList[currentMove][3] - ONE;
11517 if (moveList[currentMove][1] == '@') {
11518 if (appData.highlightLastMove) {
11519 SetHighlights(-1, -1, toX, toY);
11522 fromX = moveList[currentMove][0] - AAA;
11523 fromY = moveList[currentMove][1] - ONE;
11525 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11527 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11529 if (appData.highlightLastMove) {
11530 SetHighlights(fromX, fromY, toX, toY);
11533 DisplayMove(currentMove);
11534 SendMoveToProgram(currentMove++, &first);
11535 DisplayBothClocks();
11536 DrawPosition(FALSE, boards[currentMove]);
11537 // [HGM] PV info: always display, routine tests if empty
11538 DisplayComment(currentMove - 1, commentList[currentMove]);
11544 LoadGameOneMove (ChessMove readAhead)
11546 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11547 char promoChar = NULLCHAR;
11548 ChessMove moveType;
11549 char move[MSG_SIZ];
11552 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11553 gameMode != AnalyzeMode && gameMode != Training) {
11558 yyboardindex = forwardMostMove;
11559 if (readAhead != EndOfFile) {
11560 moveType = readAhead;
11562 if (gameFileFP == NULL)
11564 moveType = (ChessMove) Myylex();
11568 switch (moveType) {
11570 if (appData.debugMode)
11571 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11574 /* append the comment but don't display it */
11575 AppendComment(currentMove, p, FALSE);
11578 case WhiteCapturesEnPassant:
11579 case BlackCapturesEnPassant:
11580 case WhitePromotion:
11581 case BlackPromotion:
11582 case WhiteNonPromotion:
11583 case BlackNonPromotion:
11586 case WhiteKingSideCastle:
11587 case WhiteQueenSideCastle:
11588 case BlackKingSideCastle:
11589 case BlackQueenSideCastle:
11590 case WhiteKingSideCastleWild:
11591 case WhiteQueenSideCastleWild:
11592 case BlackKingSideCastleWild:
11593 case BlackQueenSideCastleWild:
11595 case WhiteHSideCastleFR:
11596 case WhiteASideCastleFR:
11597 case BlackHSideCastleFR:
11598 case BlackASideCastleFR:
11600 if (appData.debugMode)
11601 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11602 fromX = currentMoveString[0] - AAA;
11603 fromY = currentMoveString[1] - ONE;
11604 toX = currentMoveString[2] - AAA;
11605 toY = currentMoveString[3] - ONE;
11606 promoChar = currentMoveString[4];
11607 if(promoChar == ';') promoChar = NULLCHAR;
11612 if (appData.debugMode)
11613 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11614 fromX = moveType == WhiteDrop ?
11615 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11616 (int) CharToPiece(ToLower(currentMoveString[0]));
11618 toX = currentMoveString[2] - AAA;
11619 toY = currentMoveString[3] - ONE;
11625 case GameUnfinished:
11626 if (appData.debugMode)
11627 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11628 p = strchr(yy_text, '{');
11629 if (p == NULL) p = strchr(yy_text, '(');
11632 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11634 q = strchr(p, *p == '{' ? '}' : ')');
11635 if (q != NULL) *q = NULLCHAR;
11638 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11639 GameEnds(moveType, p, GE_FILE);
11641 if (cmailMsgLoaded) {
11643 flipView = WhiteOnMove(currentMove);
11644 if (moveType == GameUnfinished) flipView = !flipView;
11645 if (appData.debugMode)
11646 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11651 if (appData.debugMode)
11652 fprintf(debugFP, "Parser hit end of file\n");
11653 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11659 if (WhiteOnMove(currentMove)) {
11660 GameEnds(BlackWins, "Black mates", GE_FILE);
11662 GameEnds(WhiteWins, "White mates", GE_FILE);
11666 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11672 case MoveNumberOne:
11673 if (lastLoadGameStart == GNUChessGame) {
11674 /* GNUChessGames have numbers, but they aren't move numbers */
11675 if (appData.debugMode)
11676 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11677 yy_text, (int) moveType);
11678 return LoadGameOneMove(EndOfFile); /* tail recursion */
11680 /* else fall thru */
11685 /* Reached start of next game in file */
11686 if (appData.debugMode)
11687 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11688 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11694 if (WhiteOnMove(currentMove)) {
11695 GameEnds(BlackWins, "Black mates", GE_FILE);
11697 GameEnds(WhiteWins, "White mates", GE_FILE);
11701 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11707 case PositionDiagram: /* should not happen; ignore */
11708 case ElapsedTime: /* ignore */
11709 case NAG: /* ignore */
11710 if (appData.debugMode)
11711 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11712 yy_text, (int) moveType);
11713 return LoadGameOneMove(EndOfFile); /* tail recursion */
11716 if (appData.testLegality) {
11717 if (appData.debugMode)
11718 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11719 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11720 (forwardMostMove / 2) + 1,
11721 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11722 DisplayError(move, 0);
11725 if (appData.debugMode)
11726 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11727 yy_text, currentMoveString);
11728 fromX = currentMoveString[0] - AAA;
11729 fromY = currentMoveString[1] - ONE;
11730 toX = currentMoveString[2] - AAA;
11731 toY = currentMoveString[3] - ONE;
11732 promoChar = currentMoveString[4];
11736 case AmbiguousMove:
11737 if (appData.debugMode)
11738 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11739 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11740 (forwardMostMove / 2) + 1,
11741 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11742 DisplayError(move, 0);
11747 case ImpossibleMove:
11748 if (appData.debugMode)
11749 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11750 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11751 (forwardMostMove / 2) + 1,
11752 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11753 DisplayError(move, 0);
11759 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11760 DrawPosition(FALSE, boards[currentMove]);
11761 DisplayBothClocks();
11762 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11763 DisplayComment(currentMove - 1, commentList[currentMove]);
11765 (void) StopLoadGameTimer();
11767 cmailOldMove = forwardMostMove;
11770 /* currentMoveString is set as a side-effect of yylex */
11772 thinkOutput[0] = NULLCHAR;
11773 MakeMove(fromX, fromY, toX, toY, promoChar);
11774 killX = killY = -1; // [HGM] lion: used up
11775 currentMove = forwardMostMove;
11780 /* Load the nth game from the given file */
11782 LoadGameFromFile (char *filename, int n, char *title, int useList)
11787 if (strcmp(filename, "-") == 0) {
11791 f = fopen(filename, "rb");
11793 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11794 DisplayError(buf, errno);
11798 if (fseek(f, 0, 0) == -1) {
11799 /* f is not seekable; probably a pipe */
11802 if (useList && n == 0) {
11803 int error = GameListBuild(f);
11805 DisplayError(_("Cannot build game list"), error);
11806 } else if (!ListEmpty(&gameList) &&
11807 ((ListGame *) gameList.tailPred)->number > 1) {
11808 GameListPopUp(f, title);
11815 return LoadGame(f, n, title, FALSE);
11820 MakeRegisteredMove ()
11822 int fromX, fromY, toX, toY;
11824 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11825 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11828 if (appData.debugMode)
11829 fprintf(debugFP, "Restoring %s for game %d\n",
11830 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11832 thinkOutput[0] = NULLCHAR;
11833 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11834 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11835 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11836 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11837 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11838 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11839 MakeMove(fromX, fromY, toX, toY, promoChar);
11840 ShowMove(fromX, fromY, toX, toY);
11842 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11849 if (WhiteOnMove(currentMove)) {
11850 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11852 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11857 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11864 if (WhiteOnMove(currentMove)) {
11865 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11867 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11872 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11883 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11885 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11889 if (gameNumber > nCmailGames) {
11890 DisplayError(_("No more games in this message"), 0);
11893 if (f == lastLoadGameFP) {
11894 int offset = gameNumber - lastLoadGameNumber;
11896 cmailMsg[0] = NULLCHAR;
11897 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11898 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11899 nCmailMovesRegistered--;
11901 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11902 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11903 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11906 if (! RegisterMove()) return FALSE;
11910 retVal = LoadGame(f, gameNumber, title, useList);
11912 /* Make move registered during previous look at this game, if any */
11913 MakeRegisteredMove();
11915 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11916 commentList[currentMove]
11917 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11918 DisplayComment(currentMove - 1, commentList[currentMove]);
11924 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11926 ReloadGame (int offset)
11928 int gameNumber = lastLoadGameNumber + offset;
11929 if (lastLoadGameFP == NULL) {
11930 DisplayError(_("No game has been loaded yet"), 0);
11933 if (gameNumber <= 0) {
11934 DisplayError(_("Can't back up any further"), 0);
11937 if (cmailMsgLoaded) {
11938 return CmailLoadGame(lastLoadGameFP, gameNumber,
11939 lastLoadGameTitle, lastLoadGameUseList);
11941 return LoadGame(lastLoadGameFP, gameNumber,
11942 lastLoadGameTitle, lastLoadGameUseList);
11946 int keys[EmptySquare+1];
11949 PositionMatches (Board b1, Board b2)
11952 switch(appData.searchMode) {
11953 case 1: return CompareWithRights(b1, b2);
11955 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11956 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11960 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11961 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11962 sum += keys[b1[r][f]] - keys[b2[r][f]];
11966 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11967 sum += keys[b1[r][f]] - keys[b2[r][f]];
11979 int pieceList[256], quickBoard[256];
11980 ChessSquare pieceType[256] = { EmptySquare };
11981 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11982 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11983 int soughtTotal, turn;
11984 Boolean epOK, flipSearch;
11987 unsigned char piece, to;
11990 #define DSIZE (250000)
11992 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11993 Move *moveDatabase = initialSpace;
11994 unsigned int movePtr, dataSize = DSIZE;
11997 MakePieceList (Board board, int *counts)
11999 int r, f, n=Q_PROMO, total=0;
12000 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12001 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12002 int sq = f + (r<<4);
12003 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12004 quickBoard[sq] = ++n;
12006 pieceType[n] = board[r][f];
12007 counts[board[r][f]]++;
12008 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12009 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12013 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12018 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12020 int sq = fromX + (fromY<<4);
12021 int piece = quickBoard[sq];
12022 quickBoard[sq] = 0;
12023 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12024 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12025 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12026 moveDatabase[movePtr++].piece = Q_WCASTL;
12027 quickBoard[sq] = piece;
12028 piece = quickBoard[from]; quickBoard[from] = 0;
12029 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12031 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12032 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12033 moveDatabase[movePtr++].piece = Q_BCASTL;
12034 quickBoard[sq] = piece;
12035 piece = quickBoard[from]; quickBoard[from] = 0;
12036 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12038 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12039 quickBoard[(fromY<<4)+toX] = 0;
12040 moveDatabase[movePtr].piece = Q_EP;
12041 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12042 moveDatabase[movePtr].to = sq;
12044 if(promoPiece != pieceType[piece]) {
12045 moveDatabase[movePtr++].piece = Q_PROMO;
12046 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12048 moveDatabase[movePtr].piece = piece;
12049 quickBoard[sq] = piece;
12054 PackGame (Board board)
12056 Move *newSpace = NULL;
12057 moveDatabase[movePtr].piece = 0; // terminate previous game
12058 if(movePtr > dataSize) {
12059 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12060 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12061 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12064 Move *p = moveDatabase, *q = newSpace;
12065 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12066 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12067 moveDatabase = newSpace;
12068 } else { // calloc failed, we must be out of memory. Too bad...
12069 dataSize = 0; // prevent calloc events for all subsequent games
12070 return 0; // and signal this one isn't cached
12074 MakePieceList(board, counts);
12079 QuickCompare (Board board, int *minCounts, int *maxCounts)
12080 { // compare according to search mode
12082 switch(appData.searchMode)
12084 case 1: // exact position match
12085 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12086 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12087 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12090 case 2: // can have extra material on empty squares
12091 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12092 if(board[r][f] == EmptySquare) continue;
12093 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12096 case 3: // material with exact Pawn structure
12097 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12098 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12099 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12100 } // fall through to material comparison
12101 case 4: // exact material
12102 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12104 case 6: // material range with given imbalance
12105 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12106 // fall through to range comparison
12107 case 5: // material range
12108 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12114 QuickScan (Board board, Move *move)
12115 { // reconstruct game,and compare all positions in it
12116 int cnt=0, stretch=0, total = MakePieceList(board, counts);
12118 int piece = move->piece;
12119 int to = move->to, from = pieceList[piece];
12120 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12121 if(!piece) return -1;
12122 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12123 piece = (++move)->piece;
12124 from = pieceList[piece];
12125 counts[pieceType[piece]]--;
12126 pieceType[piece] = (ChessSquare) move->to;
12127 counts[move->to]++;
12128 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12129 counts[pieceType[quickBoard[to]]]--;
12130 quickBoard[to] = 0; total--;
12133 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12134 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12135 from = pieceList[piece]; // so this must be King
12136 quickBoard[from] = 0;
12137 pieceList[piece] = to;
12138 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12139 quickBoard[from] = 0; // rook
12140 quickBoard[to] = piece;
12141 to = move->to; piece = move->piece;
12145 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12146 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12147 quickBoard[from] = 0;
12149 quickBoard[to] = piece;
12150 pieceList[piece] = to;
12152 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12153 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12154 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12155 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12157 static int lastCounts[EmptySquare+1];
12159 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12160 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12161 } else stretch = 0;
12162 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12171 flipSearch = FALSE;
12172 CopyBoard(soughtBoard, boards[currentMove]);
12173 soughtTotal = MakePieceList(soughtBoard, maxSought);
12174 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12175 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12176 CopyBoard(reverseBoard, boards[currentMove]);
12177 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12178 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12179 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12180 reverseBoard[r][f] = piece;
12182 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12183 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12184 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12185 || (boards[currentMove][CASTLING][2] == NoRights ||
12186 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12187 && (boards[currentMove][CASTLING][5] == NoRights ||
12188 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12191 CopyBoard(flipBoard, soughtBoard);
12192 CopyBoard(rotateBoard, reverseBoard);
12193 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12194 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12195 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12198 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12199 if(appData.searchMode >= 5) {
12200 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12201 MakePieceList(soughtBoard, minSought);
12202 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12204 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12205 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12208 GameInfo dummyInfo;
12209 static int creatingBook;
12212 GameContainsPosition (FILE *f, ListGame *lg)
12214 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12215 int fromX, fromY, toX, toY;
12217 static int initDone=FALSE;
12219 // weed out games based on numerical tag comparison
12220 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12221 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12222 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12223 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12225 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12228 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12229 else CopyBoard(boards[scratch], initialPosition); // default start position
12232 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12233 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12236 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12237 fseek(f, lg->offset, 0);
12240 yyboardindex = scratch;
12241 quickFlag = plyNr+1;
12246 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12252 if(plyNr) return -1; // after we have seen moves, this is for new game
12255 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12256 case ImpossibleMove:
12257 case WhiteWins: // game ends here with these four
12260 case GameUnfinished:
12264 if(appData.testLegality) return -1;
12265 case WhiteCapturesEnPassant:
12266 case BlackCapturesEnPassant:
12267 case WhitePromotion:
12268 case BlackPromotion:
12269 case WhiteNonPromotion:
12270 case BlackNonPromotion:
12273 case WhiteKingSideCastle:
12274 case WhiteQueenSideCastle:
12275 case BlackKingSideCastle:
12276 case BlackQueenSideCastle:
12277 case WhiteKingSideCastleWild:
12278 case WhiteQueenSideCastleWild:
12279 case BlackKingSideCastleWild:
12280 case BlackQueenSideCastleWild:
12281 case WhiteHSideCastleFR:
12282 case WhiteASideCastleFR:
12283 case BlackHSideCastleFR:
12284 case BlackASideCastleFR:
12285 fromX = currentMoveString[0] - AAA;
12286 fromY = currentMoveString[1] - ONE;
12287 toX = currentMoveString[2] - AAA;
12288 toY = currentMoveString[3] - ONE;
12289 promoChar = currentMoveString[4];
12293 fromX = next == WhiteDrop ?
12294 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12295 (int) CharToPiece(ToLower(currentMoveString[0]));
12297 toX = currentMoveString[2] - AAA;
12298 toY = currentMoveString[3] - ONE;
12302 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12304 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12305 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12306 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12307 if(appData.findMirror) {
12308 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12309 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12314 /* Load the nth game from open file f */
12316 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12320 int gn = gameNumber;
12321 ListGame *lg = NULL;
12322 int numPGNTags = 0;
12324 GameMode oldGameMode;
12325 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12327 if (appData.debugMode)
12328 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12330 if (gameMode == Training )
12331 SetTrainingModeOff();
12333 oldGameMode = gameMode;
12334 if (gameMode != BeginningOfGame) {
12335 Reset(FALSE, TRUE);
12337 killX = killY = -1; // [HGM] lion: in case we did not Reset
12340 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12341 fclose(lastLoadGameFP);
12345 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12348 fseek(f, lg->offset, 0);
12349 GameListHighlight(gameNumber);
12350 pos = lg->position;
12354 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12355 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12357 DisplayError(_("Game number out of range"), 0);
12362 if (fseek(f, 0, 0) == -1) {
12363 if (f == lastLoadGameFP ?
12364 gameNumber == lastLoadGameNumber + 1 :
12368 DisplayError(_("Can't seek on game file"), 0);
12373 lastLoadGameFP = f;
12374 lastLoadGameNumber = gameNumber;
12375 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12376 lastLoadGameUseList = useList;
12380 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12381 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12382 lg->gameInfo.black);
12384 } else if (*title != NULLCHAR) {
12385 if (gameNumber > 1) {
12386 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12389 DisplayTitle(title);
12393 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12394 gameMode = PlayFromGameFile;
12398 currentMove = forwardMostMove = backwardMostMove = 0;
12399 CopyBoard(boards[0], initialPosition);
12403 * Skip the first gn-1 games in the file.
12404 * Also skip over anything that precedes an identifiable
12405 * start of game marker, to avoid being confused by
12406 * garbage at the start of the file. Currently
12407 * recognized start of game markers are the move number "1",
12408 * the pattern "gnuchess .* game", the pattern
12409 * "^[#;%] [^ ]* game file", and a PGN tag block.
12410 * A game that starts with one of the latter two patterns
12411 * will also have a move number 1, possibly
12412 * following a position diagram.
12413 * 5-4-02: Let's try being more lenient and allowing a game to
12414 * start with an unnumbered move. Does that break anything?
12416 cm = lastLoadGameStart = EndOfFile;
12418 yyboardindex = forwardMostMove;
12419 cm = (ChessMove) Myylex();
12422 if (cmailMsgLoaded) {
12423 nCmailGames = CMAIL_MAX_GAMES - gn;
12426 DisplayError(_("Game not found in file"), 0);
12433 lastLoadGameStart = cm;
12436 case MoveNumberOne:
12437 switch (lastLoadGameStart) {
12442 case MoveNumberOne:
12444 gn--; /* count this game */
12445 lastLoadGameStart = cm;
12454 switch (lastLoadGameStart) {
12457 case MoveNumberOne:
12459 gn--; /* count this game */
12460 lastLoadGameStart = cm;
12463 lastLoadGameStart = cm; /* game counted already */
12471 yyboardindex = forwardMostMove;
12472 cm = (ChessMove) Myylex();
12473 } while (cm == PGNTag || cm == Comment);
12480 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12481 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12482 != CMAIL_OLD_RESULT) {
12484 cmailResult[ CMAIL_MAX_GAMES
12485 - gn - 1] = CMAIL_OLD_RESULT;
12492 /* Only a NormalMove can be at the start of a game
12493 * without a position diagram. */
12494 if (lastLoadGameStart == EndOfFile ) {
12496 lastLoadGameStart = MoveNumberOne;
12505 if (appData.debugMode)
12506 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12508 if (cm == XBoardGame) {
12509 /* Skip any header junk before position diagram and/or move 1 */
12511 yyboardindex = forwardMostMove;
12512 cm = (ChessMove) Myylex();
12514 if (cm == EndOfFile ||
12515 cm == GNUChessGame || cm == XBoardGame) {
12516 /* Empty game; pretend end-of-file and handle later */
12521 if (cm == MoveNumberOne || cm == PositionDiagram ||
12522 cm == PGNTag || cm == Comment)
12525 } else if (cm == GNUChessGame) {
12526 if (gameInfo.event != NULL) {
12527 free(gameInfo.event);
12529 gameInfo.event = StrSave(yy_text);
12532 startedFromSetupPosition = FALSE;
12533 while (cm == PGNTag) {
12534 if (appData.debugMode)
12535 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12536 err = ParsePGNTag(yy_text, &gameInfo);
12537 if (!err) numPGNTags++;
12539 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12540 if(gameInfo.variant != oldVariant) {
12541 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12542 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12543 InitPosition(TRUE);
12544 oldVariant = gameInfo.variant;
12545 if (appData.debugMode)
12546 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12550 if (gameInfo.fen != NULL) {
12551 Board initial_position;
12552 startedFromSetupPosition = TRUE;
12553 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12555 DisplayError(_("Bad FEN position in file"), 0);
12558 CopyBoard(boards[0], initial_position);
12559 if (blackPlaysFirst) {
12560 currentMove = forwardMostMove = backwardMostMove = 1;
12561 CopyBoard(boards[1], initial_position);
12562 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12563 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12564 timeRemaining[0][1] = whiteTimeRemaining;
12565 timeRemaining[1][1] = blackTimeRemaining;
12566 if (commentList[0] != NULL) {
12567 commentList[1] = commentList[0];
12568 commentList[0] = NULL;
12571 currentMove = forwardMostMove = backwardMostMove = 0;
12573 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12575 initialRulePlies = FENrulePlies;
12576 for( i=0; i< nrCastlingRights; i++ )
12577 initialRights[i] = initial_position[CASTLING][i];
12579 yyboardindex = forwardMostMove;
12580 free(gameInfo.fen);
12581 gameInfo.fen = NULL;
12584 yyboardindex = forwardMostMove;
12585 cm = (ChessMove) Myylex();
12587 /* Handle comments interspersed among the tags */
12588 while (cm == Comment) {
12590 if (appData.debugMode)
12591 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12593 AppendComment(currentMove, p, FALSE);
12594 yyboardindex = forwardMostMove;
12595 cm = (ChessMove) Myylex();
12599 /* don't rely on existence of Event tag since if game was
12600 * pasted from clipboard the Event tag may not exist
12602 if (numPGNTags > 0){
12604 if (gameInfo.variant == VariantNormal) {
12605 VariantClass v = StringToVariant(gameInfo.event);
12606 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12607 if(v < VariantShogi) gameInfo.variant = v;
12610 if( appData.autoDisplayTags ) {
12611 tags = PGNTags(&gameInfo);
12612 TagsPopUp(tags, CmailMsg());
12617 /* Make something up, but don't display it now */
12622 if (cm == PositionDiagram) {
12625 Board initial_position;
12627 if (appData.debugMode)
12628 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12630 if (!startedFromSetupPosition) {
12632 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12633 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12644 initial_position[i][j++] = CharToPiece(*p);
12647 while (*p == ' ' || *p == '\t' ||
12648 *p == '\n' || *p == '\r') p++;
12650 if (strncmp(p, "black", strlen("black"))==0)
12651 blackPlaysFirst = TRUE;
12653 blackPlaysFirst = FALSE;
12654 startedFromSetupPosition = TRUE;
12656 CopyBoard(boards[0], initial_position);
12657 if (blackPlaysFirst) {
12658 currentMove = forwardMostMove = backwardMostMove = 1;
12659 CopyBoard(boards[1], initial_position);
12660 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12661 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12662 timeRemaining[0][1] = whiteTimeRemaining;
12663 timeRemaining[1][1] = blackTimeRemaining;
12664 if (commentList[0] != NULL) {
12665 commentList[1] = commentList[0];
12666 commentList[0] = NULL;
12669 currentMove = forwardMostMove = backwardMostMove = 0;
12672 yyboardindex = forwardMostMove;
12673 cm = (ChessMove) Myylex();
12676 if(!creatingBook) {
12677 if (first.pr == NoProc) {
12678 StartChessProgram(&first);
12680 InitChessProgram(&first, FALSE);
12681 SendToProgram("force\n", &first);
12682 if (startedFromSetupPosition) {
12683 SendBoard(&first, forwardMostMove);
12684 if (appData.debugMode) {
12685 fprintf(debugFP, "Load Game\n");
12687 DisplayBothClocks();
12691 /* [HGM] server: flag to write setup moves in broadcast file as one */
12692 loadFlag = appData.suppressLoadMoves;
12694 while (cm == Comment) {
12696 if (appData.debugMode)
12697 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12699 AppendComment(currentMove, p, FALSE);
12700 yyboardindex = forwardMostMove;
12701 cm = (ChessMove) Myylex();
12704 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12705 cm == WhiteWins || cm == BlackWins ||
12706 cm == GameIsDrawn || cm == GameUnfinished) {
12707 DisplayMessage("", _("No moves in game"));
12708 if (cmailMsgLoaded) {
12709 if (appData.debugMode)
12710 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12714 DrawPosition(FALSE, boards[currentMove]);
12715 DisplayBothClocks();
12716 gameMode = EditGame;
12723 // [HGM] PV info: routine tests if comment empty
12724 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12725 DisplayComment(currentMove - 1, commentList[currentMove]);
12727 if (!matchMode && appData.timeDelay != 0)
12728 DrawPosition(FALSE, boards[currentMove]);
12730 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12731 programStats.ok_to_send = 1;
12734 /* if the first token after the PGN tags is a move
12735 * and not move number 1, retrieve it from the parser
12737 if (cm != MoveNumberOne)
12738 LoadGameOneMove(cm);
12740 /* load the remaining moves from the file */
12741 while (LoadGameOneMove(EndOfFile)) {
12742 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12743 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12746 /* rewind to the start of the game */
12747 currentMove = backwardMostMove;
12749 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12751 if (oldGameMode == AnalyzeFile) {
12752 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12753 AnalyzeFileEvent();
12755 if (oldGameMode == AnalyzeMode) {
12756 AnalyzeFileEvent();
12759 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12760 long int w, b; // [HGM] adjourn: restore saved clock times
12761 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12762 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12763 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12764 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12768 if(creatingBook) return TRUE;
12769 if (!matchMode && pos > 0) {
12770 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12772 if (matchMode || appData.timeDelay == 0) {
12774 } else if (appData.timeDelay > 0) {
12775 AutoPlayGameLoop();
12778 if (appData.debugMode)
12779 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12781 loadFlag = 0; /* [HGM] true game starts */
12785 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12787 ReloadPosition (int offset)
12789 int positionNumber = lastLoadPositionNumber + offset;
12790 if (lastLoadPositionFP == NULL) {
12791 DisplayError(_("No position has been loaded yet"), 0);
12794 if (positionNumber <= 0) {
12795 DisplayError(_("Can't back up any further"), 0);
12798 return LoadPosition(lastLoadPositionFP, positionNumber,
12799 lastLoadPositionTitle);
12802 /* Load the nth position from the given file */
12804 LoadPositionFromFile (char *filename, int n, char *title)
12809 if (strcmp(filename, "-") == 0) {
12810 return LoadPosition(stdin, n, "stdin");
12812 f = fopen(filename, "rb");
12814 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12815 DisplayError(buf, errno);
12818 return LoadPosition(f, n, title);
12823 /* Load the nth position from the given open file, and close it */
12825 LoadPosition (FILE *f, int positionNumber, char *title)
12827 char *p, line[MSG_SIZ];
12828 Board initial_position;
12829 int i, j, fenMode, pn;
12831 if (gameMode == Training )
12832 SetTrainingModeOff();
12834 if (gameMode != BeginningOfGame) {
12835 Reset(FALSE, TRUE);
12837 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12838 fclose(lastLoadPositionFP);
12840 if (positionNumber == 0) positionNumber = 1;
12841 lastLoadPositionFP = f;
12842 lastLoadPositionNumber = positionNumber;
12843 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12844 if (first.pr == NoProc && !appData.noChessProgram) {
12845 StartChessProgram(&first);
12846 InitChessProgram(&first, FALSE);
12848 pn = positionNumber;
12849 if (positionNumber < 0) {
12850 /* Negative position number means to seek to that byte offset */
12851 if (fseek(f, -positionNumber, 0) == -1) {
12852 DisplayError(_("Can't seek on position file"), 0);
12857 if (fseek(f, 0, 0) == -1) {
12858 if (f == lastLoadPositionFP ?
12859 positionNumber == lastLoadPositionNumber + 1 :
12860 positionNumber == 1) {
12863 DisplayError(_("Can't seek on position file"), 0);
12868 /* See if this file is FEN or old-style xboard */
12869 if (fgets(line, MSG_SIZ, f) == NULL) {
12870 DisplayError(_("Position not found in file"), 0);
12873 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12874 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12877 if (fenMode || line[0] == '#') pn--;
12879 /* skip positions before number pn */
12880 if (fgets(line, MSG_SIZ, f) == NULL) {
12882 DisplayError(_("Position not found in file"), 0);
12885 if (fenMode || line[0] == '#') pn--;
12890 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12891 DisplayError(_("Bad FEN position in file"), 0);
12895 (void) fgets(line, MSG_SIZ, f);
12896 (void) fgets(line, MSG_SIZ, f);
12898 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12899 (void) fgets(line, MSG_SIZ, f);
12900 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12903 initial_position[i][j++] = CharToPiece(*p);
12907 blackPlaysFirst = FALSE;
12909 (void) fgets(line, MSG_SIZ, f);
12910 if (strncmp(line, "black", strlen("black"))==0)
12911 blackPlaysFirst = TRUE;
12914 startedFromSetupPosition = TRUE;
12916 CopyBoard(boards[0], initial_position);
12917 if (blackPlaysFirst) {
12918 currentMove = forwardMostMove = backwardMostMove = 1;
12919 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12920 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12921 CopyBoard(boards[1], initial_position);
12922 DisplayMessage("", _("Black to play"));
12924 currentMove = forwardMostMove = backwardMostMove = 0;
12925 DisplayMessage("", _("White to play"));
12927 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12928 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12929 SendToProgram("force\n", &first);
12930 SendBoard(&first, forwardMostMove);
12932 if (appData.debugMode) {
12934 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12935 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12936 fprintf(debugFP, "Load Position\n");
12939 if (positionNumber > 1) {
12940 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12941 DisplayTitle(line);
12943 DisplayTitle(title);
12945 gameMode = EditGame;
12948 timeRemaining[0][1] = whiteTimeRemaining;
12949 timeRemaining[1][1] = blackTimeRemaining;
12950 DrawPosition(FALSE, boards[currentMove]);
12957 CopyPlayerNameIntoFileName (char **dest, char *src)
12959 while (*src != NULLCHAR && *src != ',') {
12964 *(*dest)++ = *src++;
12970 DefaultFileName (char *ext)
12972 static char def[MSG_SIZ];
12975 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12977 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12979 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12981 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12988 /* Save the current game to the given file */
12990 SaveGameToFile (char *filename, int append)
12994 int result, i, t,tot=0;
12996 if (strcmp(filename, "-") == 0) {
12997 return SaveGame(stdout, 0, NULL);
12999 for(i=0; i<10; i++) { // upto 10 tries
13000 f = fopen(filename, append ? "a" : "w");
13001 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13002 if(f || errno != 13) break;
13003 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13007 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13008 DisplayError(buf, errno);
13011 safeStrCpy(buf, lastMsg, MSG_SIZ);
13012 DisplayMessage(_("Waiting for access to save file"), "");
13013 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13014 DisplayMessage(_("Saving game"), "");
13015 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13016 result = SaveGame(f, 0, NULL);
13017 DisplayMessage(buf, "");
13024 SavePart (char *str)
13026 static char buf[MSG_SIZ];
13029 p = strchr(str, ' ');
13030 if (p == NULL) return str;
13031 strncpy(buf, str, p - str);
13032 buf[p - str] = NULLCHAR;
13036 #define PGN_MAX_LINE 75
13038 #define PGN_SIDE_WHITE 0
13039 #define PGN_SIDE_BLACK 1
13042 FindFirstMoveOutOfBook (int side)
13046 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13047 int index = backwardMostMove;
13048 int has_book_hit = 0;
13050 if( (index % 2) != side ) {
13054 while( index < forwardMostMove ) {
13055 /* Check to see if engine is in book */
13056 int depth = pvInfoList[index].depth;
13057 int score = pvInfoList[index].score;
13063 else if( score == 0 && depth == 63 ) {
13064 in_book = 1; /* Zappa */
13066 else if( score == 2 && depth == 99 ) {
13067 in_book = 1; /* Abrok */
13070 has_book_hit += in_book;
13086 GetOutOfBookInfo (char * buf)
13090 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13092 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13093 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13097 if( oob[0] >= 0 || oob[1] >= 0 ) {
13098 for( i=0; i<2; i++ ) {
13102 if( i > 0 && oob[0] >= 0 ) {
13103 strcat( buf, " " );
13106 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13107 sprintf( buf+strlen(buf), "%s%.2f",
13108 pvInfoList[idx].score >= 0 ? "+" : "",
13109 pvInfoList[idx].score / 100.0 );
13115 /* Save game in PGN style and close the file */
13117 SaveGamePGN (FILE *f)
13119 int i, offset, linelen, newblock;
13122 int movelen, numlen, blank;
13123 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13125 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13127 PrintPGNTags(f, &gameInfo);
13129 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13131 if (backwardMostMove > 0 || startedFromSetupPosition) {
13132 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13133 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13134 fprintf(f, "\n{--------------\n");
13135 PrintPosition(f, backwardMostMove);
13136 fprintf(f, "--------------}\n");
13140 /* [AS] Out of book annotation */
13141 if( appData.saveOutOfBookInfo ) {
13144 GetOutOfBookInfo( buf );
13146 if( buf[0] != '\0' ) {
13147 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13154 i = backwardMostMove;
13158 while (i < forwardMostMove) {
13159 /* Print comments preceding this move */
13160 if (commentList[i] != NULL) {
13161 if (linelen > 0) fprintf(f, "\n");
13162 fprintf(f, "%s", commentList[i]);
13167 /* Format move number */
13169 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13172 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13174 numtext[0] = NULLCHAR;
13176 numlen = strlen(numtext);
13179 /* Print move number */
13180 blank = linelen > 0 && numlen > 0;
13181 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13190 fprintf(f, "%s", numtext);
13194 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13195 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13198 blank = linelen > 0 && movelen > 0;
13199 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13208 fprintf(f, "%s", move_buffer);
13209 linelen += movelen;
13211 /* [AS] Add PV info if present */
13212 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13213 /* [HGM] add time */
13214 char buf[MSG_SIZ]; int seconds;
13216 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13222 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13225 seconds = (seconds + 4)/10; // round to full seconds
13227 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13229 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13232 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13233 pvInfoList[i].score >= 0 ? "+" : "",
13234 pvInfoList[i].score / 100.0,
13235 pvInfoList[i].depth,
13238 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13240 /* Print score/depth */
13241 blank = linelen > 0 && movelen > 0;
13242 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13251 fprintf(f, "%s", move_buffer);
13252 linelen += movelen;
13258 /* Start a new line */
13259 if (linelen > 0) fprintf(f, "\n");
13261 /* Print comments after last move */
13262 if (commentList[i] != NULL) {
13263 fprintf(f, "%s\n", commentList[i]);
13267 if (gameInfo.resultDetails != NULL &&
13268 gameInfo.resultDetails[0] != NULLCHAR) {
13269 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13270 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13271 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13272 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13273 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13275 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13279 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13283 /* Save game in old style and close the file */
13285 SaveGameOldStyle (FILE *f)
13290 tm = time((time_t *) NULL);
13292 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13295 if (backwardMostMove > 0 || startedFromSetupPosition) {
13296 fprintf(f, "\n[--------------\n");
13297 PrintPosition(f, backwardMostMove);
13298 fprintf(f, "--------------]\n");
13303 i = backwardMostMove;
13304 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13306 while (i < forwardMostMove) {
13307 if (commentList[i] != NULL) {
13308 fprintf(f, "[%s]\n", commentList[i]);
13311 if ((i % 2) == 1) {
13312 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13315 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13317 if (commentList[i] != NULL) {
13321 if (i >= forwardMostMove) {
13325 fprintf(f, "%s\n", parseList[i]);
13330 if (commentList[i] != NULL) {
13331 fprintf(f, "[%s]\n", commentList[i]);
13334 /* This isn't really the old style, but it's close enough */
13335 if (gameInfo.resultDetails != NULL &&
13336 gameInfo.resultDetails[0] != NULLCHAR) {
13337 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13338 gameInfo.resultDetails);
13340 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13347 /* Save the current game to open file f and close the file */
13349 SaveGame (FILE *f, int dummy, char *dummy2)
13351 if (gameMode == EditPosition) EditPositionDone(TRUE);
13352 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13353 if (appData.oldSaveStyle)
13354 return SaveGameOldStyle(f);
13356 return SaveGamePGN(f);
13359 /* Save the current position to the given file */
13361 SavePositionToFile (char *filename)
13366 if (strcmp(filename, "-") == 0) {
13367 return SavePosition(stdout, 0, NULL);
13369 f = fopen(filename, "a");
13371 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13372 DisplayError(buf, errno);
13375 safeStrCpy(buf, lastMsg, MSG_SIZ);
13376 DisplayMessage(_("Waiting for access to save file"), "");
13377 flock(fileno(f), LOCK_EX); // [HGM] lock
13378 DisplayMessage(_("Saving position"), "");
13379 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13380 SavePosition(f, 0, NULL);
13381 DisplayMessage(buf, "");
13387 /* Save the current position to the given open file and close the file */
13389 SavePosition (FILE *f, int dummy, char *dummy2)
13394 if (gameMode == EditPosition) EditPositionDone(TRUE);
13395 if (appData.oldSaveStyle) {
13396 tm = time((time_t *) NULL);
13398 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13400 fprintf(f, "[--------------\n");
13401 PrintPosition(f, currentMove);
13402 fprintf(f, "--------------]\n");
13404 fen = PositionToFEN(currentMove, NULL, 1);
13405 fprintf(f, "%s\n", fen);
13413 ReloadCmailMsgEvent (int unregister)
13416 static char *inFilename = NULL;
13417 static char *outFilename;
13419 struct stat inbuf, outbuf;
13422 /* Any registered moves are unregistered if unregister is set, */
13423 /* i.e. invoked by the signal handler */
13425 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13426 cmailMoveRegistered[i] = FALSE;
13427 if (cmailCommentList[i] != NULL) {
13428 free(cmailCommentList[i]);
13429 cmailCommentList[i] = NULL;
13432 nCmailMovesRegistered = 0;
13435 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13436 cmailResult[i] = CMAIL_NOT_RESULT;
13440 if (inFilename == NULL) {
13441 /* Because the filenames are static they only get malloced once */
13442 /* and they never get freed */
13443 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13444 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13446 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13447 sprintf(outFilename, "%s.out", appData.cmailGameName);
13450 status = stat(outFilename, &outbuf);
13452 cmailMailedMove = FALSE;
13454 status = stat(inFilename, &inbuf);
13455 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13458 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13459 counts the games, notes how each one terminated, etc.
13461 It would be nice to remove this kludge and instead gather all
13462 the information while building the game list. (And to keep it
13463 in the game list nodes instead of having a bunch of fixed-size
13464 parallel arrays.) Note this will require getting each game's
13465 termination from the PGN tags, as the game list builder does
13466 not process the game moves. --mann
13468 cmailMsgLoaded = TRUE;
13469 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13471 /* Load first game in the file or popup game menu */
13472 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13474 #endif /* !WIN32 */
13482 char string[MSG_SIZ];
13484 if ( cmailMailedMove
13485 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13486 return TRUE; /* Allow free viewing */
13489 /* Unregister move to ensure that we don't leave RegisterMove */
13490 /* with the move registered when the conditions for registering no */
13492 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13493 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13494 nCmailMovesRegistered --;
13496 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13498 free(cmailCommentList[lastLoadGameNumber - 1]);
13499 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13503 if (cmailOldMove == -1) {
13504 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13508 if (currentMove > cmailOldMove + 1) {
13509 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13513 if (currentMove < cmailOldMove) {
13514 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13518 if (forwardMostMove > currentMove) {
13519 /* Silently truncate extra moves */
13523 if ( (currentMove == cmailOldMove + 1)
13524 || ( (currentMove == cmailOldMove)
13525 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13526 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13527 if (gameInfo.result != GameUnfinished) {
13528 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13531 if (commentList[currentMove] != NULL) {
13532 cmailCommentList[lastLoadGameNumber - 1]
13533 = StrSave(commentList[currentMove]);
13535 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13537 if (appData.debugMode)
13538 fprintf(debugFP, "Saving %s for game %d\n",
13539 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13541 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13543 f = fopen(string, "w");
13544 if (appData.oldSaveStyle) {
13545 SaveGameOldStyle(f); /* also closes the file */
13547 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13548 f = fopen(string, "w");
13549 SavePosition(f, 0, NULL); /* also closes the file */
13551 fprintf(f, "{--------------\n");
13552 PrintPosition(f, currentMove);
13553 fprintf(f, "--------------}\n\n");
13555 SaveGame(f, 0, NULL); /* also closes the file*/
13558 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13559 nCmailMovesRegistered ++;
13560 } else if (nCmailGames == 1) {
13561 DisplayError(_("You have not made a move yet"), 0);
13572 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13573 FILE *commandOutput;
13574 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13575 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13581 if (! cmailMsgLoaded) {
13582 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13586 if (nCmailGames == nCmailResults) {
13587 DisplayError(_("No unfinished games"), 0);
13591 #if CMAIL_PROHIBIT_REMAIL
13592 if (cmailMailedMove) {
13593 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);
13594 DisplayError(msg, 0);
13599 if (! (cmailMailedMove || RegisterMove())) return;
13601 if ( cmailMailedMove
13602 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13603 snprintf(string, MSG_SIZ, partCommandString,
13604 appData.debugMode ? " -v" : "", appData.cmailGameName);
13605 commandOutput = popen(string, "r");
13607 if (commandOutput == NULL) {
13608 DisplayError(_("Failed to invoke cmail"), 0);
13610 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13611 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13613 if (nBuffers > 1) {
13614 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13615 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13616 nBytes = MSG_SIZ - 1;
13618 (void) memcpy(msg, buffer, nBytes);
13620 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13622 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13623 cmailMailedMove = TRUE; /* Prevent >1 moves */
13626 for (i = 0; i < nCmailGames; i ++) {
13627 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13632 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13634 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13636 appData.cmailGameName,
13638 LoadGameFromFile(buffer, 1, buffer, FALSE);
13639 cmailMsgLoaded = FALSE;
13643 DisplayInformation(msg);
13644 pclose(commandOutput);
13647 if ((*cmailMsg) != '\0') {
13648 DisplayInformation(cmailMsg);
13653 #endif /* !WIN32 */
13662 int prependComma = 0;
13664 char string[MSG_SIZ]; /* Space for game-list */
13667 if (!cmailMsgLoaded) return "";
13669 if (cmailMailedMove) {
13670 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13672 /* Create a list of games left */
13673 snprintf(string, MSG_SIZ, "[");
13674 for (i = 0; i < nCmailGames; i ++) {
13675 if (! ( cmailMoveRegistered[i]
13676 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13677 if (prependComma) {
13678 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13680 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13684 strcat(string, number);
13687 strcat(string, "]");
13689 if (nCmailMovesRegistered + nCmailResults == 0) {
13690 switch (nCmailGames) {
13692 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13696 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13700 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13705 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13707 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13712 if (nCmailResults == nCmailGames) {
13713 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13715 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13720 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13732 if (gameMode == Training)
13733 SetTrainingModeOff();
13736 cmailMsgLoaded = FALSE;
13737 if (appData.icsActive) {
13738 SendToICS(ics_prefix);
13739 SendToICS("refresh\n");
13744 ExitEvent (int status)
13748 /* Give up on clean exit */
13752 /* Keep trying for clean exit */
13756 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13758 if (telnetISR != NULL) {
13759 RemoveInputSource(telnetISR);
13761 if (icsPR != NoProc) {
13762 DestroyChildProcess(icsPR, TRUE);
13765 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13766 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13768 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13769 /* make sure this other one finishes before killing it! */
13770 if(endingGame) { int count = 0;
13771 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13772 while(endingGame && count++ < 10) DoSleep(1);
13773 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13776 /* Kill off chess programs */
13777 if (first.pr != NoProc) {
13780 DoSleep( appData.delayBeforeQuit );
13781 SendToProgram("quit\n", &first);
13782 DoSleep( appData.delayAfterQuit );
13783 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13785 if (second.pr != NoProc) {
13786 DoSleep( appData.delayBeforeQuit );
13787 SendToProgram("quit\n", &second);
13788 DoSleep( appData.delayAfterQuit );
13789 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13791 if (first.isr != NULL) {
13792 RemoveInputSource(first.isr);
13794 if (second.isr != NULL) {
13795 RemoveInputSource(second.isr);
13798 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13799 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13801 ShutDownFrontEnd();
13806 PauseEngine (ChessProgramState *cps)
13808 SendToProgram("pause\n", cps);
13813 UnPauseEngine (ChessProgramState *cps)
13815 SendToProgram("resume\n", cps);
13822 if (appData.debugMode)
13823 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13827 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13829 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13830 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13831 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13833 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13834 HandleMachineMove(stashedInputMove, stalledEngine);
13835 stalledEngine = NULL;
13838 if (gameMode == MachinePlaysWhite ||
13839 gameMode == TwoMachinesPlay ||
13840 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13841 if(first.pause) UnPauseEngine(&first);
13842 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13843 if(second.pause) UnPauseEngine(&second);
13844 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13847 DisplayBothClocks();
13849 if (gameMode == PlayFromGameFile) {
13850 if (appData.timeDelay >= 0)
13851 AutoPlayGameLoop();
13852 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13853 Reset(FALSE, TRUE);
13854 SendToICS(ics_prefix);
13855 SendToICS("refresh\n");
13856 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13857 ForwardInner(forwardMostMove);
13859 pauseExamInvalid = FALSE;
13861 switch (gameMode) {
13865 pauseExamForwardMostMove = forwardMostMove;
13866 pauseExamInvalid = FALSE;
13869 case IcsPlayingWhite:
13870 case IcsPlayingBlack:
13874 case PlayFromGameFile:
13875 (void) StopLoadGameTimer();
13879 case BeginningOfGame:
13880 if (appData.icsActive) return;
13881 /* else fall through */
13882 case MachinePlaysWhite:
13883 case MachinePlaysBlack:
13884 case TwoMachinesPlay:
13885 if (forwardMostMove == 0)
13886 return; /* don't pause if no one has moved */
13887 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13888 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13889 if(onMove->pause) { // thinking engine can be paused
13890 PauseEngine(onMove); // do it
13891 if(onMove->other->pause) // pondering opponent can always be paused immediately
13892 PauseEngine(onMove->other);
13894 SendToProgram("easy\n", onMove->other);
13896 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13897 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13899 PauseEngine(&first);
13901 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13902 } else { // human on move, pause pondering by either method
13904 PauseEngine(&first);
13905 else if(appData.ponderNextMove)
13906 SendToProgram("easy\n", &first);
13909 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13919 EditCommentEvent ()
13921 char title[MSG_SIZ];
13923 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13924 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13926 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13927 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13928 parseList[currentMove - 1]);
13931 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13938 char *tags = PGNTags(&gameInfo);
13940 EditTagsPopUp(tags, NULL);
13947 if(second.analyzing) {
13948 SendToProgram("exit\n", &second);
13949 second.analyzing = FALSE;
13951 if (second.pr == NoProc) StartChessProgram(&second);
13952 InitChessProgram(&second, FALSE);
13953 FeedMovesToProgram(&second, currentMove);
13955 SendToProgram("analyze\n", &second);
13956 second.analyzing = TRUE;
13960 /* Toggle ShowThinking */
13962 ToggleShowThinking()
13964 appData.showThinking = !appData.showThinking;
13965 ShowThinkingEvent();
13969 AnalyzeModeEvent ()
13973 if (!first.analysisSupport) {
13974 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13975 DisplayError(buf, 0);
13978 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13979 if (appData.icsActive) {
13980 if (gameMode != IcsObserving) {
13981 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13982 DisplayError(buf, 0);
13984 if (appData.icsEngineAnalyze) {
13985 if (appData.debugMode)
13986 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13992 /* if enable, user wants to disable icsEngineAnalyze */
13993 if (appData.icsEngineAnalyze) {
13998 appData.icsEngineAnalyze = TRUE;
13999 if (appData.debugMode)
14000 fprintf(debugFP, "ICS engine analyze starting... \n");
14003 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14004 if (appData.noChessProgram || gameMode == AnalyzeMode)
14007 if (gameMode != AnalyzeFile) {
14008 if (!appData.icsEngineAnalyze) {
14010 if (gameMode != EditGame) return 0;
14012 if (!appData.showThinking) ToggleShowThinking();
14013 ResurrectChessProgram();
14014 SendToProgram("analyze\n", &first);
14015 first.analyzing = TRUE;
14016 /*first.maybeThinking = TRUE;*/
14017 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14018 EngineOutputPopUp();
14020 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14025 StartAnalysisClock();
14026 GetTimeMark(&lastNodeCountTime);
14032 AnalyzeFileEvent ()
14034 if (appData.noChessProgram || gameMode == AnalyzeFile)
14037 if (!first.analysisSupport) {
14039 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14040 DisplayError(buf, 0);
14044 if (gameMode != AnalyzeMode) {
14045 keepInfo = 1; // mere annotating should not alter PGN tags
14048 if (gameMode != EditGame) return;
14049 if (!appData.showThinking) ToggleShowThinking();
14050 ResurrectChessProgram();
14051 SendToProgram("analyze\n", &first);
14052 first.analyzing = TRUE;
14053 /*first.maybeThinking = TRUE;*/
14054 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14055 EngineOutputPopUp();
14057 gameMode = AnalyzeFile;
14061 StartAnalysisClock();
14062 GetTimeMark(&lastNodeCountTime);
14064 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14065 AnalysisPeriodicEvent(1);
14069 MachineWhiteEvent ()
14072 char *bookHit = NULL;
14074 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14078 if (gameMode == PlayFromGameFile ||
14079 gameMode == TwoMachinesPlay ||
14080 gameMode == Training ||
14081 gameMode == AnalyzeMode ||
14082 gameMode == EndOfGame)
14085 if (gameMode == EditPosition)
14086 EditPositionDone(TRUE);
14088 if (!WhiteOnMove(currentMove)) {
14089 DisplayError(_("It is not White's turn"), 0);
14093 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14096 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14097 gameMode == AnalyzeFile)
14100 ResurrectChessProgram(); /* in case it isn't running */
14101 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14102 gameMode = MachinePlaysWhite;
14105 gameMode = MachinePlaysWhite;
14109 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14111 if (first.sendName) {
14112 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14113 SendToProgram(buf, &first);
14115 if (first.sendTime) {
14116 if (first.useColors) {
14117 SendToProgram("black\n", &first); /*gnu kludge*/
14119 SendTimeRemaining(&first, TRUE);
14121 if (first.useColors) {
14122 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14124 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14125 SetMachineThinkingEnables();
14126 first.maybeThinking = TRUE;
14130 if (appData.autoFlipView && !flipView) {
14131 flipView = !flipView;
14132 DrawPosition(FALSE, NULL);
14133 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14136 if(bookHit) { // [HGM] book: simulate book reply
14137 static char bookMove[MSG_SIZ]; // a bit generous?
14139 programStats.nodes = programStats.depth = programStats.time =
14140 programStats.score = programStats.got_only_move = 0;
14141 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14143 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14144 strcat(bookMove, bookHit);
14145 HandleMachineMove(bookMove, &first);
14150 MachineBlackEvent ()
14153 char *bookHit = NULL;
14155 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14159 if (gameMode == PlayFromGameFile ||
14160 gameMode == TwoMachinesPlay ||
14161 gameMode == Training ||
14162 gameMode == AnalyzeMode ||
14163 gameMode == EndOfGame)
14166 if (gameMode == EditPosition)
14167 EditPositionDone(TRUE);
14169 if (WhiteOnMove(currentMove)) {
14170 DisplayError(_("It is not Black's turn"), 0);
14174 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14177 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14178 gameMode == AnalyzeFile)
14181 ResurrectChessProgram(); /* in case it isn't running */
14182 gameMode = MachinePlaysBlack;
14186 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14188 if (first.sendName) {
14189 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14190 SendToProgram(buf, &first);
14192 if (first.sendTime) {
14193 if (first.useColors) {
14194 SendToProgram("white\n", &first); /*gnu kludge*/
14196 SendTimeRemaining(&first, FALSE);
14198 if (first.useColors) {
14199 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14201 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14202 SetMachineThinkingEnables();
14203 first.maybeThinking = TRUE;
14206 if (appData.autoFlipView && flipView) {
14207 flipView = !flipView;
14208 DrawPosition(FALSE, NULL);
14209 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14211 if(bookHit) { // [HGM] book: simulate book reply
14212 static char bookMove[MSG_SIZ]; // a bit generous?
14214 programStats.nodes = programStats.depth = programStats.time =
14215 programStats.score = programStats.got_only_move = 0;
14216 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14218 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14219 strcat(bookMove, bookHit);
14220 HandleMachineMove(bookMove, &first);
14226 DisplayTwoMachinesTitle ()
14229 if (appData.matchGames > 0) {
14230 if(appData.tourneyFile[0]) {
14231 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14232 gameInfo.white, _("vs."), gameInfo.black,
14233 nextGame+1, appData.matchGames+1,
14234 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14236 if (first.twoMachinesColor[0] == 'w') {
14237 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14238 gameInfo.white, _("vs."), gameInfo.black,
14239 first.matchWins, second.matchWins,
14240 matchGame - 1 - (first.matchWins + second.matchWins));
14242 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14243 gameInfo.white, _("vs."), gameInfo.black,
14244 second.matchWins, first.matchWins,
14245 matchGame - 1 - (first.matchWins + second.matchWins));
14248 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14254 SettingsMenuIfReady ()
14256 if (second.lastPing != second.lastPong) {
14257 DisplayMessage("", _("Waiting for second chess program"));
14258 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14262 DisplayMessage("", "");
14263 SettingsPopUp(&second);
14267 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14270 if (cps->pr == NoProc) {
14271 StartChessProgram(cps);
14272 if (cps->protocolVersion == 1) {
14274 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14276 /* kludge: allow timeout for initial "feature" command */
14277 if(retry != TwoMachinesEventIfReady) FreezeUI();
14278 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14279 DisplayMessage("", buf);
14280 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14288 TwoMachinesEvent P((void))
14292 ChessProgramState *onmove;
14293 char *bookHit = NULL;
14294 static int stalling = 0;
14298 if (appData.noChessProgram) return;
14300 switch (gameMode) {
14301 case TwoMachinesPlay:
14303 case MachinePlaysWhite:
14304 case MachinePlaysBlack:
14305 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14306 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14310 case BeginningOfGame:
14311 case PlayFromGameFile:
14314 if (gameMode != EditGame) return;
14317 EditPositionDone(TRUE);
14328 // forwardMostMove = currentMove;
14329 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14330 startingEngine = TRUE;
14332 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14334 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14335 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14336 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14339 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14341 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14342 startingEngine = FALSE;
14343 DisplayError("second engine does not play this", 0);
14348 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14349 SendToProgram("force\n", &second);
14351 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14354 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14355 if(appData.matchPause>10000 || appData.matchPause<10)
14356 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14357 wait = SubtractTimeMarks(&now, &pauseStart);
14358 if(wait < appData.matchPause) {
14359 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14362 // we are now committed to starting the game
14364 DisplayMessage("", "");
14365 if (startedFromSetupPosition) {
14366 SendBoard(&second, backwardMostMove);
14367 if (appData.debugMode) {
14368 fprintf(debugFP, "Two Machines\n");
14371 for (i = backwardMostMove; i < forwardMostMove; i++) {
14372 SendMoveToProgram(i, &second);
14375 gameMode = TwoMachinesPlay;
14376 pausing = startingEngine = FALSE;
14377 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14379 DisplayTwoMachinesTitle();
14381 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14386 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14387 SendToProgram(first.computerString, &first);
14388 if (first.sendName) {
14389 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14390 SendToProgram(buf, &first);
14392 SendToProgram(second.computerString, &second);
14393 if (second.sendName) {
14394 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14395 SendToProgram(buf, &second);
14399 if (!first.sendTime || !second.sendTime) {
14400 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14401 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14403 if (onmove->sendTime) {
14404 if (onmove->useColors) {
14405 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14407 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14409 if (onmove->useColors) {
14410 SendToProgram(onmove->twoMachinesColor, onmove);
14412 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14413 // SendToProgram("go\n", onmove);
14414 onmove->maybeThinking = TRUE;
14415 SetMachineThinkingEnables();
14419 if(bookHit) { // [HGM] book: simulate book reply
14420 static char bookMove[MSG_SIZ]; // a bit generous?
14422 programStats.nodes = programStats.depth = programStats.time =
14423 programStats.score = programStats.got_only_move = 0;
14424 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14426 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14427 strcat(bookMove, bookHit);
14428 savedMessage = bookMove; // args for deferred call
14429 savedState = onmove;
14430 ScheduleDelayedEvent(DeferredBookMove, 1);
14437 if (gameMode == Training) {
14438 SetTrainingModeOff();
14439 gameMode = PlayFromGameFile;
14440 DisplayMessage("", _("Training mode off"));
14442 gameMode = Training;
14443 animateTraining = appData.animate;
14445 /* make sure we are not already at the end of the game */
14446 if (currentMove < forwardMostMove) {
14447 SetTrainingModeOn();
14448 DisplayMessage("", _("Training mode on"));
14450 gameMode = PlayFromGameFile;
14451 DisplayError(_("Already at end of game"), 0);
14460 if (!appData.icsActive) return;
14461 switch (gameMode) {
14462 case IcsPlayingWhite:
14463 case IcsPlayingBlack:
14466 case BeginningOfGame:
14474 EditPositionDone(TRUE);
14487 gameMode = IcsIdle;
14497 switch (gameMode) {
14499 SetTrainingModeOff();
14501 case MachinePlaysWhite:
14502 case MachinePlaysBlack:
14503 case BeginningOfGame:
14504 SendToProgram("force\n", &first);
14505 SetUserThinkingEnables();
14507 case PlayFromGameFile:
14508 (void) StopLoadGameTimer();
14509 if (gameFileFP != NULL) {
14514 EditPositionDone(TRUE);
14519 SendToProgram("force\n", &first);
14521 case TwoMachinesPlay:
14522 GameEnds(EndOfFile, NULL, GE_PLAYER);
14523 ResurrectChessProgram();
14524 SetUserThinkingEnables();
14527 ResurrectChessProgram();
14529 case IcsPlayingBlack:
14530 case IcsPlayingWhite:
14531 DisplayError(_("Warning: You are still playing a game"), 0);
14534 DisplayError(_("Warning: You are still observing a game"), 0);
14537 DisplayError(_("Warning: You are still examining a game"), 0);
14548 first.offeredDraw = second.offeredDraw = 0;
14550 if (gameMode == PlayFromGameFile) {
14551 whiteTimeRemaining = timeRemaining[0][currentMove];
14552 blackTimeRemaining = timeRemaining[1][currentMove];
14556 if (gameMode == MachinePlaysWhite ||
14557 gameMode == MachinePlaysBlack ||
14558 gameMode == TwoMachinesPlay ||
14559 gameMode == EndOfGame) {
14560 i = forwardMostMove;
14561 while (i > currentMove) {
14562 SendToProgram("undo\n", &first);
14565 if(!adjustedClock) {
14566 whiteTimeRemaining = timeRemaining[0][currentMove];
14567 blackTimeRemaining = timeRemaining[1][currentMove];
14568 DisplayBothClocks();
14570 if (whiteFlag || blackFlag) {
14571 whiteFlag = blackFlag = 0;
14576 gameMode = EditGame;
14583 EditPositionEvent ()
14585 if (gameMode == EditPosition) {
14591 if (gameMode != EditGame) return;
14593 gameMode = EditPosition;
14596 if (currentMove > 0)
14597 CopyBoard(boards[0], boards[currentMove]);
14599 blackPlaysFirst = !WhiteOnMove(currentMove);
14601 currentMove = forwardMostMove = backwardMostMove = 0;
14602 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14604 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14610 /* [DM] icsEngineAnalyze - possible call from other functions */
14611 if (appData.icsEngineAnalyze) {
14612 appData.icsEngineAnalyze = FALSE;
14614 DisplayMessage("",_("Close ICS engine analyze..."));
14616 if (first.analysisSupport && first.analyzing) {
14617 SendToBoth("exit\n");
14618 first.analyzing = second.analyzing = FALSE;
14620 thinkOutput[0] = NULLCHAR;
14624 EditPositionDone (Boolean fakeRights)
14626 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14628 startedFromSetupPosition = TRUE;
14629 InitChessProgram(&first, FALSE);
14630 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14631 boards[0][EP_STATUS] = EP_NONE;
14632 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14633 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14634 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14635 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14636 } else boards[0][CASTLING][2] = NoRights;
14637 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14638 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14639 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14640 } else boards[0][CASTLING][5] = NoRights;
14641 if(gameInfo.variant == VariantSChess) {
14643 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14644 boards[0][VIRGIN][i] = 0;
14645 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14646 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14650 SendToProgram("force\n", &first);
14651 if (blackPlaysFirst) {
14652 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14653 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14654 currentMove = forwardMostMove = backwardMostMove = 1;
14655 CopyBoard(boards[1], boards[0]);
14657 currentMove = forwardMostMove = backwardMostMove = 0;
14659 SendBoard(&first, forwardMostMove);
14660 if (appData.debugMode) {
14661 fprintf(debugFP, "EditPosDone\n");
14664 DisplayMessage("", "");
14665 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14666 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14667 gameMode = EditGame;
14669 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14670 ClearHighlights(); /* [AS] */
14673 /* Pause for `ms' milliseconds */
14674 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14676 TimeDelay (long ms)
14683 } while (SubtractTimeMarks(&m2, &m1) < ms);
14686 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14688 SendMultiLineToICS (char *buf)
14690 char temp[MSG_SIZ+1], *p;
14697 strncpy(temp, buf, len);
14702 if (*p == '\n' || *p == '\r')
14707 strcat(temp, "\n");
14709 SendToPlayer(temp, strlen(temp));
14713 SetWhiteToPlayEvent ()
14715 if (gameMode == EditPosition) {
14716 blackPlaysFirst = FALSE;
14717 DisplayBothClocks(); /* works because currentMove is 0 */
14718 } else if (gameMode == IcsExamining) {
14719 SendToICS(ics_prefix);
14720 SendToICS("tomove white\n");
14725 SetBlackToPlayEvent ()
14727 if (gameMode == EditPosition) {
14728 blackPlaysFirst = TRUE;
14729 currentMove = 1; /* kludge */
14730 DisplayBothClocks();
14732 } else if (gameMode == IcsExamining) {
14733 SendToICS(ics_prefix);
14734 SendToICS("tomove black\n");
14739 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14742 ChessSquare piece = boards[0][y][x];
14743 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14744 static int lastVariant;
14746 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14748 switch (selection) {
14750 CopyBoard(currentBoard, boards[0]);
14751 CopyBoard(menuBoard, initialPosition);
14752 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14753 SendToICS(ics_prefix);
14754 SendToICS("bsetup clear\n");
14755 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14756 SendToICS(ics_prefix);
14757 SendToICS("clearboard\n");
14760 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14761 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14762 for (y = 0; y < BOARD_HEIGHT; y++) {
14763 if (gameMode == IcsExamining) {
14764 if (boards[currentMove][y][x] != EmptySquare) {
14765 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14770 if(boards[0][y][x] != p) nonEmpty++;
14771 boards[0][y][x] = p;
14774 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14776 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14777 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
14778 ChessSquare p = menuBoard[0][x];
14779 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14780 p = menuBoard[BOARD_HEIGHT-1][x];
14781 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14783 DisplayMessage("Clicking clock again restores position", "");
14784 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14785 if(!nonEmpty) { // asked to clear an empty board
14786 CopyBoard(boards[0], menuBoard);
14788 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14789 CopyBoard(boards[0], initialPosition);
14791 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14792 && !CompareBoards(nullBoard, erasedBoard)) {
14793 CopyBoard(boards[0], erasedBoard);
14795 CopyBoard(erasedBoard, currentBoard);
14799 if (gameMode == EditPosition) {
14800 DrawPosition(FALSE, boards[0]);
14805 SetWhiteToPlayEvent();
14809 SetBlackToPlayEvent();
14813 if (gameMode == IcsExamining) {
14814 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14815 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14818 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14819 if(x == BOARD_LEFT-2) {
14820 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14821 boards[0][y][1] = 0;
14823 if(x == BOARD_RGHT+1) {
14824 if(y >= gameInfo.holdingsSize) break;
14825 boards[0][y][BOARD_WIDTH-2] = 0;
14828 boards[0][y][x] = EmptySquare;
14829 DrawPosition(FALSE, boards[0]);
14834 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14835 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14836 selection = (ChessSquare) (PROMOTED piece);
14837 } else if(piece == EmptySquare) selection = WhiteSilver;
14838 else selection = (ChessSquare)((int)piece - 1);
14842 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14843 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14844 selection = (ChessSquare) (DEMOTED piece);
14845 } else if(piece == EmptySquare) selection = BlackSilver;
14846 else selection = (ChessSquare)((int)piece + 1);
14851 if(gameInfo.variant == VariantShatranj ||
14852 gameInfo.variant == VariantXiangqi ||
14853 gameInfo.variant == VariantCourier ||
14854 gameInfo.variant == VariantASEAN ||
14855 gameInfo.variant == VariantMakruk )
14856 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14861 if(gameInfo.variant == VariantXiangqi)
14862 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14863 if(gameInfo.variant == VariantKnightmate)
14864 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14867 if (gameMode == IcsExamining) {
14868 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14869 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14870 PieceToChar(selection), AAA + x, ONE + y);
14873 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14875 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14876 n = PieceToNumber(selection - BlackPawn);
14877 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14878 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14879 boards[0][BOARD_HEIGHT-1-n][1]++;
14881 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14882 n = PieceToNumber(selection);
14883 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14884 boards[0][n][BOARD_WIDTH-1] = selection;
14885 boards[0][n][BOARD_WIDTH-2]++;
14888 boards[0][y][x] = selection;
14889 DrawPosition(TRUE, boards[0]);
14891 fromX = fromY = -1;
14899 DropMenuEvent (ChessSquare selection, int x, int y)
14901 ChessMove moveType;
14903 switch (gameMode) {
14904 case IcsPlayingWhite:
14905 case MachinePlaysBlack:
14906 if (!WhiteOnMove(currentMove)) {
14907 DisplayMoveError(_("It is Black's turn"));
14910 moveType = WhiteDrop;
14912 case IcsPlayingBlack:
14913 case MachinePlaysWhite:
14914 if (WhiteOnMove(currentMove)) {
14915 DisplayMoveError(_("It is White's turn"));
14918 moveType = BlackDrop;
14921 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14927 if (moveType == BlackDrop && selection < BlackPawn) {
14928 selection = (ChessSquare) ((int) selection
14929 + (int) BlackPawn - (int) WhitePawn);
14931 if (boards[currentMove][y][x] != EmptySquare) {
14932 DisplayMoveError(_("That square is occupied"));
14936 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14942 /* Accept a pending offer of any kind from opponent */
14944 if (appData.icsActive) {
14945 SendToICS(ics_prefix);
14946 SendToICS("accept\n");
14947 } else if (cmailMsgLoaded) {
14948 if (currentMove == cmailOldMove &&
14949 commentList[cmailOldMove] != NULL &&
14950 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14951 "Black offers a draw" : "White offers a draw")) {
14953 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14954 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14956 DisplayError(_("There is no pending offer on this move"), 0);
14957 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14960 /* Not used for offers from chess program */
14967 /* Decline a pending offer of any kind from opponent */
14969 if (appData.icsActive) {
14970 SendToICS(ics_prefix);
14971 SendToICS("decline\n");
14972 } else if (cmailMsgLoaded) {
14973 if (currentMove == cmailOldMove &&
14974 commentList[cmailOldMove] != NULL &&
14975 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14976 "Black offers a draw" : "White offers a draw")) {
14978 AppendComment(cmailOldMove, "Draw declined", TRUE);
14979 DisplayComment(cmailOldMove - 1, "Draw declined");
14982 DisplayError(_("There is no pending offer on this move"), 0);
14985 /* Not used for offers from chess program */
14992 /* Issue ICS rematch command */
14993 if (appData.icsActive) {
14994 SendToICS(ics_prefix);
14995 SendToICS("rematch\n");
15002 /* Call your opponent's flag (claim a win on time) */
15003 if (appData.icsActive) {
15004 SendToICS(ics_prefix);
15005 SendToICS("flag\n");
15007 switch (gameMode) {
15010 case MachinePlaysWhite:
15013 GameEnds(GameIsDrawn, "Both players ran out of time",
15016 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15018 DisplayError(_("Your opponent is not out of time"), 0);
15021 case MachinePlaysBlack:
15024 GameEnds(GameIsDrawn, "Both players ran out of time",
15027 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15029 DisplayError(_("Your opponent is not out of time"), 0);
15037 ClockClick (int which)
15038 { // [HGM] code moved to back-end from winboard.c
15039 if(which) { // black clock
15040 if (gameMode == EditPosition || gameMode == IcsExamining) {
15041 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15042 SetBlackToPlayEvent();
15043 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15044 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15045 } else if (shiftKey) {
15046 AdjustClock(which, -1);
15047 } else if (gameMode == IcsPlayingWhite ||
15048 gameMode == MachinePlaysBlack) {
15051 } else { // white clock
15052 if (gameMode == EditPosition || gameMode == IcsExamining) {
15053 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15054 SetWhiteToPlayEvent();
15055 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15056 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15057 } else if (shiftKey) {
15058 AdjustClock(which, -1);
15059 } else if (gameMode == IcsPlayingBlack ||
15060 gameMode == MachinePlaysWhite) {
15069 /* Offer draw or accept pending draw offer from opponent */
15071 if (appData.icsActive) {
15072 /* Note: tournament rules require draw offers to be
15073 made after you make your move but before you punch
15074 your clock. Currently ICS doesn't let you do that;
15075 instead, you immediately punch your clock after making
15076 a move, but you can offer a draw at any time. */
15078 SendToICS(ics_prefix);
15079 SendToICS("draw\n");
15080 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15081 } else if (cmailMsgLoaded) {
15082 if (currentMove == cmailOldMove &&
15083 commentList[cmailOldMove] != NULL &&
15084 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15085 "Black offers a draw" : "White offers a draw")) {
15086 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15087 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15088 } else if (currentMove == cmailOldMove + 1) {
15089 char *offer = WhiteOnMove(cmailOldMove) ?
15090 "White offers a draw" : "Black offers a draw";
15091 AppendComment(currentMove, offer, TRUE);
15092 DisplayComment(currentMove - 1, offer);
15093 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15095 DisplayError(_("You must make your move before offering a draw"), 0);
15096 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15098 } else if (first.offeredDraw) {
15099 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15101 if (first.sendDrawOffers) {
15102 SendToProgram("draw\n", &first);
15103 userOfferedDraw = TRUE;
15111 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15113 if (appData.icsActive) {
15114 SendToICS(ics_prefix);
15115 SendToICS("adjourn\n");
15117 /* Currently GNU Chess doesn't offer or accept Adjourns */
15125 /* Offer Abort or accept pending Abort offer from opponent */
15127 if (appData.icsActive) {
15128 SendToICS(ics_prefix);
15129 SendToICS("abort\n");
15131 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15138 /* Resign. You can do this even if it's not your turn. */
15140 if (appData.icsActive) {
15141 SendToICS(ics_prefix);
15142 SendToICS("resign\n");
15144 switch (gameMode) {
15145 case MachinePlaysWhite:
15146 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15148 case MachinePlaysBlack:
15149 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15152 if (cmailMsgLoaded) {
15154 if (WhiteOnMove(cmailOldMove)) {
15155 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15157 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15159 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15170 StopObservingEvent ()
15172 /* Stop observing current games */
15173 SendToICS(ics_prefix);
15174 SendToICS("unobserve\n");
15178 StopExaminingEvent ()
15180 /* Stop observing current game */
15181 SendToICS(ics_prefix);
15182 SendToICS("unexamine\n");
15186 ForwardInner (int target)
15188 int limit; int oldSeekGraphUp = seekGraphUp;
15190 if (appData.debugMode)
15191 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15192 target, currentMove, forwardMostMove);
15194 if (gameMode == EditPosition)
15197 seekGraphUp = FALSE;
15198 MarkTargetSquares(1);
15200 if (gameMode == PlayFromGameFile && !pausing)
15203 if (gameMode == IcsExamining && pausing)
15204 limit = pauseExamForwardMostMove;
15206 limit = forwardMostMove;
15208 if (target > limit) target = limit;
15210 if (target > 0 && moveList[target - 1][0]) {
15211 int fromX, fromY, toX, toY;
15212 toX = moveList[target - 1][2] - AAA;
15213 toY = moveList[target - 1][3] - ONE;
15214 if (moveList[target - 1][1] == '@') {
15215 if (appData.highlightLastMove) {
15216 SetHighlights(-1, -1, toX, toY);
15219 fromX = moveList[target - 1][0] - AAA;
15220 fromY = moveList[target - 1][1] - ONE;
15221 if (target == currentMove + 1) {
15222 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15224 if (appData.highlightLastMove) {
15225 SetHighlights(fromX, fromY, toX, toY);
15229 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15230 gameMode == Training || gameMode == PlayFromGameFile ||
15231 gameMode == AnalyzeFile) {
15232 while (currentMove < target) {
15233 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15234 SendMoveToProgram(currentMove++, &first);
15237 currentMove = target;
15240 if (gameMode == EditGame || gameMode == EndOfGame) {
15241 whiteTimeRemaining = timeRemaining[0][currentMove];
15242 blackTimeRemaining = timeRemaining[1][currentMove];
15244 DisplayBothClocks();
15245 DisplayMove(currentMove - 1);
15246 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15247 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15248 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15249 DisplayComment(currentMove - 1, commentList[currentMove]);
15251 ClearMap(); // [HGM] exclude: invalidate map
15258 if (gameMode == IcsExamining && !pausing) {
15259 SendToICS(ics_prefix);
15260 SendToICS("forward\n");
15262 ForwardInner(currentMove + 1);
15269 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15270 /* to optimze, we temporarily turn off analysis mode while we feed
15271 * the remaining moves to the engine. Otherwise we get analysis output
15274 if (first.analysisSupport) {
15275 SendToProgram("exit\nforce\n", &first);
15276 first.analyzing = FALSE;
15280 if (gameMode == IcsExamining && !pausing) {
15281 SendToICS(ics_prefix);
15282 SendToICS("forward 999999\n");
15284 ForwardInner(forwardMostMove);
15287 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15288 /* we have fed all the moves, so reactivate analysis mode */
15289 SendToProgram("analyze\n", &first);
15290 first.analyzing = TRUE;
15291 /*first.maybeThinking = TRUE;*/
15292 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15297 BackwardInner (int target)
15299 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15301 if (appData.debugMode)
15302 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15303 target, currentMove, forwardMostMove);
15305 if (gameMode == EditPosition) return;
15306 seekGraphUp = FALSE;
15307 MarkTargetSquares(1);
15308 if (currentMove <= backwardMostMove) {
15310 DrawPosition(full_redraw, boards[currentMove]);
15313 if (gameMode == PlayFromGameFile && !pausing)
15316 if (moveList[target][0]) {
15317 int fromX, fromY, toX, toY;
15318 toX = moveList[target][2] - AAA;
15319 toY = moveList[target][3] - ONE;
15320 if (moveList[target][1] == '@') {
15321 if (appData.highlightLastMove) {
15322 SetHighlights(-1, -1, toX, toY);
15325 fromX = moveList[target][0] - AAA;
15326 fromY = moveList[target][1] - ONE;
15327 if (target == currentMove - 1) {
15328 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15330 if (appData.highlightLastMove) {
15331 SetHighlights(fromX, fromY, toX, toY);
15335 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15336 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15337 while (currentMove > target) {
15338 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15339 // null move cannot be undone. Reload program with move history before it.
15341 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15342 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15344 SendBoard(&first, i);
15345 if(second.analyzing) SendBoard(&second, i);
15346 for(currentMove=i; currentMove<target; currentMove++) {
15347 SendMoveToProgram(currentMove, &first);
15348 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15352 SendToBoth("undo\n");
15356 currentMove = target;
15359 if (gameMode == EditGame || gameMode == EndOfGame) {
15360 whiteTimeRemaining = timeRemaining[0][currentMove];
15361 blackTimeRemaining = timeRemaining[1][currentMove];
15363 DisplayBothClocks();
15364 DisplayMove(currentMove - 1);
15365 DrawPosition(full_redraw, boards[currentMove]);
15366 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15367 // [HGM] PV info: routine tests if comment empty
15368 DisplayComment(currentMove - 1, commentList[currentMove]);
15369 ClearMap(); // [HGM] exclude: invalidate map
15375 if (gameMode == IcsExamining && !pausing) {
15376 SendToICS(ics_prefix);
15377 SendToICS("backward\n");
15379 BackwardInner(currentMove - 1);
15386 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15387 /* to optimize, we temporarily turn off analysis mode while we undo
15388 * all the moves. Otherwise we get analysis output after each undo.
15390 if (first.analysisSupport) {
15391 SendToProgram("exit\nforce\n", &first);
15392 first.analyzing = FALSE;
15396 if (gameMode == IcsExamining && !pausing) {
15397 SendToICS(ics_prefix);
15398 SendToICS("backward 999999\n");
15400 BackwardInner(backwardMostMove);
15403 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15404 /* we have fed all the moves, so reactivate analysis mode */
15405 SendToProgram("analyze\n", &first);
15406 first.analyzing = TRUE;
15407 /*first.maybeThinking = TRUE;*/
15408 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15415 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15416 if (to >= forwardMostMove) to = forwardMostMove;
15417 if (to <= backwardMostMove) to = backwardMostMove;
15418 if (to < currentMove) {
15426 RevertEvent (Boolean annotate)
15428 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15431 if (gameMode != IcsExamining) {
15432 DisplayError(_("You are not examining a game"), 0);
15436 DisplayError(_("You can't revert while pausing"), 0);
15439 SendToICS(ics_prefix);
15440 SendToICS("revert\n");
15444 RetractMoveEvent ()
15446 switch (gameMode) {
15447 case MachinePlaysWhite:
15448 case MachinePlaysBlack:
15449 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15450 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15453 if (forwardMostMove < 2) return;
15454 currentMove = forwardMostMove = forwardMostMove - 2;
15455 whiteTimeRemaining = timeRemaining[0][currentMove];
15456 blackTimeRemaining = timeRemaining[1][currentMove];
15457 DisplayBothClocks();
15458 DisplayMove(currentMove - 1);
15459 ClearHighlights();/*!! could figure this out*/
15460 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15461 SendToProgram("remove\n", &first);
15462 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15465 case BeginningOfGame:
15469 case IcsPlayingWhite:
15470 case IcsPlayingBlack:
15471 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15472 SendToICS(ics_prefix);
15473 SendToICS("takeback 2\n");
15475 SendToICS(ics_prefix);
15476 SendToICS("takeback 1\n");
15485 ChessProgramState *cps;
15487 switch (gameMode) {
15488 case MachinePlaysWhite:
15489 if (!WhiteOnMove(forwardMostMove)) {
15490 DisplayError(_("It is your turn"), 0);
15495 case MachinePlaysBlack:
15496 if (WhiteOnMove(forwardMostMove)) {
15497 DisplayError(_("It is your turn"), 0);
15502 case TwoMachinesPlay:
15503 if (WhiteOnMove(forwardMostMove) ==
15504 (first.twoMachinesColor[0] == 'w')) {
15510 case BeginningOfGame:
15514 SendToProgram("?\n", cps);
15518 TruncateGameEvent ()
15521 if (gameMode != EditGame) return;
15528 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15529 if (forwardMostMove > currentMove) {
15530 if (gameInfo.resultDetails != NULL) {
15531 free(gameInfo.resultDetails);
15532 gameInfo.resultDetails = NULL;
15533 gameInfo.result = GameUnfinished;
15535 forwardMostMove = currentMove;
15536 HistorySet(parseList, backwardMostMove, forwardMostMove,
15544 if (appData.noChessProgram) return;
15545 switch (gameMode) {
15546 case MachinePlaysWhite:
15547 if (WhiteOnMove(forwardMostMove)) {
15548 DisplayError(_("Wait until your turn."), 0);
15552 case BeginningOfGame:
15553 case MachinePlaysBlack:
15554 if (!WhiteOnMove(forwardMostMove)) {
15555 DisplayError(_("Wait until your turn."), 0);
15560 DisplayError(_("No hint available"), 0);
15563 SendToProgram("hint\n", &first);
15564 hintRequested = TRUE;
15570 ListGame * lg = (ListGame *) gameList.head;
15573 static int secondTime = FALSE;
15575 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15576 DisplayError(_("Game list not loaded or empty"), 0);
15580 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15583 DisplayNote(_("Book file exists! Try again for overwrite."));
15587 creatingBook = TRUE;
15588 secondTime = FALSE;
15590 /* Get list size */
15591 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15592 LoadGame(f, nItem, "", TRUE);
15593 AddGameToBook(TRUE);
15594 lg = (ListGame *) lg->node.succ;
15597 creatingBook = FALSE;
15604 if (appData.noChessProgram) return;
15605 switch (gameMode) {
15606 case MachinePlaysWhite:
15607 if (WhiteOnMove(forwardMostMove)) {
15608 DisplayError(_("Wait until your turn."), 0);
15612 case BeginningOfGame:
15613 case MachinePlaysBlack:
15614 if (!WhiteOnMove(forwardMostMove)) {
15615 DisplayError(_("Wait until your turn."), 0);
15620 EditPositionDone(TRUE);
15622 case TwoMachinesPlay:
15627 SendToProgram("bk\n", &first);
15628 bookOutput[0] = NULLCHAR;
15629 bookRequested = TRUE;
15635 char *tags = PGNTags(&gameInfo);
15636 TagsPopUp(tags, CmailMsg());
15640 /* end button procedures */
15643 PrintPosition (FILE *fp, int move)
15647 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15648 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15649 char c = PieceToChar(boards[move][i][j]);
15650 fputc(c == 'x' ? '.' : c, fp);
15651 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15654 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15655 fprintf(fp, "white to play\n");
15657 fprintf(fp, "black to play\n");
15661 PrintOpponents (FILE *fp)
15663 if (gameInfo.white != NULL) {
15664 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15670 /* Find last component of program's own name, using some heuristics */
15672 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15675 int local = (strcmp(host, "localhost") == 0);
15676 while (!local && (p = strchr(prog, ';')) != NULL) {
15678 while (*p == ' ') p++;
15681 if (*prog == '"' || *prog == '\'') {
15682 q = strchr(prog + 1, *prog);
15684 q = strchr(prog, ' ');
15686 if (q == NULL) q = prog + strlen(prog);
15688 while (p >= prog && *p != '/' && *p != '\\') p--;
15690 if(p == prog && *p == '"') p++;
15692 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15693 memcpy(buf, p, q - p);
15694 buf[q - p] = NULLCHAR;
15702 TimeControlTagValue ()
15705 if (!appData.clockMode) {
15706 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15707 } else if (movesPerSession > 0) {
15708 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15709 } else if (timeIncrement == 0) {
15710 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15712 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15714 return StrSave(buf);
15720 /* This routine is used only for certain modes */
15721 VariantClass v = gameInfo.variant;
15722 ChessMove r = GameUnfinished;
15725 if(keepInfo) return;
15727 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15728 r = gameInfo.result;
15729 p = gameInfo.resultDetails;
15730 gameInfo.resultDetails = NULL;
15732 ClearGameInfo(&gameInfo);
15733 gameInfo.variant = v;
15735 switch (gameMode) {
15736 case MachinePlaysWhite:
15737 gameInfo.event = StrSave( appData.pgnEventHeader );
15738 gameInfo.site = StrSave(HostName());
15739 gameInfo.date = PGNDate();
15740 gameInfo.round = StrSave("-");
15741 gameInfo.white = StrSave(first.tidy);
15742 gameInfo.black = StrSave(UserName());
15743 gameInfo.timeControl = TimeControlTagValue();
15746 case MachinePlaysBlack:
15747 gameInfo.event = StrSave( appData.pgnEventHeader );
15748 gameInfo.site = StrSave(HostName());
15749 gameInfo.date = PGNDate();
15750 gameInfo.round = StrSave("-");
15751 gameInfo.white = StrSave(UserName());
15752 gameInfo.black = StrSave(first.tidy);
15753 gameInfo.timeControl = TimeControlTagValue();
15756 case TwoMachinesPlay:
15757 gameInfo.event = StrSave( appData.pgnEventHeader );
15758 gameInfo.site = StrSave(HostName());
15759 gameInfo.date = PGNDate();
15762 snprintf(buf, MSG_SIZ, "%d", roundNr);
15763 gameInfo.round = StrSave(buf);
15765 gameInfo.round = StrSave("-");
15767 if (first.twoMachinesColor[0] == 'w') {
15768 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15769 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15771 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15772 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15774 gameInfo.timeControl = TimeControlTagValue();
15778 gameInfo.event = StrSave("Edited game");
15779 gameInfo.site = StrSave(HostName());
15780 gameInfo.date = PGNDate();
15781 gameInfo.round = StrSave("-");
15782 gameInfo.white = StrSave("-");
15783 gameInfo.black = StrSave("-");
15784 gameInfo.result = r;
15785 gameInfo.resultDetails = p;
15789 gameInfo.event = StrSave("Edited position");
15790 gameInfo.site = StrSave(HostName());
15791 gameInfo.date = PGNDate();
15792 gameInfo.round = StrSave("-");
15793 gameInfo.white = StrSave("-");
15794 gameInfo.black = StrSave("-");
15797 case IcsPlayingWhite:
15798 case IcsPlayingBlack:
15803 case PlayFromGameFile:
15804 gameInfo.event = StrSave("Game from non-PGN file");
15805 gameInfo.site = StrSave(HostName());
15806 gameInfo.date = PGNDate();
15807 gameInfo.round = StrSave("-");
15808 gameInfo.white = StrSave("?");
15809 gameInfo.black = StrSave("?");
15818 ReplaceComment (int index, char *text)
15824 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15825 pvInfoList[index-1].depth == len &&
15826 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15827 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15828 while (*text == '\n') text++;
15829 len = strlen(text);
15830 while (len > 0 && text[len - 1] == '\n') len--;
15832 if (commentList[index] != NULL)
15833 free(commentList[index]);
15836 commentList[index] = NULL;
15839 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15840 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15841 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15842 commentList[index] = (char *) malloc(len + 2);
15843 strncpy(commentList[index], text, len);
15844 commentList[index][len] = '\n';
15845 commentList[index][len + 1] = NULLCHAR;
15847 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15849 commentList[index] = (char *) malloc(len + 7);
15850 safeStrCpy(commentList[index], "{\n", 3);
15851 safeStrCpy(commentList[index]+2, text, len+1);
15852 commentList[index][len+2] = NULLCHAR;
15853 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15854 strcat(commentList[index], "\n}\n");
15859 CrushCRs (char *text)
15867 if (ch == '\r') continue;
15869 } while (ch != '\0');
15873 AppendComment (int index, char *text, Boolean addBraces)
15874 /* addBraces tells if we should add {} */
15879 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15880 if(addBraces == 3) addBraces = 0; else // force appending literally
15881 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15884 while (*text == '\n') text++;
15885 len = strlen(text);
15886 while (len > 0 && text[len - 1] == '\n') len--;
15887 text[len] = NULLCHAR;
15889 if (len == 0) return;
15891 if (commentList[index] != NULL) {
15892 Boolean addClosingBrace = addBraces;
15893 old = commentList[index];
15894 oldlen = strlen(old);
15895 while(commentList[index][oldlen-1] == '\n')
15896 commentList[index][--oldlen] = NULLCHAR;
15897 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15898 safeStrCpy(commentList[index], old, oldlen + len + 6);
15900 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15901 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15902 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15903 while (*text == '\n') { text++; len--; }
15904 commentList[index][--oldlen] = NULLCHAR;
15906 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15907 else strcat(commentList[index], "\n");
15908 strcat(commentList[index], text);
15909 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15910 else strcat(commentList[index], "\n");
15912 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15914 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15915 else commentList[index][0] = NULLCHAR;
15916 strcat(commentList[index], text);
15917 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15918 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15923 FindStr (char * text, char * sub_text)
15925 char * result = strstr( text, sub_text );
15927 if( result != NULL ) {
15928 result += strlen( sub_text );
15934 /* [AS] Try to extract PV info from PGN comment */
15935 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15937 GetInfoFromComment (int index, char * text)
15939 char * sep = text, *p;
15941 if( text != NULL && index > 0 ) {
15944 int time = -1, sec = 0, deci;
15945 char * s_eval = FindStr( text, "[%eval " );
15946 char * s_emt = FindStr( text, "[%emt " );
15948 if( s_eval != NULL || s_emt != NULL ) {
15950 if(0) { // [HGM] this code is not finished, and could actually be detrimental
15955 if( s_eval != NULL ) {
15956 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15960 if( delim != ']' ) {
15965 if( s_emt != NULL ) {
15970 /* We expect something like: [+|-]nnn.nn/dd */
15973 if(*text != '{') return text; // [HGM] braces: must be normal comment
15975 sep = strchr( text, '/' );
15976 if( sep == NULL || sep < (text+4) ) {
15981 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15982 if(p[1] == '(') { // comment starts with PV
15983 p = strchr(p, ')'); // locate end of PV
15984 if(p == NULL || sep < p+5) return text;
15985 // at this point we have something like "{(.*) +0.23/6 ..."
15986 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15987 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15988 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15990 time = -1; sec = -1; deci = -1;
15991 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15992 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15993 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15994 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15998 if( score_lo < 0 || score_lo >= 100 ) {
16002 if(sec >= 0) time = 600*time + 10*sec; else
16003 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16005 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16007 /* [HGM] PV time: now locate end of PV info */
16008 while( *++sep >= '0' && *sep <= '9'); // strip depth
16010 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16012 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16014 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16015 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16026 pvInfoList[index-1].depth = depth;
16027 pvInfoList[index-1].score = score;
16028 pvInfoList[index-1].time = 10*time; // centi-sec
16029 if(*sep == '}') *sep = 0; else *--sep = '{';
16030 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16036 SendToProgram (char *message, ChessProgramState *cps)
16038 int count, outCount, error;
16041 if (cps->pr == NoProc) return;
16044 if (appData.debugMode) {
16047 fprintf(debugFP, "%ld >%-6s: %s",
16048 SubtractTimeMarks(&now, &programStartTime),
16049 cps->which, message);
16051 fprintf(serverFP, "%ld >%-6s: %s",
16052 SubtractTimeMarks(&now, &programStartTime),
16053 cps->which, message), fflush(serverFP);
16056 count = strlen(message);
16057 outCount = OutputToProcess(cps->pr, message, count, &error);
16058 if (outCount < count && !exiting
16059 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16060 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16061 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16062 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16063 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16064 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16065 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16066 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16068 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16069 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16070 gameInfo.result = res;
16072 gameInfo.resultDetails = StrSave(buf);
16074 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16075 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16080 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16084 ChessProgramState *cps = (ChessProgramState *)closure;
16086 if (isr != cps->isr) return; /* Killed intentionally */
16089 RemoveInputSource(cps->isr);
16090 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16091 _(cps->which), cps->program);
16092 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16093 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16094 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16095 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16096 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16097 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16099 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16100 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16101 gameInfo.result = res;
16103 gameInfo.resultDetails = StrSave(buf);
16105 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16106 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16108 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16109 _(cps->which), cps->program);
16110 RemoveInputSource(cps->isr);
16112 /* [AS] Program is misbehaving badly... kill it */
16113 if( count == -2 ) {
16114 DestroyChildProcess( cps->pr, 9 );
16118 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16123 if ((end_str = strchr(message, '\r')) != NULL)
16124 *end_str = NULLCHAR;
16125 if ((end_str = strchr(message, '\n')) != NULL)
16126 *end_str = NULLCHAR;
16128 if (appData.debugMode) {
16129 TimeMark now; int print = 1;
16130 char *quote = ""; char c; int i;
16132 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16133 char start = message[0];
16134 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16135 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16136 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16137 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16138 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16139 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16140 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16141 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16142 sscanf(message, "hint: %c", &c)!=1 &&
16143 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16144 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16145 print = (appData.engineComments >= 2);
16147 message[0] = start; // restore original message
16151 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16152 SubtractTimeMarks(&now, &programStartTime), cps->which,
16156 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16157 SubtractTimeMarks(&now, &programStartTime), cps->which,
16159 message), fflush(serverFP);
16163 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16164 if (appData.icsEngineAnalyze) {
16165 if (strstr(message, "whisper") != NULL ||
16166 strstr(message, "kibitz") != NULL ||
16167 strstr(message, "tellics") != NULL) return;
16170 HandleMachineMove(message, cps);
16175 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16180 if( timeControl_2 > 0 ) {
16181 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16182 tc = timeControl_2;
16185 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16186 inc /= cps->timeOdds;
16187 st /= cps->timeOdds;
16189 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16192 /* Set exact time per move, normally using st command */
16193 if (cps->stKludge) {
16194 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16196 if (seconds == 0) {
16197 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16199 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16202 snprintf(buf, MSG_SIZ, "st %d\n", st);
16205 /* Set conventional or incremental time control, using level command */
16206 if (seconds == 0) {
16207 /* Note old gnuchess bug -- minutes:seconds used to not work.
16208 Fixed in later versions, but still avoid :seconds
16209 when seconds is 0. */
16210 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16212 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16213 seconds, inc/1000.);
16216 SendToProgram(buf, cps);
16218 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16219 /* Orthogonally, limit search to given depth */
16221 if (cps->sdKludge) {
16222 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16224 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16226 SendToProgram(buf, cps);
16229 if(cps->nps >= 0) { /* [HGM] nps */
16230 if(cps->supportsNPS == FALSE)
16231 cps->nps = -1; // don't use if engine explicitly says not supported!
16233 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16234 SendToProgram(buf, cps);
16239 ChessProgramState *
16241 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16243 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16244 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16250 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16252 char message[MSG_SIZ];
16255 /* Note: this routine must be called when the clocks are stopped
16256 or when they have *just* been set or switched; otherwise
16257 it will be off by the time since the current tick started.
16259 if (machineWhite) {
16260 time = whiteTimeRemaining / 10;
16261 otime = blackTimeRemaining / 10;
16263 time = blackTimeRemaining / 10;
16264 otime = whiteTimeRemaining / 10;
16266 /* [HGM] translate opponent's time by time-odds factor */
16267 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16269 if (time <= 0) time = 1;
16270 if (otime <= 0) otime = 1;
16272 snprintf(message, MSG_SIZ, "time %ld\n", time);
16273 SendToProgram(message, cps);
16275 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16276 SendToProgram(message, cps);
16280 EngineDefinedVariant (ChessProgramState *cps, int n)
16281 { // return name of n-th unknown variant that engine supports
16282 static char buf[MSG_SIZ];
16283 char *p, *s = cps->variants;
16284 if(!s) return NULL;
16285 do { // parse string from variants feature
16287 p = strchr(s, ',');
16288 if(p) *p = NULLCHAR;
16289 v = StringToVariant(s);
16290 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16291 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16292 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16295 if(n < 0) return buf;
16301 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16304 int len = strlen(name);
16307 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16309 sscanf(*p, "%d", &val);
16311 while (**p && **p != ' ')
16313 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16314 SendToProgram(buf, cps);
16321 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16324 int len = strlen(name);
16325 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16327 sscanf(*p, "%d", loc);
16328 while (**p && **p != ' ') (*p)++;
16329 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16330 SendToProgram(buf, cps);
16337 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16340 int len = strlen(name);
16341 if (strncmp((*p), name, len) == 0
16342 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16344 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16345 sscanf(*p, "%[^\"]", *loc);
16346 while (**p && **p != '\"') (*p)++;
16347 if (**p == '\"') (*p)++;
16348 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16349 SendToProgram(buf, cps);
16356 ParseOption (Option *opt, ChessProgramState *cps)
16357 // [HGM] options: process the string that defines an engine option, and determine
16358 // name, type, default value, and allowed value range
16360 char *p, *q, buf[MSG_SIZ];
16361 int n, min = (-1)<<31, max = 1<<31, def;
16363 if(p = strstr(opt->name, " -spin ")) {
16364 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16365 if(max < min) max = min; // enforce consistency
16366 if(def < min) def = min;
16367 if(def > max) def = max;
16372 } else if((p = strstr(opt->name, " -slider "))) {
16373 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16374 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16375 if(max < min) max = min; // enforce consistency
16376 if(def < min) def = min;
16377 if(def > max) def = max;
16381 opt->type = Spin; // Slider;
16382 } else if((p = strstr(opt->name, " -string "))) {
16383 opt->textValue = p+9;
16384 opt->type = TextBox;
16385 } else if((p = strstr(opt->name, " -file "))) {
16386 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16387 opt->textValue = p+7;
16388 opt->type = FileName; // FileName;
16389 } else if((p = strstr(opt->name, " -path "))) {
16390 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16391 opt->textValue = p+7;
16392 opt->type = PathName; // PathName;
16393 } else if(p = strstr(opt->name, " -check ")) {
16394 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16395 opt->value = (def != 0);
16396 opt->type = CheckBox;
16397 } else if(p = strstr(opt->name, " -combo ")) {
16398 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16399 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16400 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16401 opt->value = n = 0;
16402 while(q = StrStr(q, " /// ")) {
16403 n++; *q = 0; // count choices, and null-terminate each of them
16405 if(*q == '*') { // remember default, which is marked with * prefix
16409 cps->comboList[cps->comboCnt++] = q;
16411 cps->comboList[cps->comboCnt++] = NULL;
16413 opt->type = ComboBox;
16414 } else if(p = strstr(opt->name, " -button")) {
16415 opt->type = Button;
16416 } else if(p = strstr(opt->name, " -save")) {
16417 opt->type = SaveButton;
16418 } else return FALSE;
16419 *p = 0; // terminate option name
16420 // now look if the command-line options define a setting for this engine option.
16421 if(cps->optionSettings && cps->optionSettings[0])
16422 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16423 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16424 snprintf(buf, MSG_SIZ, "option %s", p);
16425 if(p = strstr(buf, ",")) *p = 0;
16426 if(q = strchr(buf, '=')) switch(opt->type) {
16428 for(n=0; n<opt->max; n++)
16429 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16432 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16436 opt->value = atoi(q+1);
16441 SendToProgram(buf, cps);
16447 FeatureDone (ChessProgramState *cps, int val)
16449 DelayedEventCallback cb = GetDelayedEvent();
16450 if ((cb == InitBackEnd3 && cps == &first) ||
16451 (cb == SettingsMenuIfReady && cps == &second) ||
16452 (cb == LoadEngine) ||
16453 (cb == TwoMachinesEventIfReady)) {
16454 CancelDelayedEvent();
16455 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16457 cps->initDone = val;
16458 if(val) cps->reload = FALSE;
16461 /* Parse feature command from engine */
16463 ParseFeatures (char *args, ChessProgramState *cps)
16471 while (*p == ' ') p++;
16472 if (*p == NULLCHAR) return;
16474 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16475 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16476 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16477 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16478 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16479 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16480 if (BoolFeature(&p, "reuse", &val, cps)) {
16481 /* Engine can disable reuse, but can't enable it if user said no */
16482 if (!val) cps->reuse = FALSE;
16485 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16486 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16487 if (gameMode == TwoMachinesPlay) {
16488 DisplayTwoMachinesTitle();
16494 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16495 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16496 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16497 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16498 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16499 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16500 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16501 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16502 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16503 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16504 if (IntFeature(&p, "done", &val, cps)) {
16505 FeatureDone(cps, val);
16508 /* Added by Tord: */
16509 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16510 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16511 /* End of additions by Tord */
16513 /* [HGM] added features: */
16514 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16515 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16516 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16517 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16518 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16519 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16520 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16521 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16522 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16523 FREE(cps->option[cps->nrOptions].name);
16524 cps->option[cps->nrOptions].name = q; q = NULL;
16525 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16526 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16527 SendToProgram(buf, cps);
16530 if(cps->nrOptions >= MAX_OPTIONS) {
16532 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16533 DisplayError(buf, 0);
16537 /* End of additions by HGM */
16539 /* unknown feature: complain and skip */
16541 while (*q && *q != '=') q++;
16542 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16543 SendToProgram(buf, cps);
16549 while (*p && *p != '\"') p++;
16550 if (*p == '\"') p++;
16552 while (*p && *p != ' ') p++;
16560 PeriodicUpdatesEvent (int newState)
16562 if (newState == appData.periodicUpdates)
16565 appData.periodicUpdates=newState;
16567 /* Display type changes, so update it now */
16568 // DisplayAnalysis();
16570 /* Get the ball rolling again... */
16572 AnalysisPeriodicEvent(1);
16573 StartAnalysisClock();
16578 PonderNextMoveEvent (int newState)
16580 if (newState == appData.ponderNextMove) return;
16581 if (gameMode == EditPosition) EditPositionDone(TRUE);
16583 SendToProgram("hard\n", &first);
16584 if (gameMode == TwoMachinesPlay) {
16585 SendToProgram("hard\n", &second);
16588 SendToProgram("easy\n", &first);
16589 thinkOutput[0] = NULLCHAR;
16590 if (gameMode == TwoMachinesPlay) {
16591 SendToProgram("easy\n", &second);
16594 appData.ponderNextMove = newState;
16598 NewSettingEvent (int option, int *feature, char *command, int value)
16602 if (gameMode == EditPosition) EditPositionDone(TRUE);
16603 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16604 if(feature == NULL || *feature) SendToProgram(buf, &first);
16605 if (gameMode == TwoMachinesPlay) {
16606 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16611 ShowThinkingEvent ()
16612 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16614 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16615 int newState = appData.showThinking
16616 // [HGM] thinking: other features now need thinking output as well
16617 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16619 if (oldState == newState) return;
16620 oldState = newState;
16621 if (gameMode == EditPosition) EditPositionDone(TRUE);
16623 SendToProgram("post\n", &first);
16624 if (gameMode == TwoMachinesPlay) {
16625 SendToProgram("post\n", &second);
16628 SendToProgram("nopost\n", &first);
16629 thinkOutput[0] = NULLCHAR;
16630 if (gameMode == TwoMachinesPlay) {
16631 SendToProgram("nopost\n", &second);
16634 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16638 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16640 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16641 if (pr == NoProc) return;
16642 AskQuestion(title, question, replyPrefix, pr);
16646 TypeInEvent (char firstChar)
16648 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16649 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16650 gameMode == AnalyzeMode || gameMode == EditGame ||
16651 gameMode == EditPosition || gameMode == IcsExamining ||
16652 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16653 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16654 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16655 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16656 gameMode == Training) PopUpMoveDialog(firstChar);
16660 TypeInDoneEvent (char *move)
16663 int n, fromX, fromY, toX, toY;
16665 ChessMove moveType;
16668 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16669 EditPositionPasteFEN(move);
16672 // [HGM] movenum: allow move number to be typed in any mode
16673 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16677 // undocumented kludge: allow command-line option to be typed in!
16678 // (potentially fatal, and does not implement the effect of the option.)
16679 // should only be used for options that are values on which future decisions will be made,
16680 // and definitely not on options that would be used during initialization.
16681 if(strstr(move, "!!! -") == move) {
16682 ParseArgsFromString(move+4);
16686 if (gameMode != EditGame && currentMove != forwardMostMove &&
16687 gameMode != Training) {
16688 DisplayMoveError(_("Displayed move is not current"));
16690 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16691 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16692 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16693 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16694 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16695 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16697 DisplayMoveError(_("Could not parse move"));
16703 DisplayMove (int moveNumber)
16705 char message[MSG_SIZ];
16707 char cpThinkOutput[MSG_SIZ];
16709 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16711 if (moveNumber == forwardMostMove - 1 ||
16712 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16714 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16716 if (strchr(cpThinkOutput, '\n')) {
16717 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16720 *cpThinkOutput = NULLCHAR;
16723 /* [AS] Hide thinking from human user */
16724 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16725 *cpThinkOutput = NULLCHAR;
16726 if( thinkOutput[0] != NULLCHAR ) {
16729 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16730 cpThinkOutput[i] = '.';
16732 cpThinkOutput[i] = NULLCHAR;
16733 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16737 if (moveNumber == forwardMostMove - 1 &&
16738 gameInfo.resultDetails != NULL) {
16739 if (gameInfo.resultDetails[0] == NULLCHAR) {
16740 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16742 snprintf(res, MSG_SIZ, " {%s} %s",
16743 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16749 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16750 DisplayMessage(res, cpThinkOutput);
16752 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16753 WhiteOnMove(moveNumber) ? " " : ".. ",
16754 parseList[moveNumber], res);
16755 DisplayMessage(message, cpThinkOutput);
16760 DisplayComment (int moveNumber, char *text)
16762 char title[MSG_SIZ];
16764 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16765 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16767 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16768 WhiteOnMove(moveNumber) ? " " : ".. ",
16769 parseList[moveNumber]);
16771 if (text != NULL && (appData.autoDisplayComment || commentUp))
16772 CommentPopUp(title, text);
16775 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16776 * might be busy thinking or pondering. It can be omitted if your
16777 * gnuchess is configured to stop thinking immediately on any user
16778 * input. However, that gnuchess feature depends on the FIONREAD
16779 * ioctl, which does not work properly on some flavors of Unix.
16782 Attention (ChessProgramState *cps)
16785 if (!cps->useSigint) return;
16786 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16787 switch (gameMode) {
16788 case MachinePlaysWhite:
16789 case MachinePlaysBlack:
16790 case TwoMachinesPlay:
16791 case IcsPlayingWhite:
16792 case IcsPlayingBlack:
16795 /* Skip if we know it isn't thinking */
16796 if (!cps->maybeThinking) return;
16797 if (appData.debugMode)
16798 fprintf(debugFP, "Interrupting %s\n", cps->which);
16799 InterruptChildProcess(cps->pr);
16800 cps->maybeThinking = FALSE;
16805 #endif /*ATTENTION*/
16811 if (whiteTimeRemaining <= 0) {
16814 if (appData.icsActive) {
16815 if (appData.autoCallFlag &&
16816 gameMode == IcsPlayingBlack && !blackFlag) {
16817 SendToICS(ics_prefix);
16818 SendToICS("flag\n");
16822 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16824 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16825 if (appData.autoCallFlag) {
16826 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16833 if (blackTimeRemaining <= 0) {
16836 if (appData.icsActive) {
16837 if (appData.autoCallFlag &&
16838 gameMode == IcsPlayingWhite && !whiteFlag) {
16839 SendToICS(ics_prefix);
16840 SendToICS("flag\n");
16844 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16846 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16847 if (appData.autoCallFlag) {
16848 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16859 CheckTimeControl ()
16861 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16862 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16865 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16867 if ( !WhiteOnMove(forwardMostMove) ) {
16868 /* White made time control */
16869 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16870 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16871 /* [HGM] time odds: correct new time quota for time odds! */
16872 / WhitePlayer()->timeOdds;
16873 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16875 lastBlack -= blackTimeRemaining;
16876 /* Black made time control */
16877 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16878 / WhitePlayer()->other->timeOdds;
16879 lastWhite = whiteTimeRemaining;
16884 DisplayBothClocks ()
16886 int wom = gameMode == EditPosition ?
16887 !blackPlaysFirst : WhiteOnMove(currentMove);
16888 DisplayWhiteClock(whiteTimeRemaining, wom);
16889 DisplayBlackClock(blackTimeRemaining, !wom);
16893 /* Timekeeping seems to be a portability nightmare. I think everyone
16894 has ftime(), but I'm really not sure, so I'm including some ifdefs
16895 to use other calls if you don't. Clocks will be less accurate if
16896 you have neither ftime nor gettimeofday.
16899 /* VS 2008 requires the #include outside of the function */
16900 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16901 #include <sys/timeb.h>
16904 /* Get the current time as a TimeMark */
16906 GetTimeMark (TimeMark *tm)
16908 #if HAVE_GETTIMEOFDAY
16910 struct timeval timeVal;
16911 struct timezone timeZone;
16913 gettimeofday(&timeVal, &timeZone);
16914 tm->sec = (long) timeVal.tv_sec;
16915 tm->ms = (int) (timeVal.tv_usec / 1000L);
16917 #else /*!HAVE_GETTIMEOFDAY*/
16920 // include <sys/timeb.h> / moved to just above start of function
16921 struct timeb timeB;
16924 tm->sec = (long) timeB.time;
16925 tm->ms = (int) timeB.millitm;
16927 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16928 tm->sec = (long) time(NULL);
16934 /* Return the difference in milliseconds between two
16935 time marks. We assume the difference will fit in a long!
16938 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16940 return 1000L*(tm2->sec - tm1->sec) +
16941 (long) (tm2->ms - tm1->ms);
16946 * Code to manage the game clocks.
16948 * In tournament play, black starts the clock and then white makes a move.
16949 * We give the human user a slight advantage if he is playing white---the
16950 * clocks don't run until he makes his first move, so it takes zero time.
16951 * Also, we don't account for network lag, so we could get out of sync
16952 * with GNU Chess's clock -- but then, referees are always right.
16955 static TimeMark tickStartTM;
16956 static long intendedTickLength;
16959 NextTickLength (long timeRemaining)
16961 long nominalTickLength, nextTickLength;
16963 if (timeRemaining > 0L && timeRemaining <= 10000L)
16964 nominalTickLength = 100L;
16966 nominalTickLength = 1000L;
16967 nextTickLength = timeRemaining % nominalTickLength;
16968 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16970 return nextTickLength;
16973 /* Adjust clock one minute up or down */
16975 AdjustClock (Boolean which, int dir)
16977 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16978 if(which) blackTimeRemaining += 60000*dir;
16979 else whiteTimeRemaining += 60000*dir;
16980 DisplayBothClocks();
16981 adjustedClock = TRUE;
16984 /* Stop clocks and reset to a fresh time control */
16988 (void) StopClockTimer();
16989 if (appData.icsActive) {
16990 whiteTimeRemaining = blackTimeRemaining = 0;
16991 } else if (searchTime) {
16992 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16993 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16994 } else { /* [HGM] correct new time quote for time odds */
16995 whiteTC = blackTC = fullTimeControlString;
16996 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16997 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16999 if (whiteFlag || blackFlag) {
17001 whiteFlag = blackFlag = FALSE;
17003 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17004 DisplayBothClocks();
17005 adjustedClock = FALSE;
17008 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17010 /* Decrement running clock by amount of time that has passed */
17014 long timeRemaining;
17015 long lastTickLength, fudge;
17018 if (!appData.clockMode) return;
17019 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17023 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17025 /* Fudge if we woke up a little too soon */
17026 fudge = intendedTickLength - lastTickLength;
17027 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17029 if (WhiteOnMove(forwardMostMove)) {
17030 if(whiteNPS >= 0) lastTickLength = 0;
17031 timeRemaining = whiteTimeRemaining -= lastTickLength;
17032 if(timeRemaining < 0 && !appData.icsActive) {
17033 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17034 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17035 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17036 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17039 DisplayWhiteClock(whiteTimeRemaining - fudge,
17040 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17042 if(blackNPS >= 0) lastTickLength = 0;
17043 timeRemaining = blackTimeRemaining -= lastTickLength;
17044 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17045 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17047 blackStartMove = forwardMostMove;
17048 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17051 DisplayBlackClock(blackTimeRemaining - fudge,
17052 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17054 if (CheckFlags()) return;
17056 if(twoBoards) { // count down secondary board's clocks as well
17057 activePartnerTime -= lastTickLength;
17059 if(activePartner == 'W')
17060 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17062 DisplayBlackClock(activePartnerTime, TRUE);
17067 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17068 StartClockTimer(intendedTickLength);
17070 /* if the time remaining has fallen below the alarm threshold, sound the
17071 * alarm. if the alarm has sounded and (due to a takeback or time control
17072 * with increment) the time remaining has increased to a level above the
17073 * threshold, reset the alarm so it can sound again.
17076 if (appData.icsActive && appData.icsAlarm) {
17078 /* make sure we are dealing with the user's clock */
17079 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17080 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17083 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17084 alarmSounded = FALSE;
17085 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17087 alarmSounded = TRUE;
17093 /* A player has just moved, so stop the previously running
17094 clock and (if in clock mode) start the other one.
17095 We redisplay both clocks in case we're in ICS mode, because
17096 ICS gives us an update to both clocks after every move.
17097 Note that this routine is called *after* forwardMostMove
17098 is updated, so the last fractional tick must be subtracted
17099 from the color that is *not* on move now.
17102 SwitchClocks (int newMoveNr)
17104 long lastTickLength;
17106 int flagged = FALSE;
17110 if (StopClockTimer() && appData.clockMode) {
17111 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17112 if (!WhiteOnMove(forwardMostMove)) {
17113 if(blackNPS >= 0) lastTickLength = 0;
17114 blackTimeRemaining -= lastTickLength;
17115 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17116 // if(pvInfoList[forwardMostMove].time == -1)
17117 pvInfoList[forwardMostMove].time = // use GUI time
17118 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17120 if(whiteNPS >= 0) lastTickLength = 0;
17121 whiteTimeRemaining -= lastTickLength;
17122 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17123 // if(pvInfoList[forwardMostMove].time == -1)
17124 pvInfoList[forwardMostMove].time =
17125 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17127 flagged = CheckFlags();
17129 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17130 CheckTimeControl();
17132 if (flagged || !appData.clockMode) return;
17134 switch (gameMode) {
17135 case MachinePlaysBlack:
17136 case MachinePlaysWhite:
17137 case BeginningOfGame:
17138 if (pausing) return;
17142 case PlayFromGameFile:
17150 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17151 if(WhiteOnMove(forwardMostMove))
17152 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17153 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17157 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17158 whiteTimeRemaining : blackTimeRemaining);
17159 StartClockTimer(intendedTickLength);
17163 /* Stop both clocks */
17167 long lastTickLength;
17170 if (!StopClockTimer()) return;
17171 if (!appData.clockMode) return;
17175 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17176 if (WhiteOnMove(forwardMostMove)) {
17177 if(whiteNPS >= 0) lastTickLength = 0;
17178 whiteTimeRemaining -= lastTickLength;
17179 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17181 if(blackNPS >= 0) lastTickLength = 0;
17182 blackTimeRemaining -= lastTickLength;
17183 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17188 /* Start clock of player on move. Time may have been reset, so
17189 if clock is already running, stop and restart it. */
17193 (void) StopClockTimer(); /* in case it was running already */
17194 DisplayBothClocks();
17195 if (CheckFlags()) return;
17197 if (!appData.clockMode) return;
17198 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17200 GetTimeMark(&tickStartTM);
17201 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17202 whiteTimeRemaining : blackTimeRemaining);
17204 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17205 whiteNPS = blackNPS = -1;
17206 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17207 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17208 whiteNPS = first.nps;
17209 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17210 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17211 blackNPS = first.nps;
17212 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17213 whiteNPS = second.nps;
17214 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17215 blackNPS = second.nps;
17216 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17218 StartClockTimer(intendedTickLength);
17222 TimeString (long ms)
17224 long second, minute, hour, day;
17226 static char buf[32];
17228 if (ms > 0 && ms <= 9900) {
17229 /* convert milliseconds to tenths, rounding up */
17230 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17232 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17236 /* convert milliseconds to seconds, rounding up */
17237 /* use floating point to avoid strangeness of integer division
17238 with negative dividends on many machines */
17239 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17246 day = second / (60 * 60 * 24);
17247 second = second % (60 * 60 * 24);
17248 hour = second / (60 * 60);
17249 second = second % (60 * 60);
17250 minute = second / 60;
17251 second = second % 60;
17254 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17255 sign, day, hour, minute, second);
17257 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17259 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17266 * This is necessary because some C libraries aren't ANSI C compliant yet.
17269 StrStr (char *string, char *match)
17273 length = strlen(match);
17275 for (i = strlen(string) - length; i >= 0; i--, string++)
17276 if (!strncmp(match, string, length))
17283 StrCaseStr (char *string, char *match)
17287 length = strlen(match);
17289 for (i = strlen(string) - length; i >= 0; i--, string++) {
17290 for (j = 0; j < length; j++) {
17291 if (ToLower(match[j]) != ToLower(string[j]))
17294 if (j == length) return string;
17302 StrCaseCmp (char *s1, char *s2)
17307 c1 = ToLower(*s1++);
17308 c2 = ToLower(*s2++);
17309 if (c1 > c2) return 1;
17310 if (c1 < c2) return -1;
17311 if (c1 == NULLCHAR) return 0;
17319 return isupper(c) ? tolower(c) : c;
17326 return islower(c) ? toupper(c) : c;
17328 #endif /* !_amigados */
17335 if ((ret = (char *) malloc(strlen(s) + 1)))
17337 safeStrCpy(ret, s, strlen(s)+1);
17343 StrSavePtr (char *s, char **savePtr)
17348 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17349 safeStrCpy(*savePtr, s, strlen(s)+1);
17361 clock = time((time_t *)NULL);
17362 tm = localtime(&clock);
17363 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17364 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17365 return StrSave(buf);
17370 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17372 int i, j, fromX, fromY, toX, toY;
17379 whiteToPlay = (gameMode == EditPosition) ?
17380 !blackPlaysFirst : (move % 2 == 0);
17383 /* Piece placement data */
17384 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17385 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17387 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17388 if (boards[move][i][j] == EmptySquare) {
17390 } else { ChessSquare piece = boards[move][i][j];
17391 if (emptycount > 0) {
17392 if(emptycount<10) /* [HGM] can be >= 10 */
17393 *p++ = '0' + emptycount;
17394 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17397 if(PieceToChar(piece) == '+') {
17398 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17400 piece = (ChessSquare)(DEMOTED piece);
17402 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17404 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17405 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17410 if (emptycount > 0) {
17411 if(emptycount<10) /* [HGM] can be >= 10 */
17412 *p++ = '0' + emptycount;
17413 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17420 /* [HGM] print Crazyhouse or Shogi holdings */
17421 if( gameInfo.holdingsWidth ) {
17422 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17424 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17425 piece = boards[move][i][BOARD_WIDTH-1];
17426 if( piece != EmptySquare )
17427 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17428 *p++ = PieceToChar(piece);
17430 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17431 piece = boards[move][BOARD_HEIGHT-i-1][0];
17432 if( piece != EmptySquare )
17433 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17434 *p++ = PieceToChar(piece);
17437 if( q == p ) *p++ = '-';
17443 *p++ = whiteToPlay ? 'w' : 'b';
17446 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17447 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17449 if(nrCastlingRights) {
17451 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17452 /* [HGM] write directly from rights */
17453 if(boards[move][CASTLING][2] != NoRights &&
17454 boards[move][CASTLING][0] != NoRights )
17455 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17456 if(boards[move][CASTLING][2] != NoRights &&
17457 boards[move][CASTLING][1] != NoRights )
17458 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17459 if(boards[move][CASTLING][5] != NoRights &&
17460 boards[move][CASTLING][3] != NoRights )
17461 *p++ = boards[move][CASTLING][3] + AAA;
17462 if(boards[move][CASTLING][5] != NoRights &&
17463 boards[move][CASTLING][4] != NoRights )
17464 *p++ = boards[move][CASTLING][4] + AAA;
17467 /* [HGM] write true castling rights */
17468 if( nrCastlingRights == 6 ) {
17470 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17471 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17472 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17473 boards[move][CASTLING][2] != NoRights );
17474 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17475 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17476 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17477 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17478 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17482 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17483 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17484 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17485 boards[move][CASTLING][5] != NoRights );
17486 if(gameInfo.variant == VariantSChess) {
17487 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17488 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17489 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17490 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17495 if (q == p) *p++ = '-'; /* No castling rights */
17499 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17500 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17501 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17502 /* En passant target square */
17503 if (move > backwardMostMove) {
17504 fromX = moveList[move - 1][0] - AAA;
17505 fromY = moveList[move - 1][1] - ONE;
17506 toX = moveList[move - 1][2] - AAA;
17507 toY = moveList[move - 1][3] - ONE;
17508 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17509 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17510 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17512 /* 2-square pawn move just happened */
17514 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17518 } else if(move == backwardMostMove) {
17519 // [HGM] perhaps we should always do it like this, and forget the above?
17520 if((signed char)boards[move][EP_STATUS] >= 0) {
17521 *p++ = boards[move][EP_STATUS] + AAA;
17522 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17534 { int i = 0, j=move;
17536 /* [HGM] find reversible plies */
17537 if (appData.debugMode) { int k;
17538 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17539 for(k=backwardMostMove; k<=forwardMostMove; k++)
17540 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17544 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17545 if( j == backwardMostMove ) i += initialRulePlies;
17546 sprintf(p, "%d ", i);
17547 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17549 /* Fullmove number */
17550 sprintf(p, "%d", (move / 2) + 1);
17551 } else *--p = NULLCHAR;
17553 return StrSave(buf);
17557 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17561 int emptycount, virgin[BOARD_FILES];
17566 /* Piece placement data */
17567 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17570 if (*p == '/' || *p == ' ' || *p == '[' ) {
17572 emptycount = gameInfo.boardWidth - j;
17573 while (emptycount--)
17574 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17575 if (*p == '/') p++;
17576 else if(autoSize) { // we stumbled unexpectedly into end of board
17577 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17578 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17580 appData.NrRanks = gameInfo.boardHeight - i; i=0;
17583 #if(BOARD_FILES >= 10)
17584 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17585 p++; emptycount=10;
17586 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17587 while (emptycount--)
17588 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17590 } else if (*p == '*') {
17591 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17592 } else if (isdigit(*p)) {
17593 emptycount = *p++ - '0';
17594 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17595 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17596 while (emptycount--)
17597 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17598 } else if (*p == '+' || isalpha(*p)) {
17599 if (j >= gameInfo.boardWidth) return FALSE;
17601 piece = CharToPiece(*++p);
17602 if(piece == EmptySquare) return FALSE; /* unknown piece */
17603 piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17604 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17605 } else piece = CharToPiece(*p++);
17607 if(piece==EmptySquare) return FALSE; /* unknown piece */
17608 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17609 piece = (ChessSquare) (PROMOTED piece);
17610 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17613 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17619 while (*p == '/' || *p == ' ') p++;
17621 if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17623 /* [HGM] by default clear Crazyhouse holdings, if present */
17624 if(gameInfo.holdingsWidth) {
17625 for(i=0; i<BOARD_HEIGHT; i++) {
17626 board[i][0] = EmptySquare; /* black holdings */
17627 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17628 board[i][1] = (ChessSquare) 0; /* black counts */
17629 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17633 /* [HGM] look for Crazyhouse holdings here */
17634 while(*p==' ') p++;
17635 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17637 if(*p == '-' ) p++; /* empty holdings */ else {
17638 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17639 /* if we would allow FEN reading to set board size, we would */
17640 /* have to add holdings and shift the board read so far here */
17641 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17643 if((int) piece >= (int) BlackPawn ) {
17644 i = (int)piece - (int)BlackPawn;
17645 i = PieceToNumber((ChessSquare)i);
17646 if( i >= gameInfo.holdingsSize ) return FALSE;
17647 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17648 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17650 i = (int)piece - (int)WhitePawn;
17651 i = PieceToNumber((ChessSquare)i);
17652 if( i >= gameInfo.holdingsSize ) return FALSE;
17653 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17654 board[i][BOARD_WIDTH-2]++; /* black holdings */
17661 while(*p == ' ') p++;
17665 if(appData.colorNickNames) {
17666 if( c == appData.colorNickNames[0] ) c = 'w'; else
17667 if( c == appData.colorNickNames[1] ) c = 'b';
17671 *blackPlaysFirst = FALSE;
17674 *blackPlaysFirst = TRUE;
17680 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17681 /* return the extra info in global variiables */
17683 /* set defaults in case FEN is incomplete */
17684 board[EP_STATUS] = EP_UNKNOWN;
17685 for(i=0; i<nrCastlingRights; i++ ) {
17686 board[CASTLING][i] =
17687 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17688 } /* assume possible unless obviously impossible */
17689 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17690 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17691 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17692 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17693 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17694 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17695 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17696 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17699 while(*p==' ') p++;
17700 if(nrCastlingRights) {
17701 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17702 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17703 /* castling indicator present, so default becomes no castlings */
17704 for(i=0; i<nrCastlingRights; i++ ) {
17705 board[CASTLING][i] = NoRights;
17708 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17709 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17710 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17711 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17712 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17714 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17715 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17716 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17718 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17719 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17720 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17721 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17722 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17723 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17726 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17727 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17728 board[CASTLING][2] = whiteKingFile;
17729 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17730 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17733 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17734 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17735 board[CASTLING][2] = whiteKingFile;
17736 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17737 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17740 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17741 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17742 board[CASTLING][5] = blackKingFile;
17743 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17744 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17747 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17748 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17749 board[CASTLING][5] = blackKingFile;
17750 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17751 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17754 default: /* FRC castlings */
17755 if(c >= 'a') { /* black rights */
17756 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17757 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17758 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17759 if(i == BOARD_RGHT) break;
17760 board[CASTLING][5] = i;
17762 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17763 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17765 board[CASTLING][3] = c;
17767 board[CASTLING][4] = c;
17768 } else { /* white rights */
17769 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17770 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17771 if(board[0][i] == WhiteKing) break;
17772 if(i == BOARD_RGHT) break;
17773 board[CASTLING][2] = i;
17774 c -= AAA - 'a' + 'A';
17775 if(board[0][c] >= WhiteKing) break;
17777 board[CASTLING][0] = c;
17779 board[CASTLING][1] = c;
17783 for(i=0; i<nrCastlingRights; i++)
17784 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17785 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17786 if (appData.debugMode) {
17787 fprintf(debugFP, "FEN castling rights:");
17788 for(i=0; i<nrCastlingRights; i++)
17789 fprintf(debugFP, " %d", board[CASTLING][i]);
17790 fprintf(debugFP, "\n");
17793 while(*p==' ') p++;
17796 /* read e.p. field in games that know e.p. capture */
17797 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17798 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17799 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17801 p++; board[EP_STATUS] = EP_NONE;
17803 char c = *p++ - AAA;
17805 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17806 if(*p >= '0' && *p <='9') p++;
17807 board[EP_STATUS] = c;
17812 if(sscanf(p, "%d", &i) == 1) {
17813 FENrulePlies = i; /* 50-move ply counter */
17814 /* (The move number is still ignored) */
17821 EditPositionPasteFEN (char *fen)
17824 Board initial_position;
17826 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17827 DisplayError(_("Bad FEN position in clipboard"), 0);
17830 int savedBlackPlaysFirst = blackPlaysFirst;
17831 EditPositionEvent();
17832 blackPlaysFirst = savedBlackPlaysFirst;
17833 CopyBoard(boards[0], initial_position);
17834 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17835 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17836 DisplayBothClocks();
17837 DrawPosition(FALSE, boards[currentMove]);
17842 static char cseq[12] = "\\ ";
17845 set_cont_sequence (char *new_seq)
17850 // handle bad attempts to set the sequence
17852 return 0; // acceptable error - no debug
17854 len = strlen(new_seq);
17855 ret = (len > 0) && (len < sizeof(cseq));
17857 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17858 else if (appData.debugMode)
17859 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17864 reformat a source message so words don't cross the width boundary. internal
17865 newlines are not removed. returns the wrapped size (no null character unless
17866 included in source message). If dest is NULL, only calculate the size required
17867 for the dest buffer. lp argument indicats line position upon entry, and it's
17868 passed back upon exit.
17871 wrap (char *dest, char *src, int count, int width, int *lp)
17873 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17875 cseq_len = strlen(cseq);
17876 old_line = line = *lp;
17877 ansi = len = clen = 0;
17879 for (i=0; i < count; i++)
17881 if (src[i] == '\033')
17884 // if we hit the width, back up
17885 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17887 // store i & len in case the word is too long
17888 old_i = i, old_len = len;
17890 // find the end of the last word
17891 while (i && src[i] != ' ' && src[i] != '\n')
17897 // word too long? restore i & len before splitting it
17898 if ((old_i-i+clen) >= width)
17905 if (i && src[i-1] == ' ')
17908 if (src[i] != ' ' && src[i] != '\n')
17915 // now append the newline and continuation sequence
17920 strncpy(dest+len, cseq, cseq_len);
17928 dest[len] = src[i];
17932 if (src[i] == '\n')
17937 if (dest && appData.debugMode)
17939 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17940 count, width, line, len, *lp);
17941 show_bytes(debugFP, src, count);
17942 fprintf(debugFP, "\ndest: ");
17943 show_bytes(debugFP, dest, len);
17944 fprintf(debugFP, "\n");
17946 *lp = dest ? line : old_line;
17951 // [HGM] vari: routines for shelving variations
17952 Boolean modeRestore = FALSE;
17955 PushInner (int firstMove, int lastMove)
17957 int i, j, nrMoves = lastMove - firstMove;
17959 // push current tail of game on stack
17960 savedResult[storedGames] = gameInfo.result;
17961 savedDetails[storedGames] = gameInfo.resultDetails;
17962 gameInfo.resultDetails = NULL;
17963 savedFirst[storedGames] = firstMove;
17964 savedLast [storedGames] = lastMove;
17965 savedFramePtr[storedGames] = framePtr;
17966 framePtr -= nrMoves; // reserve space for the boards
17967 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17968 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17969 for(j=0; j<MOVE_LEN; j++)
17970 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17971 for(j=0; j<2*MOVE_LEN; j++)
17972 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17973 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17974 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17975 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17976 pvInfoList[firstMove+i-1].depth = 0;
17977 commentList[framePtr+i] = commentList[firstMove+i];
17978 commentList[firstMove+i] = NULL;
17982 forwardMostMove = firstMove; // truncate game so we can start variation
17986 PushTail (int firstMove, int lastMove)
17988 if(appData.icsActive) { // only in local mode
17989 forwardMostMove = currentMove; // mimic old ICS behavior
17992 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17994 PushInner(firstMove, lastMove);
17995 if(storedGames == 1) GreyRevert(FALSE);
17996 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18000 PopInner (Boolean annotate)
18003 char buf[8000], moveBuf[20];
18005 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18006 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18007 nrMoves = savedLast[storedGames] - currentMove;
18010 if(!WhiteOnMove(currentMove))
18011 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18012 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18013 for(i=currentMove; i<forwardMostMove; i++) {
18015 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18016 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18017 strcat(buf, moveBuf);
18018 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18019 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18023 for(i=1; i<=nrMoves; i++) { // copy last variation back
18024 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18025 for(j=0; j<MOVE_LEN; j++)
18026 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18027 for(j=0; j<2*MOVE_LEN; j++)
18028 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18029 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18030 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18031 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18032 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18033 commentList[currentMove+i] = commentList[framePtr+i];
18034 commentList[framePtr+i] = NULL;
18036 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18037 framePtr = savedFramePtr[storedGames];
18038 gameInfo.result = savedResult[storedGames];
18039 if(gameInfo.resultDetails != NULL) {
18040 free(gameInfo.resultDetails);
18042 gameInfo.resultDetails = savedDetails[storedGames];
18043 forwardMostMove = currentMove + nrMoves;
18047 PopTail (Boolean annotate)
18049 if(appData.icsActive) return FALSE; // only in local mode
18050 if(!storedGames) return FALSE; // sanity
18051 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18053 PopInner(annotate);
18054 if(currentMove < forwardMostMove) ForwardEvent(); else
18055 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18057 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18063 { // remove all shelved variations
18065 for(i=0; i<storedGames; i++) {
18066 if(savedDetails[i])
18067 free(savedDetails[i]);
18068 savedDetails[i] = NULL;
18070 for(i=framePtr; i<MAX_MOVES; i++) {
18071 if(commentList[i]) free(commentList[i]);
18072 commentList[i] = NULL;
18074 framePtr = MAX_MOVES-1;
18079 LoadVariation (int index, char *text)
18080 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18081 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18082 int level = 0, move;
18084 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18085 // first find outermost bracketing variation
18086 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18087 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18088 if(*p == '{') wait = '}'; else
18089 if(*p == '[') wait = ']'; else
18090 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18091 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18093 if(*p == wait) wait = NULLCHAR; // closing ]} found
18096 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18097 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18098 end[1] = NULLCHAR; // clip off comment beyond variation
18099 ToNrEvent(currentMove-1);
18100 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18101 // kludge: use ParsePV() to append variation to game
18102 move = currentMove;
18103 ParsePV(start, TRUE, TRUE);
18104 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18105 ClearPremoveHighlights();
18107 ToNrEvent(currentMove+1);
18113 char *p, *q, buf[MSG_SIZ];
18114 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18115 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18116 ParseArgsFromString(buf);
18117 ActivateTheme(TRUE); // also redo colors
18121 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18124 q = appData.themeNames;
18125 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18126 if(appData.useBitmaps) {
18127 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18128 appData.liteBackTextureFile, appData.darkBackTextureFile,
18129 appData.liteBackTextureMode,
18130 appData.darkBackTextureMode );
18132 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18133 Col2Text(2), // lightSquareColor
18134 Col2Text(3) ); // darkSquareColor
18136 if(appData.useBorder) {
18137 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18140 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18142 if(appData.useFont) {
18143 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18144 appData.renderPiecesWithFont,
18145 appData.fontToPieceTable,
18146 Col2Text(9), // appData.fontBackColorWhite
18147 Col2Text(10) ); // appData.fontForeColorBlack
18149 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18150 appData.pieceDirectory);
18151 if(!appData.pieceDirectory[0])
18152 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18153 Col2Text(0), // whitePieceColor
18154 Col2Text(1) ); // blackPieceColor
18156 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18157 Col2Text(4), // highlightSquareColor
18158 Col2Text(5) ); // premoveHighlightColor
18159 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18160 if(insert != q) insert[-1] = NULLCHAR;
18161 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18164 ActivateTheme(FALSE);