2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 int flock(int f, int code);
75 #include <sys/types.h>
84 #else /* not STDC_HEADERS */
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
105 # include <sys/time.h>
111 #if defined(_amigados) && !defined(__GNUC__)
116 extern int gettimeofday(struct timeval *, struct timezone *);
124 #include "frontend.h"
131 #include "backendz.h"
132 #include "evalgraph.h"
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153 char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180 char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182 int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((void));
231 extern void ConsoleCreate();
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
254 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
279 /* States for ics_getting_history */
281 #define H_REQUESTED 1
282 #define H_GOT_REQ_HEADER 2
283 #define H_GOT_UNREQ_HEADER 3
284 #define H_GETTING_MOVES 4
285 #define H_GOT_UNWANTED_HEADER 5
287 /* whosays values for GameEnds */
296 /* Maximum number of games in a cmail message */
297 #define CMAIL_MAX_GAMES 20
299 /* Different types of move when calling RegisterMove */
301 #define CMAIL_RESIGN 1
303 #define CMAIL_ACCEPT 3
305 /* Different types of result to remember for each game */
306 #define CMAIL_NOT_RESULT 0
307 #define CMAIL_OLD_RESULT 1
308 #define CMAIL_NEW_RESULT 2
310 /* Telnet protocol constants */
321 safeStrCpy (char *dst, const char *src, size_t count)
324 assert( dst != NULL );
325 assert( src != NULL );
328 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
329 if( i == count && dst[count-1] != NULLCHAR)
331 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
332 if(appData.debugMode)
333 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
339 /* Some compiler can't cast u64 to double
340 * This function do the job for us:
342 * We use the highest bit for cast, this only
343 * works if the highest bit is not
344 * in use (This should not happen)
346 * We used this for all compiler
349 u64ToDouble (u64 value)
352 u64 tmp = value & u64Const(0x7fffffffffffffff);
353 r = (double)(s64)tmp;
354 if (value & u64Const(0x8000000000000000))
355 r += 9.2233720368547758080e18; /* 2^63 */
359 /* Fake up flags for now, as we aren't keeping track of castling
360 availability yet. [HGM] Change of logic: the flag now only
361 indicates the type of castlings allowed by the rule of the game.
362 The actual rights themselves are maintained in the array
363 castlingRights, as part of the game history, and are not probed
369 int flags = F_ALL_CASTLE_OK;
370 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
371 switch (gameInfo.variant) {
373 flags &= ~F_ALL_CASTLE_OK;
374 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
375 flags |= F_IGNORE_CHECK;
377 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
380 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
382 case VariantKriegspiel:
383 flags |= F_KRIEGSPIEL_CAPTURE;
385 case VariantCapaRandom:
386 case VariantFischeRandom:
387 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
388 case VariantNoCastle:
389 case VariantShatranj:
394 flags &= ~F_ALL_CASTLE_OK;
402 FILE *gameFileFP, *debugFP, *serverFP;
403 char *currentDebugFile; // [HGM] debug split: to remember name
406 [AS] Note: sometimes, the sscanf() function is used to parse the input
407 into a fixed-size buffer. Because of this, we must be prepared to
408 receive strings as long as the size of the input buffer, which is currently
409 set to 4K for Windows and 8K for the rest.
410 So, we must either allocate sufficiently large buffers here, or
411 reduce the size of the input buffer in the input reading part.
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
418 ChessProgramState first, second, pairing;
420 /* premove variables */
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
430 char *ics_prefix = "$";
431 enum ICS_TYPE ics_type = ICS_GENERIC;
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey, controlKey; // [HGM] set by mouse handler
461 int have_sent_ICS_logon = 0;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE, startingEngine = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
475 /* animateTraining preserves the state of appData.animate
476 * when Training mode is activated. This allows the
477 * response to be animated when appData.animate == TRUE and
478 * appData.animateDragging == TRUE.
480 Boolean animateTraining;
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char initialRights[BOARD_FILES];
490 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int initialRulePlies, FENrulePlies;
492 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
513 ChessSquare FIDEArray[2][BOARD_FILES] = {
514 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517 BlackKing, BlackBishop, BlackKnight, BlackRook }
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524 BlackKing, BlackKing, BlackKnight, BlackRook }
527 ChessSquare KnightmateArray[2][BOARD_FILES] = {
528 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530 { BlackRook, BlackMan, BlackBishop, BlackQueen,
531 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558 { BlackRook, BlackKnight, BlackMan, BlackFerz,
559 BlackKing, BlackMan, BlackKnight, BlackRook }
562 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
563 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
564 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
565 { BlackRook, BlackKnight, BlackMan, BlackFerz,
566 BlackKing, BlackMan, BlackKnight, BlackRook }
569 ChessSquare lionArray[2][BOARD_FILES] = {
570 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
571 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
572 { BlackRook, BlackLion, BlackBishop, BlackQueen,
573 BlackKing, BlackBishop, BlackKnight, BlackRook }
577 #if (BOARD_FILES>=10)
578 ChessSquare ShogiArray[2][BOARD_FILES] = {
579 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
580 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
581 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
582 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
585 ChessSquare XiangqiArray[2][BOARD_FILES] = {
586 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
587 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
588 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
589 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
592 ChessSquare CapablancaArray[2][BOARD_FILES] = {
593 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
594 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
595 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
596 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
599 ChessSquare GreatArray[2][BOARD_FILES] = {
600 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
601 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
602 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
603 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
606 ChessSquare JanusArray[2][BOARD_FILES] = {
607 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
608 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
609 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
610 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
613 ChessSquare GrandArray[2][BOARD_FILES] = {
614 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
615 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
616 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
617 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
621 ChessSquare GothicArray[2][BOARD_FILES] = {
622 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
623 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
624 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
625 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
628 #define GothicArray CapablancaArray
632 ChessSquare FalconArray[2][BOARD_FILES] = {
633 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
634 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
635 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
636 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
639 #define FalconArray CapablancaArray
642 #else // !(BOARD_FILES>=10)
643 #define XiangqiPosition FIDEArray
644 #define CapablancaArray FIDEArray
645 #define GothicArray FIDEArray
646 #define GreatArray FIDEArray
647 #endif // !(BOARD_FILES>=10)
649 #if (BOARD_FILES>=12)
650 ChessSquare CourierArray[2][BOARD_FILES] = {
651 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
652 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
653 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
654 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
656 ChessSquare ChuArray[6][BOARD_FILES] = {
657 { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
658 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
659 { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
660 BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
661 { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
662 WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
663 { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
664 BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
665 { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
666 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
667 { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
668 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
670 #else // !(BOARD_FILES>=12)
671 #define CourierArray CapablancaArray
672 #define ChuArray CapablancaArray
673 #endif // !(BOARD_FILES>=12)
676 Board initialPosition;
679 /* Convert str to a rating. Checks for special cases of "----",
681 "++++", etc. Also strips ()'s */
683 string_to_rating (char *str)
685 while(*str && !isdigit(*str)) ++str;
687 return 0; /* One of the special "no rating" cases */
695 /* Init programStats */
696 programStats.movelist[0] = 0;
697 programStats.depth = 0;
698 programStats.nr_moves = 0;
699 programStats.moves_left = 0;
700 programStats.nodes = 0;
701 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
702 programStats.score = 0;
703 programStats.got_only_move = 0;
704 programStats.got_fail = 0;
705 programStats.line_is_book = 0;
710 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
711 if (appData.firstPlaysBlack) {
712 first.twoMachinesColor = "black\n";
713 second.twoMachinesColor = "white\n";
715 first.twoMachinesColor = "white\n";
716 second.twoMachinesColor = "black\n";
719 first.other = &second;
720 second.other = &first;
723 if(appData.timeOddsMode) {
724 norm = appData.timeOdds[0];
725 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
727 first.timeOdds = appData.timeOdds[0]/norm;
728 second.timeOdds = appData.timeOdds[1]/norm;
731 if(programVersion) free(programVersion);
732 if (appData.noChessProgram) {
733 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
734 sprintf(programVersion, "%s", PACKAGE_STRING);
736 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
737 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
738 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
743 UnloadEngine (ChessProgramState *cps)
745 /* Kill off first chess program */
746 if (cps->isr != NULL)
747 RemoveInputSource(cps->isr);
750 if (cps->pr != NoProc) {
752 DoSleep( appData.delayBeforeQuit );
753 SendToProgram("quit\n", cps);
754 DoSleep( appData.delayAfterQuit );
755 DestroyChildProcess(cps->pr, cps->useSigterm);
758 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
762 ClearOptions (ChessProgramState *cps)
765 cps->nrOptions = cps->comboCnt = 0;
766 for(i=0; i<MAX_OPTIONS; i++) {
767 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
768 cps->option[i].textValue = 0;
772 char *engineNames[] = {
773 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
774 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
776 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
777 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
782 InitEngine (ChessProgramState *cps, int n)
783 { // [HGM] all engine initialiation put in a function that does one engine
787 cps->which = engineNames[n];
788 cps->maybeThinking = FALSE;
792 cps->sendDrawOffers = 1;
794 cps->program = appData.chessProgram[n];
795 cps->host = appData.host[n];
796 cps->dir = appData.directory[n];
797 cps->initString = appData.engInitString[n];
798 cps->computerString = appData.computerString[n];
799 cps->useSigint = TRUE;
800 cps->useSigterm = TRUE;
801 cps->reuse = appData.reuse[n];
802 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
803 cps->useSetboard = FALSE;
805 cps->usePing = FALSE;
808 cps->usePlayother = FALSE;
809 cps->useColors = TRUE;
810 cps->useUsermove = FALSE;
811 cps->sendICS = FALSE;
812 cps->sendName = appData.icsActive;
813 cps->sdKludge = FALSE;
814 cps->stKludge = FALSE;
815 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
816 TidyProgramName(cps->program, cps->host, cps->tidy);
818 ASSIGN(cps->variants, appData.variant);
819 cps->analysisSupport = 2; /* detect */
820 cps->analyzing = FALSE;
821 cps->initDone = FALSE;
824 /* New features added by Tord: */
825 cps->useFEN960 = FALSE;
826 cps->useOOCastle = TRUE;
827 /* End of new features added by Tord. */
828 cps->fenOverride = appData.fenOverride[n];
830 /* [HGM] time odds: set factor for each machine */
831 cps->timeOdds = appData.timeOdds[n];
833 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
834 cps->accumulateTC = appData.accumulateTC[n];
835 cps->maxNrOfSessions = 1;
840 cps->supportsNPS = UNKNOWN;
841 cps->memSize = FALSE;
842 cps->maxCores = FALSE;
843 ASSIGN(cps->egtFormats, "");
846 cps->optionSettings = appData.engOptions[n];
848 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
849 cps->isUCI = appData.isUCI[n]; /* [AS] */
850 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
853 if (appData.protocolVersion[n] > PROTOVER
854 || appData.protocolVersion[n] < 1)
859 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
860 appData.protocolVersion[n]);
861 if( (len >= MSG_SIZ) && appData.debugMode )
862 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
864 DisplayFatalError(buf, 0, 2);
868 cps->protocolVersion = appData.protocolVersion[n];
871 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
872 ParseFeatures(appData.featureDefaults, cps);
875 ChessProgramState *savCps;
883 if(WaitForEngine(savCps, LoadEngine)) return;
884 CommonEngineInit(); // recalculate time odds
885 if(gameInfo.variant != StringToVariant(appData.variant)) {
886 // we changed variant when loading the engine; this forces us to reset
887 Reset(TRUE, savCps != &first);
888 oldMode = BeginningOfGame; // to prevent restoring old mode
890 InitChessProgram(savCps, FALSE);
891 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
892 DisplayMessage("", "");
893 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
894 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
897 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
901 ReplaceEngine (ChessProgramState *cps, int n)
903 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
905 if(oldMode != BeginningOfGame) EditGameEvent();
908 appData.noChessProgram = FALSE;
909 appData.clockMode = TRUE;
912 if(n) return; // only startup first engine immediately; second can wait
913 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
917 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
918 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
920 static char resetOptions[] =
921 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
922 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
923 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
924 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
927 FloatToFront(char **list, char *engineLine)
929 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
931 if(appData.recentEngines <= 0) return;
932 TidyProgramName(engineLine, "localhost", tidy+1);
933 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
934 strncpy(buf+1, *list, MSG_SIZ-50);
935 if(p = strstr(buf, tidy)) { // tidy name appears in list
936 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
937 while(*p++ = *++q); // squeeze out
939 strcat(tidy, buf+1); // put list behind tidy name
940 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
941 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
942 ASSIGN(*list, tidy+1);
945 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
948 Load (ChessProgramState *cps, int i)
950 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
951 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
952 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
953 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
954 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
955 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
956 appData.firstProtocolVersion = PROTOVER;
957 ParseArgsFromString(buf);
959 ReplaceEngine(cps, i);
960 FloatToFront(&appData.recentEngineList, engineLine);
964 while(q = strchr(p, SLASH)) p = q+1;
965 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
966 if(engineDir[0] != NULLCHAR) {
967 ASSIGN(appData.directory[i], engineDir); p = engineName;
968 } else if(p != engineName) { // derive directory from engine path, when not given
970 ASSIGN(appData.directory[i], engineName);
972 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
973 } else { ASSIGN(appData.directory[i], "."); }
974 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
976 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
977 snprintf(command, MSG_SIZ, "%s %s", p, params);
980 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
981 ASSIGN(appData.chessProgram[i], p);
982 appData.isUCI[i] = isUCI;
983 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
984 appData.hasOwnBookUCI[i] = hasBook;
985 if(!nickName[0]) useNick = FALSE;
986 if(useNick) ASSIGN(appData.pgnName[i], nickName);
990 q = firstChessProgramNames;
991 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
992 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
993 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
994 quote, p, quote, appData.directory[i],
995 useNick ? " -fn \"" : "",
996 useNick ? nickName : "",
998 v1 ? " -firstProtocolVersion 1" : "",
999 hasBook ? "" : " -fNoOwnBookUCI",
1000 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1001 storeVariant ? " -variant " : "",
1002 storeVariant ? VariantName(gameInfo.variant) : "");
1003 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1004 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1005 if(insert != q) insert[-1] = NULLCHAR;
1006 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1008 FloatToFront(&appData.recentEngineList, buf);
1010 ReplaceEngine(cps, i);
1016 int matched, min, sec;
1018 * Parse timeControl resource
1020 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1021 appData.movesPerSession)) {
1023 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1024 DisplayFatalError(buf, 0, 2);
1028 * Parse searchTime resource
1030 if (*appData.searchTime != NULLCHAR) {
1031 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1033 searchTime = min * 60;
1034 } else if (matched == 2) {
1035 searchTime = min * 60 + sec;
1038 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1039 DisplayFatalError(buf, 0, 2);
1048 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1049 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1051 GetTimeMark(&programStartTime);
1052 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1053 appData.seedBase = random() + (random()<<15);
1054 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1056 ClearProgramStats();
1057 programStats.ok_to_send = 1;
1058 programStats.seen_stat = 0;
1061 * Initialize game list
1067 * Internet chess server status
1069 if (appData.icsActive) {
1070 appData.matchMode = FALSE;
1071 appData.matchGames = 0;
1073 appData.noChessProgram = !appData.zippyPlay;
1075 appData.zippyPlay = FALSE;
1076 appData.zippyTalk = FALSE;
1077 appData.noChessProgram = TRUE;
1079 if (*appData.icsHelper != NULLCHAR) {
1080 appData.useTelnet = TRUE;
1081 appData.telnetProgram = appData.icsHelper;
1084 appData.zippyTalk = appData.zippyPlay = FALSE;
1087 /* [AS] Initialize pv info list [HGM] and game state */
1091 for( i=0; i<=framePtr; i++ ) {
1092 pvInfoList[i].depth = -1;
1093 boards[i][EP_STATUS] = EP_NONE;
1094 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1100 /* [AS] Adjudication threshold */
1101 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1103 InitEngine(&first, 0);
1104 InitEngine(&second, 1);
1107 pairing.which = "pairing"; // pairing engine
1108 pairing.pr = NoProc;
1110 pairing.program = appData.pairingEngine;
1111 pairing.host = "localhost";
1114 if (appData.icsActive) {
1115 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1116 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1117 appData.clockMode = FALSE;
1118 first.sendTime = second.sendTime = 0;
1122 /* Override some settings from environment variables, for backward
1123 compatibility. Unfortunately it's not feasible to have the env
1124 vars just set defaults, at least in xboard. Ugh.
1126 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1131 if (!appData.icsActive) {
1135 /* Check for variants that are supported only in ICS mode,
1136 or not at all. Some that are accepted here nevertheless
1137 have bugs; see comments below.
1139 VariantClass variant = StringToVariant(appData.variant);
1141 case VariantBughouse: /* need four players and two boards */
1142 case VariantKriegspiel: /* need to hide pieces and move details */
1143 /* case VariantFischeRandom: (Fabien: moved below) */
1144 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1145 if( (len >= MSG_SIZ) && appData.debugMode )
1146 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1148 DisplayFatalError(buf, 0, 2);
1151 case VariantUnknown:
1152 case VariantLoadable:
1162 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1163 if( (len >= MSG_SIZ) && appData.debugMode )
1164 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1166 DisplayFatalError(buf, 0, 2);
1169 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1170 case VariantFairy: /* [HGM] TestLegality definitely off! */
1171 case VariantGothic: /* [HGM] should work */
1172 case VariantCapablanca: /* [HGM] should work */
1173 case VariantCourier: /* [HGM] initial forced moves not implemented */
1174 case VariantShogi: /* [HGM] could still mate with pawn drop */
1175 case VariantChu: /* [HGM] experimental */
1176 case VariantKnightmate: /* [HGM] should work */
1177 case VariantCylinder: /* [HGM] untested */
1178 case VariantFalcon: /* [HGM] untested */
1179 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1180 offboard interposition not understood */
1181 case VariantNormal: /* definitely works! */
1182 case VariantWildCastle: /* pieces not automatically shuffled */
1183 case VariantNoCastle: /* pieces not automatically shuffled */
1184 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1185 case VariantLosers: /* should work except for win condition,
1186 and doesn't know captures are mandatory */
1187 case VariantSuicide: /* should work except for win condition,
1188 and doesn't know captures are mandatory */
1189 case VariantGiveaway: /* should work except for win condition,
1190 and doesn't know captures are mandatory */
1191 case VariantTwoKings: /* should work */
1192 case VariantAtomic: /* should work except for win condition */
1193 case Variant3Check: /* should work except for win condition */
1194 case VariantShatranj: /* should work except for all win conditions */
1195 case VariantMakruk: /* should work except for draw countdown */
1196 case VariantASEAN : /* should work except for draw countdown */
1197 case VariantBerolina: /* might work if TestLegality is off */
1198 case VariantCapaRandom: /* should work */
1199 case VariantJanus: /* should work */
1200 case VariantSuper: /* experimental */
1201 case VariantGreat: /* experimental, requires legality testing to be off */
1202 case VariantSChess: /* S-Chess, should work */
1203 case VariantGrand: /* should work */
1204 case VariantSpartan: /* should work */
1205 case VariantLion: /* should work */
1213 NextIntegerFromString (char ** str, long * value)
1218 while( *s == ' ' || *s == '\t' ) {
1224 if( *s >= '0' && *s <= '9' ) {
1225 while( *s >= '0' && *s <= '9' ) {
1226 *value = *value * 10 + (*s - '0');
1239 NextTimeControlFromString (char ** str, long * value)
1242 int result = NextIntegerFromString( str, &temp );
1245 *value = temp * 60; /* Minutes */
1246 if( **str == ':' ) {
1248 result = NextIntegerFromString( str, &temp );
1249 *value += temp; /* Seconds */
1257 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1258 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1259 int result = -1, type = 0; long temp, temp2;
1261 if(**str != ':') return -1; // old params remain in force!
1263 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1264 if( NextIntegerFromString( str, &temp ) ) return -1;
1265 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1268 /* time only: incremental or sudden-death time control */
1269 if(**str == '+') { /* increment follows; read it */
1271 if(**str == '!') type = *(*str)++; // Bronstein TC
1272 if(result = NextIntegerFromString( str, &temp2)) return -1;
1273 *inc = temp2 * 1000;
1274 if(**str == '.') { // read fraction of increment
1275 char *start = ++(*str);
1276 if(result = NextIntegerFromString( str, &temp2)) return -1;
1278 while(start++ < *str) temp2 /= 10;
1282 *moves = 0; *tc = temp * 1000; *incType = type;
1286 (*str)++; /* classical time control */
1287 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1299 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1300 { /* [HGM] get time to add from the multi-session time-control string */
1301 int incType, moves=1; /* kludge to force reading of first session */
1302 long time, increment;
1305 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1307 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1308 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1309 if(movenr == -1) return time; /* last move before new session */
1310 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1311 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1312 if(!moves) return increment; /* current session is incremental */
1313 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1314 } while(movenr >= -1); /* try again for next session */
1316 return 0; // no new time quota on this move
1320 ParseTimeControl (char *tc, float ti, int mps)
1324 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1327 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1328 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1329 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1333 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1335 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1338 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1340 snprintf(buf, MSG_SIZ, ":%s", mytc);
1342 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1344 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1349 /* Parse second time control */
1352 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1360 timeControl_2 = tc2 * 1000;
1370 timeControl = tc1 * 1000;
1373 timeIncrement = ti * 1000; /* convert to ms */
1374 movesPerSession = 0;
1377 movesPerSession = mps;
1385 if (appData.debugMode) {
1386 # ifdef __GIT_VERSION
1387 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1389 fprintf(debugFP, "Version: %s\n", programVersion);
1392 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1394 set_cont_sequence(appData.wrapContSeq);
1395 if (appData.matchGames > 0) {
1396 appData.matchMode = TRUE;
1397 } else if (appData.matchMode) {
1398 appData.matchGames = 1;
1400 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1401 appData.matchGames = appData.sameColorGames;
1402 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1403 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1404 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1407 if (appData.noChessProgram || first.protocolVersion == 1) {
1410 /* kludge: allow timeout for initial "feature" commands */
1412 DisplayMessage("", _("Starting chess program"));
1413 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1418 CalculateIndex (int index, int gameNr)
1419 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1421 if(index > 0) return index; // fixed nmber
1422 if(index == 0) return 1;
1423 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1424 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1429 LoadGameOrPosition (int gameNr)
1430 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1431 if (*appData.loadGameFile != NULLCHAR) {
1432 if (!LoadGameFromFile(appData.loadGameFile,
1433 CalculateIndex(appData.loadGameIndex, gameNr),
1434 appData.loadGameFile, FALSE)) {
1435 DisplayFatalError(_("Bad game file"), 0, 1);
1438 } else if (*appData.loadPositionFile != NULLCHAR) {
1439 if (!LoadPositionFromFile(appData.loadPositionFile,
1440 CalculateIndex(appData.loadPositionIndex, gameNr),
1441 appData.loadPositionFile)) {
1442 DisplayFatalError(_("Bad position file"), 0, 1);
1450 ReserveGame (int gameNr, char resChar)
1452 FILE *tf = fopen(appData.tourneyFile, "r+");
1453 char *p, *q, c, buf[MSG_SIZ];
1454 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1455 safeStrCpy(buf, lastMsg, MSG_SIZ);
1456 DisplayMessage(_("Pick new game"), "");
1457 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1458 ParseArgsFromFile(tf);
1459 p = q = appData.results;
1460 if(appData.debugMode) {
1461 char *r = appData.participants;
1462 fprintf(debugFP, "results = '%s'\n", p);
1463 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1464 fprintf(debugFP, "\n");
1466 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1468 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1469 safeStrCpy(q, p, strlen(p) + 2);
1470 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1471 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1472 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1473 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1476 fseek(tf, -(strlen(p)+4), SEEK_END);
1478 if(c != '"') // depending on DOS or Unix line endings we can be one off
1479 fseek(tf, -(strlen(p)+2), SEEK_END);
1480 else fseek(tf, -(strlen(p)+3), SEEK_END);
1481 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1482 DisplayMessage(buf, "");
1483 free(p); appData.results = q;
1484 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1485 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1486 int round = appData.defaultMatchGames * appData.tourneyType;
1487 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1488 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1489 UnloadEngine(&first); // next game belongs to other pairing;
1490 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1492 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1496 MatchEvent (int mode)
1497 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1499 if(matchMode) { // already in match mode: switch it off
1501 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1504 // if(gameMode != BeginningOfGame) {
1505 // DisplayError(_("You can only start a match from the initial position."), 0);
1509 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1510 /* Set up machine vs. machine match */
1512 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1513 if(appData.tourneyFile[0]) {
1515 if(nextGame > appData.matchGames) {
1517 if(strchr(appData.results, '*') == NULL) {
1519 appData.tourneyCycles++;
1520 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1522 NextTourneyGame(-1, &dummy);
1524 if(nextGame <= appData.matchGames) {
1525 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1527 ScheduleDelayedEvent(NextMatchGame, 10000);
1532 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1533 DisplayError(buf, 0);
1534 appData.tourneyFile[0] = 0;
1538 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1539 DisplayFatalError(_("Can't have a match with no chess programs"),
1544 matchGame = roundNr = 1;
1545 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1549 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1552 InitBackEnd3 P((void))
1554 GameMode initialMode;
1558 InitChessProgram(&first, startedFromSetupPosition);
1560 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1561 free(programVersion);
1562 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1563 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1564 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1567 if (appData.icsActive) {
1569 /* [DM] Make a console window if needed [HGM] merged ifs */
1575 if (*appData.icsCommPort != NULLCHAR)
1576 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1577 appData.icsCommPort);
1579 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1580 appData.icsHost, appData.icsPort);
1582 if( (len >= MSG_SIZ) && appData.debugMode )
1583 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1585 DisplayFatalError(buf, err, 1);
1590 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1592 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1593 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1594 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1595 } else if (appData.noChessProgram) {
1601 if (*appData.cmailGameName != NULLCHAR) {
1603 OpenLoopback(&cmailPR);
1605 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1609 DisplayMessage("", "");
1610 if (StrCaseCmp(appData.initialMode, "") == 0) {
1611 initialMode = BeginningOfGame;
1612 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1613 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1614 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1615 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1618 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1619 initialMode = TwoMachinesPlay;
1620 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1621 initialMode = AnalyzeFile;
1622 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1623 initialMode = AnalyzeMode;
1624 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1625 initialMode = MachinePlaysWhite;
1626 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1627 initialMode = MachinePlaysBlack;
1628 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1629 initialMode = EditGame;
1630 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1631 initialMode = EditPosition;
1632 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1633 initialMode = Training;
1635 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1636 if( (len >= MSG_SIZ) && appData.debugMode )
1637 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1639 DisplayFatalError(buf, 0, 2);
1643 if (appData.matchMode) {
1644 if(appData.tourneyFile[0]) { // start tourney from command line
1646 if(f = fopen(appData.tourneyFile, "r")) {
1647 ParseArgsFromFile(f); // make sure tourney parmeters re known
1649 appData.clockMode = TRUE;
1651 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1654 } else if (*appData.cmailGameName != NULLCHAR) {
1655 /* Set up cmail mode */
1656 ReloadCmailMsgEvent(TRUE);
1658 /* Set up other modes */
1659 if (initialMode == AnalyzeFile) {
1660 if (*appData.loadGameFile == NULLCHAR) {
1661 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1665 if (*appData.loadGameFile != NULLCHAR) {
1666 (void) LoadGameFromFile(appData.loadGameFile,
1667 appData.loadGameIndex,
1668 appData.loadGameFile, TRUE);
1669 } else if (*appData.loadPositionFile != NULLCHAR) {
1670 (void) LoadPositionFromFile(appData.loadPositionFile,
1671 appData.loadPositionIndex,
1672 appData.loadPositionFile);
1673 /* [HGM] try to make self-starting even after FEN load */
1674 /* to allow automatic setup of fairy variants with wtm */
1675 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1676 gameMode = BeginningOfGame;
1677 setboardSpoiledMachineBlack = 1;
1679 /* [HGM] loadPos: make that every new game uses the setup */
1680 /* from file as long as we do not switch variant */
1681 if(!blackPlaysFirst) {
1682 startedFromPositionFile = TRUE;
1683 CopyBoard(filePosition, boards[0]);
1686 if (initialMode == AnalyzeMode) {
1687 if (appData.noChessProgram) {
1688 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1691 if (appData.icsActive) {
1692 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1696 } else if (initialMode == AnalyzeFile) {
1697 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1698 ShowThinkingEvent();
1700 AnalysisPeriodicEvent(1);
1701 } else if (initialMode == MachinePlaysWhite) {
1702 if (appData.noChessProgram) {
1703 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1707 if (appData.icsActive) {
1708 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1712 MachineWhiteEvent();
1713 } else if (initialMode == MachinePlaysBlack) {
1714 if (appData.noChessProgram) {
1715 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1719 if (appData.icsActive) {
1720 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1724 MachineBlackEvent();
1725 } else if (initialMode == TwoMachinesPlay) {
1726 if (appData.noChessProgram) {
1727 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1731 if (appData.icsActive) {
1732 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1737 } else if (initialMode == EditGame) {
1739 } else if (initialMode == EditPosition) {
1740 EditPositionEvent();
1741 } else if (initialMode == Training) {
1742 if (*appData.loadGameFile == NULLCHAR) {
1743 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1752 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1754 DisplayBook(current+1);
1756 MoveHistorySet( movelist, first, last, current, pvInfoList );
1758 EvalGraphSet( first, last, current, pvInfoList );
1760 MakeEngineOutputTitle();
1764 * Establish will establish a contact to a remote host.port.
1765 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1766 * used to talk to the host.
1767 * Returns 0 if okay, error code if not.
1774 if (*appData.icsCommPort != NULLCHAR) {
1775 /* Talk to the host through a serial comm port */
1776 return OpenCommPort(appData.icsCommPort, &icsPR);
1778 } else if (*appData.gateway != NULLCHAR) {
1779 if (*appData.remoteShell == NULLCHAR) {
1780 /* Use the rcmd protocol to run telnet program on a gateway host */
1781 snprintf(buf, sizeof(buf), "%s %s %s",
1782 appData.telnetProgram, appData.icsHost, appData.icsPort);
1783 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1786 /* Use the rsh program to run telnet program on a gateway host */
1787 if (*appData.remoteUser == NULLCHAR) {
1788 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1789 appData.gateway, appData.telnetProgram,
1790 appData.icsHost, appData.icsPort);
1792 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1793 appData.remoteShell, appData.gateway,
1794 appData.remoteUser, appData.telnetProgram,
1795 appData.icsHost, appData.icsPort);
1797 return StartChildProcess(buf, "", &icsPR);
1800 } else if (appData.useTelnet) {
1801 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1804 /* TCP socket interface differs somewhat between
1805 Unix and NT; handle details in the front end.
1807 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1812 EscapeExpand (char *p, char *q)
1813 { // [HGM] initstring: routine to shape up string arguments
1814 while(*p++ = *q++) if(p[-1] == '\\')
1816 case 'n': p[-1] = '\n'; break;
1817 case 'r': p[-1] = '\r'; break;
1818 case 't': p[-1] = '\t'; break;
1819 case '\\': p[-1] = '\\'; break;
1820 case 0: *p = 0; return;
1821 default: p[-1] = q[-1]; break;
1826 show_bytes (FILE *fp, char *buf, int count)
1829 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1830 fprintf(fp, "\\%03o", *buf & 0xff);
1839 /* Returns an errno value */
1841 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1843 char buf[8192], *p, *q, *buflim;
1844 int left, newcount, outcount;
1846 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1847 *appData.gateway != NULLCHAR) {
1848 if (appData.debugMode) {
1849 fprintf(debugFP, ">ICS: ");
1850 show_bytes(debugFP, message, count);
1851 fprintf(debugFP, "\n");
1853 return OutputToProcess(pr, message, count, outError);
1856 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1863 if (appData.debugMode) {
1864 fprintf(debugFP, ">ICS: ");
1865 show_bytes(debugFP, buf, newcount);
1866 fprintf(debugFP, "\n");
1868 outcount = OutputToProcess(pr, buf, newcount, outError);
1869 if (outcount < newcount) return -1; /* to be sure */
1876 } else if (((unsigned char) *p) == TN_IAC) {
1877 *q++ = (char) TN_IAC;
1884 if (appData.debugMode) {
1885 fprintf(debugFP, ">ICS: ");
1886 show_bytes(debugFP, buf, newcount);
1887 fprintf(debugFP, "\n");
1889 outcount = OutputToProcess(pr, buf, newcount, outError);
1890 if (outcount < newcount) return -1; /* to be sure */
1895 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1897 int outError, outCount;
1898 static int gotEof = 0;
1901 /* Pass data read from player on to ICS */
1904 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1905 if (outCount < count) {
1906 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1908 if(have_sent_ICS_logon == 2) {
1909 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1910 fprintf(ini, "%s", message);
1911 have_sent_ICS_logon = 3;
1913 have_sent_ICS_logon = 1;
1914 } else if(have_sent_ICS_logon == 3) {
1915 fprintf(ini, "%s", message);
1917 have_sent_ICS_logon = 1;
1919 } else if (count < 0) {
1920 RemoveInputSource(isr);
1921 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1922 } else if (gotEof++ > 0) {
1923 RemoveInputSource(isr);
1924 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1930 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1931 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1932 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1933 SendToICS("date\n");
1934 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1937 /* added routine for printf style output to ics */
1939 ics_printf (char *format, ...)
1941 char buffer[MSG_SIZ];
1944 va_start(args, format);
1945 vsnprintf(buffer, sizeof(buffer), format, args);
1946 buffer[sizeof(buffer)-1] = '\0';
1954 int count, outCount, outError;
1956 if (icsPR == NoProc) return;
1959 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1960 if (outCount < count) {
1961 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1965 /* This is used for sending logon scripts to the ICS. Sending
1966 without a delay causes problems when using timestamp on ICC
1967 (at least on my machine). */
1969 SendToICSDelayed (char *s, long msdelay)
1971 int count, outCount, outError;
1973 if (icsPR == NoProc) return;
1976 if (appData.debugMode) {
1977 fprintf(debugFP, ">ICS: ");
1978 show_bytes(debugFP, s, count);
1979 fprintf(debugFP, "\n");
1981 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1983 if (outCount < count) {
1984 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1989 /* Remove all highlighting escape sequences in s
1990 Also deletes any suffix starting with '('
1993 StripHighlightAndTitle (char *s)
1995 static char retbuf[MSG_SIZ];
1998 while (*s != NULLCHAR) {
1999 while (*s == '\033') {
2000 while (*s != NULLCHAR && !isalpha(*s)) s++;
2001 if (*s != NULLCHAR) s++;
2003 while (*s != NULLCHAR && *s != '\033') {
2004 if (*s == '(' || *s == '[') {
2015 /* Remove all highlighting escape sequences in s */
2017 StripHighlight (char *s)
2019 static char retbuf[MSG_SIZ];
2022 while (*s != NULLCHAR) {
2023 while (*s == '\033') {
2024 while (*s != NULLCHAR && !isalpha(*s)) s++;
2025 if (*s != NULLCHAR) s++;
2027 while (*s != NULLCHAR && *s != '\033') {
2035 char engineVariant[MSG_SIZ];
2036 char *variantNames[] = VARIANT_NAMES;
2038 VariantName (VariantClass v)
2040 if(v == VariantUnknown || *engineVariant) return engineVariant;
2041 return variantNames[v];
2045 /* Identify a variant from the strings the chess servers use or the
2046 PGN Variant tag names we use. */
2048 StringToVariant (char *e)
2052 VariantClass v = VariantNormal;
2053 int i, found = FALSE;
2059 /* [HGM] skip over optional board-size prefixes */
2060 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2061 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2062 while( *e++ != '_');
2065 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2069 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2070 if (StrCaseStr(e, variantNames[i])) {
2071 v = (VariantClass) i;
2078 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2079 || StrCaseStr(e, "wild/fr")
2080 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2081 v = VariantFischeRandom;
2082 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2083 (i = 1, p = StrCaseStr(e, "w"))) {
2085 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2092 case 0: /* FICS only, actually */
2094 /* Castling legal even if K starts on d-file */
2095 v = VariantWildCastle;
2100 /* Castling illegal even if K & R happen to start in
2101 normal positions. */
2102 v = VariantNoCastle;
2115 /* Castling legal iff K & R start in normal positions */
2121 /* Special wilds for position setup; unclear what to do here */
2122 v = VariantLoadable;
2125 /* Bizarre ICC game */
2126 v = VariantTwoKings;
2129 v = VariantKriegspiel;
2135 v = VariantFischeRandom;
2138 v = VariantCrazyhouse;
2141 v = VariantBughouse;
2147 /* Not quite the same as FICS suicide! */
2148 v = VariantGiveaway;
2154 v = VariantShatranj;
2157 /* Temporary names for future ICC types. The name *will* change in
2158 the next xboard/WinBoard release after ICC defines it. */
2196 v = VariantCapablanca;
2199 v = VariantKnightmate;
2205 v = VariantCylinder;
2211 v = VariantCapaRandom;
2214 v = VariantBerolina;
2226 /* Found "wild" or "w" in the string but no number;
2227 must assume it's normal chess. */
2231 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2232 if( (len >= MSG_SIZ) && appData.debugMode )
2233 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2235 DisplayError(buf, 0);
2241 if (appData.debugMode) {
2242 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2243 e, wnum, VariantName(v));
2248 static int leftover_start = 0, leftover_len = 0;
2249 char star_match[STAR_MATCH_N][MSG_SIZ];
2251 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2252 advance *index beyond it, and set leftover_start to the new value of
2253 *index; else return FALSE. If pattern contains the character '*', it
2254 matches any sequence of characters not containing '\r', '\n', or the
2255 character following the '*' (if any), and the matched sequence(s) are
2256 copied into star_match.
2259 looking_at ( char *buf, int *index, char *pattern)
2261 char *bufp = &buf[*index], *patternp = pattern;
2263 char *matchp = star_match[0];
2266 if (*patternp == NULLCHAR) {
2267 *index = leftover_start = bufp - buf;
2271 if (*bufp == NULLCHAR) return FALSE;
2272 if (*patternp == '*') {
2273 if (*bufp == *(patternp + 1)) {
2275 matchp = star_match[++star_count];
2279 } else if (*bufp == '\n' || *bufp == '\r') {
2281 if (*patternp == NULLCHAR)
2286 *matchp++ = *bufp++;
2290 if (*patternp != *bufp) return FALSE;
2297 SendToPlayer (char *data, int length)
2299 int error, outCount;
2300 outCount = OutputToProcess(NoProc, data, length, &error);
2301 if (outCount < length) {
2302 DisplayFatalError(_("Error writing to display"), error, 1);
2307 PackHolding (char packed[], char *holding)
2317 switch (runlength) {
2328 sprintf(q, "%d", runlength);
2340 /* Telnet protocol requests from the front end */
2342 TelnetRequest (unsigned char ddww, unsigned char option)
2344 unsigned char msg[3];
2345 int outCount, outError;
2347 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2349 if (appData.debugMode) {
2350 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2366 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2375 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2378 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2383 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2385 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2392 if (!appData.icsActive) return;
2393 TelnetRequest(TN_DO, TN_ECHO);
2399 if (!appData.icsActive) return;
2400 TelnetRequest(TN_DONT, TN_ECHO);
2404 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2406 /* put the holdings sent to us by the server on the board holdings area */
2407 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2411 if(gameInfo.holdingsWidth < 2) return;
2412 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2413 return; // prevent overwriting by pre-board holdings
2415 if( (int)lowestPiece >= BlackPawn ) {
2418 holdingsStartRow = BOARD_HEIGHT-1;
2421 holdingsColumn = BOARD_WIDTH-1;
2422 countsColumn = BOARD_WIDTH-2;
2423 holdingsStartRow = 0;
2427 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2428 board[i][holdingsColumn] = EmptySquare;
2429 board[i][countsColumn] = (ChessSquare) 0;
2431 while( (p=*holdings++) != NULLCHAR ) {
2432 piece = CharToPiece( ToUpper(p) );
2433 if(piece == EmptySquare) continue;
2434 /*j = (int) piece - (int) WhitePawn;*/
2435 j = PieceToNumber(piece);
2436 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2437 if(j < 0) continue; /* should not happen */
2438 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2439 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2440 board[holdingsStartRow+j*direction][countsColumn]++;
2446 VariantSwitch (Board board, VariantClass newVariant)
2448 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2449 static Board oldBoard;
2451 startedFromPositionFile = FALSE;
2452 if(gameInfo.variant == newVariant) return;
2454 /* [HGM] This routine is called each time an assignment is made to
2455 * gameInfo.variant during a game, to make sure the board sizes
2456 * are set to match the new variant. If that means adding or deleting
2457 * holdings, we shift the playing board accordingly
2458 * This kludge is needed because in ICS observe mode, we get boards
2459 * of an ongoing game without knowing the variant, and learn about the
2460 * latter only later. This can be because of the move list we requested,
2461 * in which case the game history is refilled from the beginning anyway,
2462 * but also when receiving holdings of a crazyhouse game. In the latter
2463 * case we want to add those holdings to the already received position.
2467 if (appData.debugMode) {
2468 fprintf(debugFP, "Switch board from %s to %s\n",
2469 VariantName(gameInfo.variant), VariantName(newVariant));
2470 setbuf(debugFP, NULL);
2472 shuffleOpenings = 0; /* [HGM] shuffle */
2473 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2477 newWidth = 9; newHeight = 9;
2478 gameInfo.holdingsSize = 7;
2479 case VariantBughouse:
2480 case VariantCrazyhouse:
2481 newHoldingsWidth = 2; break;
2485 newHoldingsWidth = 2;
2486 gameInfo.holdingsSize = 8;
2489 case VariantCapablanca:
2490 case VariantCapaRandom:
2493 newHoldingsWidth = gameInfo.holdingsSize = 0;
2496 if(newWidth != gameInfo.boardWidth ||
2497 newHeight != gameInfo.boardHeight ||
2498 newHoldingsWidth != gameInfo.holdingsWidth ) {
2500 /* shift position to new playing area, if needed */
2501 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2502 for(i=0; i<BOARD_HEIGHT; i++)
2503 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2504 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2506 for(i=0; i<newHeight; i++) {
2507 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2508 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2510 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2511 for(i=0; i<BOARD_HEIGHT; i++)
2512 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2513 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2516 board[HOLDINGS_SET] = 0;
2517 gameInfo.boardWidth = newWidth;
2518 gameInfo.boardHeight = newHeight;
2519 gameInfo.holdingsWidth = newHoldingsWidth;
2520 gameInfo.variant = newVariant;
2521 InitDrawingSizes(-2, 0);
2522 } else gameInfo.variant = newVariant;
2523 CopyBoard(oldBoard, board); // remember correctly formatted board
2524 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2525 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2528 static int loggedOn = FALSE;
2530 /*-- Game start info cache: --*/
2532 char gs_kind[MSG_SIZ];
2533 static char player1Name[128] = "";
2534 static char player2Name[128] = "";
2535 static char cont_seq[] = "\n\\ ";
2536 static int player1Rating = -1;
2537 static int player2Rating = -1;
2538 /*----------------------------*/
2540 ColorClass curColor = ColorNormal;
2541 int suppressKibitz = 0;
2544 Boolean soughtPending = FALSE;
2545 Boolean seekGraphUp;
2546 #define MAX_SEEK_ADS 200
2548 char *seekAdList[MAX_SEEK_ADS];
2549 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2550 float tcList[MAX_SEEK_ADS];
2551 char colorList[MAX_SEEK_ADS];
2552 int nrOfSeekAds = 0;
2553 int minRating = 1010, maxRating = 2800;
2554 int hMargin = 10, vMargin = 20, h, w;
2555 extern int squareSize, lineGap;
2560 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2561 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2562 if(r < minRating+100 && r >=0 ) r = minRating+100;
2563 if(r > maxRating) r = maxRating;
2564 if(tc < 1.f) tc = 1.f;
2565 if(tc > 95.f) tc = 95.f;
2566 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2567 y = ((double)r - minRating)/(maxRating - minRating)
2568 * (h-vMargin-squareSize/8-1) + vMargin;
2569 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2570 if(strstr(seekAdList[i], " u ")) color = 1;
2571 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2572 !strstr(seekAdList[i], "bullet") &&
2573 !strstr(seekAdList[i], "blitz") &&
2574 !strstr(seekAdList[i], "standard") ) color = 2;
2575 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2576 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2580 PlotSingleSeekAd (int i)
2586 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2588 char buf[MSG_SIZ], *ext = "";
2589 VariantClass v = StringToVariant(type);
2590 if(strstr(type, "wild")) {
2591 ext = type + 4; // append wild number
2592 if(v == VariantFischeRandom) type = "chess960"; else
2593 if(v == VariantLoadable) type = "setup"; else
2594 type = VariantName(v);
2596 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2597 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2598 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2599 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2600 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2601 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2602 seekNrList[nrOfSeekAds] = nr;
2603 zList[nrOfSeekAds] = 0;
2604 seekAdList[nrOfSeekAds++] = StrSave(buf);
2605 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2610 EraseSeekDot (int i)
2612 int x = xList[i], y = yList[i], d=squareSize/4, k;
2613 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2614 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2615 // now replot every dot that overlapped
2616 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2617 int xx = xList[k], yy = yList[k];
2618 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2619 DrawSeekDot(xx, yy, colorList[k]);
2624 RemoveSeekAd (int nr)
2627 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2629 if(seekAdList[i]) free(seekAdList[i]);
2630 seekAdList[i] = seekAdList[--nrOfSeekAds];
2631 seekNrList[i] = seekNrList[nrOfSeekAds];
2632 ratingList[i] = ratingList[nrOfSeekAds];
2633 colorList[i] = colorList[nrOfSeekAds];
2634 tcList[i] = tcList[nrOfSeekAds];
2635 xList[i] = xList[nrOfSeekAds];
2636 yList[i] = yList[nrOfSeekAds];
2637 zList[i] = zList[nrOfSeekAds];
2638 seekAdList[nrOfSeekAds] = NULL;
2644 MatchSoughtLine (char *line)
2646 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2647 int nr, base, inc, u=0; char dummy;
2649 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2650 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2652 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2653 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2654 // match: compact and save the line
2655 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2665 if(!seekGraphUp) return FALSE;
2666 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2667 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2669 DrawSeekBackground(0, 0, w, h);
2670 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2671 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2672 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2673 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2675 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2678 snprintf(buf, MSG_SIZ, "%d", i);
2679 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2682 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2683 for(i=1; i<100; i+=(i<10?1:5)) {
2684 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2685 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2686 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2688 snprintf(buf, MSG_SIZ, "%d", i);
2689 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2692 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2697 SeekGraphClick (ClickType click, int x, int y, int moving)
2699 static int lastDown = 0, displayed = 0, lastSecond;
2700 if(y < 0) return FALSE;
2701 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2702 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2703 if(!seekGraphUp) return FALSE;
2704 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2705 DrawPosition(TRUE, NULL);
2708 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2709 if(click == Release || moving) return FALSE;
2711 soughtPending = TRUE;
2712 SendToICS(ics_prefix);
2713 SendToICS("sought\n"); // should this be "sought all"?
2714 } else { // issue challenge based on clicked ad
2715 int dist = 10000; int i, closest = 0, second = 0;
2716 for(i=0; i<nrOfSeekAds; i++) {
2717 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2718 if(d < dist) { dist = d; closest = i; }
2719 second += (d - zList[i] < 120); // count in-range ads
2720 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2724 second = (second > 1);
2725 if(displayed != closest || second != lastSecond) {
2726 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2727 lastSecond = second; displayed = closest;
2729 if(click == Press) {
2730 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2733 } // on press 'hit', only show info
2734 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2735 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2736 SendToICS(ics_prefix);
2738 return TRUE; // let incoming board of started game pop down the graph
2739 } else if(click == Release) { // release 'miss' is ignored
2740 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2741 if(moving == 2) { // right up-click
2742 nrOfSeekAds = 0; // refresh graph
2743 soughtPending = TRUE;
2744 SendToICS(ics_prefix);
2745 SendToICS("sought\n"); // should this be "sought all"?
2748 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2749 // press miss or release hit 'pop down' seek graph
2750 seekGraphUp = FALSE;
2751 DrawPosition(TRUE, NULL);
2757 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2759 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2760 #define STARTED_NONE 0
2761 #define STARTED_MOVES 1
2762 #define STARTED_BOARD 2
2763 #define STARTED_OBSERVE 3
2764 #define STARTED_HOLDINGS 4
2765 #define STARTED_CHATTER 5
2766 #define STARTED_COMMENT 6
2767 #define STARTED_MOVES_NOHIDE 7
2769 static int started = STARTED_NONE;
2770 static char parse[20000];
2771 static int parse_pos = 0;
2772 static char buf[BUF_SIZE + 1];
2773 static int firstTime = TRUE, intfSet = FALSE;
2774 static ColorClass prevColor = ColorNormal;
2775 static int savingComment = FALSE;
2776 static int cmatch = 0; // continuation sequence match
2783 int backup; /* [DM] For zippy color lines */
2785 char talker[MSG_SIZ]; // [HGM] chat
2788 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2790 if (appData.debugMode) {
2792 fprintf(debugFP, "<ICS: ");
2793 show_bytes(debugFP, data, count);
2794 fprintf(debugFP, "\n");
2798 if (appData.debugMode) { int f = forwardMostMove;
2799 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2800 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2801 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2804 /* If last read ended with a partial line that we couldn't parse,
2805 prepend it to the new read and try again. */
2806 if (leftover_len > 0) {
2807 for (i=0; i<leftover_len; i++)
2808 buf[i] = buf[leftover_start + i];
2811 /* copy new characters into the buffer */
2812 bp = buf + leftover_len;
2813 buf_len=leftover_len;
2814 for (i=0; i<count; i++)
2817 if (data[i] == '\r')
2820 // join lines split by ICS?
2821 if (!appData.noJoin)
2824 Joining just consists of finding matches against the
2825 continuation sequence, and discarding that sequence
2826 if found instead of copying it. So, until a match
2827 fails, there's nothing to do since it might be the
2828 complete sequence, and thus, something we don't want
2831 if (data[i] == cont_seq[cmatch])
2834 if (cmatch == strlen(cont_seq))
2836 cmatch = 0; // complete match. just reset the counter
2839 it's possible for the ICS to not include the space
2840 at the end of the last word, making our [correct]
2841 join operation fuse two separate words. the server
2842 does this when the space occurs at the width setting.
2844 if (!buf_len || buf[buf_len-1] != ' ')
2855 match failed, so we have to copy what matched before
2856 falling through and copying this character. In reality,
2857 this will only ever be just the newline character, but
2858 it doesn't hurt to be precise.
2860 strncpy(bp, cont_seq, cmatch);
2872 buf[buf_len] = NULLCHAR;
2873 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2878 while (i < buf_len) {
2879 /* Deal with part of the TELNET option negotiation
2880 protocol. We refuse to do anything beyond the
2881 defaults, except that we allow the WILL ECHO option,
2882 which ICS uses to turn off password echoing when we are
2883 directly connected to it. We reject this option
2884 if localLineEditing mode is on (always on in xboard)
2885 and we are talking to port 23, which might be a real
2886 telnet server that will try to keep WILL ECHO on permanently.
2888 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2889 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2890 unsigned char option;
2892 switch ((unsigned char) buf[++i]) {
2894 if (appData.debugMode)
2895 fprintf(debugFP, "\n<WILL ");
2896 switch (option = (unsigned char) buf[++i]) {
2898 if (appData.debugMode)
2899 fprintf(debugFP, "ECHO ");
2900 /* Reply only if this is a change, according
2901 to the protocol rules. */
2902 if (remoteEchoOption) break;
2903 if (appData.localLineEditing &&
2904 atoi(appData.icsPort) == TN_PORT) {
2905 TelnetRequest(TN_DONT, TN_ECHO);
2908 TelnetRequest(TN_DO, TN_ECHO);
2909 remoteEchoOption = TRUE;
2913 if (appData.debugMode)
2914 fprintf(debugFP, "%d ", option);
2915 /* Whatever this is, we don't want it. */
2916 TelnetRequest(TN_DONT, option);
2921 if (appData.debugMode)
2922 fprintf(debugFP, "\n<WONT ");
2923 switch (option = (unsigned char) buf[++i]) {
2925 if (appData.debugMode)
2926 fprintf(debugFP, "ECHO ");
2927 /* Reply only if this is a change, according
2928 to the protocol rules. */
2929 if (!remoteEchoOption) break;
2931 TelnetRequest(TN_DONT, TN_ECHO);
2932 remoteEchoOption = FALSE;
2935 if (appData.debugMode)
2936 fprintf(debugFP, "%d ", (unsigned char) option);
2937 /* Whatever this is, it must already be turned
2938 off, because we never agree to turn on
2939 anything non-default, so according to the
2940 protocol rules, we don't reply. */
2945 if (appData.debugMode)
2946 fprintf(debugFP, "\n<DO ");
2947 switch (option = (unsigned char) buf[++i]) {
2949 /* Whatever this is, we refuse to do it. */
2950 if (appData.debugMode)
2951 fprintf(debugFP, "%d ", option);
2952 TelnetRequest(TN_WONT, option);
2957 if (appData.debugMode)
2958 fprintf(debugFP, "\n<DONT ");
2959 switch (option = (unsigned char) buf[++i]) {
2961 if (appData.debugMode)
2962 fprintf(debugFP, "%d ", option);
2963 /* Whatever this is, we are already not doing
2964 it, because we never agree to do anything
2965 non-default, so according to the protocol
2966 rules, we don't reply. */
2971 if (appData.debugMode)
2972 fprintf(debugFP, "\n<IAC ");
2973 /* Doubled IAC; pass it through */
2977 if (appData.debugMode)
2978 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2979 /* Drop all other telnet commands on the floor */
2982 if (oldi > next_out)
2983 SendToPlayer(&buf[next_out], oldi - next_out);
2989 /* OK, this at least will *usually* work */
2990 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2994 if (loggedOn && !intfSet) {
2995 if (ics_type == ICS_ICC) {
2996 snprintf(str, MSG_SIZ,
2997 "/set-quietly interface %s\n/set-quietly style 12\n",
2999 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3000 strcat(str, "/set-2 51 1\n/set seek 1\n");
3001 } else if (ics_type == ICS_CHESSNET) {
3002 snprintf(str, MSG_SIZ, "/style 12\n");
3004 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3005 strcat(str, programVersion);
3006 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3007 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3008 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3010 strcat(str, "$iset nohighlight 1\n");
3012 strcat(str, "$iset lock 1\n$style 12\n");
3015 NotifyFrontendLogin();
3019 if (started == STARTED_COMMENT) {
3020 /* Accumulate characters in comment */
3021 parse[parse_pos++] = buf[i];
3022 if (buf[i] == '\n') {
3023 parse[parse_pos] = NULLCHAR;
3024 if(chattingPartner>=0) {
3026 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3027 OutputChatMessage(chattingPartner, mess);
3028 chattingPartner = -1;
3029 next_out = i+1; // [HGM] suppress printing in ICS window
3031 if(!suppressKibitz) // [HGM] kibitz
3032 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3033 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3034 int nrDigit = 0, nrAlph = 0, j;
3035 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3036 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3037 parse[parse_pos] = NULLCHAR;
3038 // try to be smart: if it does not look like search info, it should go to
3039 // ICS interaction window after all, not to engine-output window.
3040 for(j=0; j<parse_pos; j++) { // count letters and digits
3041 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3042 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3043 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3045 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3046 int depth=0; float score;
3047 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3048 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3049 pvInfoList[forwardMostMove-1].depth = depth;
3050 pvInfoList[forwardMostMove-1].score = 100*score;
3052 OutputKibitz(suppressKibitz, parse);
3055 if(gameMode == IcsObserving) // restore original ICS messages
3056 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3057 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3059 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3060 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3061 SendToPlayer(tmp, strlen(tmp));
3063 next_out = i+1; // [HGM] suppress printing in ICS window
3065 started = STARTED_NONE;
3067 /* Don't match patterns against characters in comment */
3072 if (started == STARTED_CHATTER) {
3073 if (buf[i] != '\n') {
3074 /* Don't match patterns against characters in chatter */
3078 started = STARTED_NONE;
3079 if(suppressKibitz) next_out = i+1;
3082 /* Kludge to deal with rcmd protocol */
3083 if (firstTime && looking_at(buf, &i, "\001*")) {
3084 DisplayFatalError(&buf[1], 0, 1);
3090 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3093 if (appData.debugMode)
3094 fprintf(debugFP, "ics_type %d\n", ics_type);
3097 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3098 ics_type = ICS_FICS;
3100 if (appData.debugMode)
3101 fprintf(debugFP, "ics_type %d\n", ics_type);
3104 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3105 ics_type = ICS_CHESSNET;
3107 if (appData.debugMode)
3108 fprintf(debugFP, "ics_type %d\n", ics_type);
3113 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3114 looking_at(buf, &i, "Logging you in as \"*\"") ||
3115 looking_at(buf, &i, "will be \"*\""))) {
3116 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3120 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3122 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3123 DisplayIcsInteractionTitle(buf);
3124 have_set_title = TRUE;
3127 /* skip finger notes */
3128 if (started == STARTED_NONE &&
3129 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3130 (buf[i] == '1' && buf[i+1] == '0')) &&
3131 buf[i+2] == ':' && buf[i+3] == ' ') {
3132 started = STARTED_CHATTER;
3138 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3139 if(appData.seekGraph) {
3140 if(soughtPending && MatchSoughtLine(buf+i)) {
3141 i = strstr(buf+i, "rated") - buf;
3142 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3143 next_out = leftover_start = i;
3144 started = STARTED_CHATTER;
3145 suppressKibitz = TRUE;
3148 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3149 && looking_at(buf, &i, "* ads displayed")) {
3150 soughtPending = FALSE;
3155 if(appData.autoRefresh) {
3156 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3157 int s = (ics_type == ICS_ICC); // ICC format differs
3159 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3160 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3161 looking_at(buf, &i, "*% "); // eat prompt
3162 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3163 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3164 next_out = i; // suppress
3167 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3168 char *p = star_match[0];
3170 if(seekGraphUp) RemoveSeekAd(atoi(p));
3171 while(*p && *p++ != ' '); // next
3173 looking_at(buf, &i, "*% "); // eat prompt
3174 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3181 /* skip formula vars */
3182 if (started == STARTED_NONE &&
3183 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3184 started = STARTED_CHATTER;
3189 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3190 if (appData.autoKibitz && started == STARTED_NONE &&
3191 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3192 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3193 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3194 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3195 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3196 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3197 suppressKibitz = TRUE;
3198 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3200 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3201 && (gameMode == IcsPlayingWhite)) ||
3202 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3203 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3204 started = STARTED_CHATTER; // own kibitz we simply discard
3206 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3207 parse_pos = 0; parse[0] = NULLCHAR;
3208 savingComment = TRUE;
3209 suppressKibitz = gameMode != IcsObserving ? 2 :
3210 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3214 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3215 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3216 && atoi(star_match[0])) {
3217 // suppress the acknowledgements of our own autoKibitz
3219 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3221 SendToPlayer(star_match[0], strlen(star_match[0]));
3222 if(looking_at(buf, &i, "*% ")) // eat prompt
3223 suppressKibitz = FALSE;
3227 } // [HGM] kibitz: end of patch
3229 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3231 // [HGM] chat: intercept tells by users for which we have an open chat window
3233 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3234 looking_at(buf, &i, "* whispers:") ||
3235 looking_at(buf, &i, "* kibitzes:") ||
3236 looking_at(buf, &i, "* shouts:") ||
3237 looking_at(buf, &i, "* c-shouts:") ||
3238 looking_at(buf, &i, "--> * ") ||
3239 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3240 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3241 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3242 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3244 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3245 chattingPartner = -1;
3247 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3248 for(p=0; p<MAX_CHAT; p++) {
3249 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3250 talker[0] = '['; strcat(talker, "] ");
3251 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3252 chattingPartner = p; break;
3255 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3256 for(p=0; p<MAX_CHAT; p++) {
3257 if(!strcmp("kibitzes", chatPartner[p])) {
3258 talker[0] = '['; strcat(talker, "] ");
3259 chattingPartner = p; break;
3262 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3263 for(p=0; p<MAX_CHAT; p++) {
3264 if(!strcmp("whispers", chatPartner[p])) {
3265 talker[0] = '['; strcat(talker, "] ");
3266 chattingPartner = p; break;
3269 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3270 if(buf[i-8] == '-' && buf[i-3] == 't')
3271 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3272 if(!strcmp("c-shouts", chatPartner[p])) {
3273 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3274 chattingPartner = p; break;
3277 if(chattingPartner < 0)
3278 for(p=0; p<MAX_CHAT; p++) {
3279 if(!strcmp("shouts", chatPartner[p])) {
3280 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3281 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3282 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3283 chattingPartner = p; break;
3287 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3288 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3289 talker[0] = 0; Colorize(ColorTell, FALSE);
3290 chattingPartner = p; break;
3292 if(chattingPartner<0) i = oldi; else {
3293 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3294 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3295 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3296 started = STARTED_COMMENT;
3297 parse_pos = 0; parse[0] = NULLCHAR;
3298 savingComment = 3 + chattingPartner; // counts as TRUE
3299 suppressKibitz = TRUE;
3302 } // [HGM] chat: end of patch
3305 if (appData.zippyTalk || appData.zippyPlay) {
3306 /* [DM] Backup address for color zippy lines */
3308 if (loggedOn == TRUE)
3309 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3310 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3312 } // [DM] 'else { ' deleted
3314 /* Regular tells and says */
3315 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3316 looking_at(buf, &i, "* (your partner) tells you: ") ||
3317 looking_at(buf, &i, "* says: ") ||
3318 /* Don't color "message" or "messages" output */
3319 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3320 looking_at(buf, &i, "*. * at *:*: ") ||
3321 looking_at(buf, &i, "--* (*:*): ") ||
3322 /* Message notifications (same color as tells) */
3323 looking_at(buf, &i, "* has left a message ") ||
3324 looking_at(buf, &i, "* just sent you a message:\n") ||
3325 /* Whispers and kibitzes */
3326 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3327 looking_at(buf, &i, "* kibitzes: ") ||
3329 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3331 if (tkind == 1 && strchr(star_match[0], ':')) {
3332 /* Avoid "tells you:" spoofs in channels */
3335 if (star_match[0][0] == NULLCHAR ||
3336 strchr(star_match[0], ' ') ||
3337 (tkind == 3 && strchr(star_match[1], ' '))) {
3338 /* Reject bogus matches */
3341 if (appData.colorize) {
3342 if (oldi > next_out) {
3343 SendToPlayer(&buf[next_out], oldi - next_out);
3348 Colorize(ColorTell, FALSE);
3349 curColor = ColorTell;
3352 Colorize(ColorKibitz, FALSE);
3353 curColor = ColorKibitz;
3356 p = strrchr(star_match[1], '(');
3363 Colorize(ColorChannel1, FALSE);
3364 curColor = ColorChannel1;
3366 Colorize(ColorChannel, FALSE);
3367 curColor = ColorChannel;
3371 curColor = ColorNormal;
3375 if (started == STARTED_NONE && appData.autoComment &&
3376 (gameMode == IcsObserving ||
3377 gameMode == IcsPlayingWhite ||
3378 gameMode == IcsPlayingBlack)) {
3379 parse_pos = i - oldi;
3380 memcpy(parse, &buf[oldi], parse_pos);
3381 parse[parse_pos] = NULLCHAR;
3382 started = STARTED_COMMENT;
3383 savingComment = TRUE;
3385 started = STARTED_CHATTER;
3386 savingComment = FALSE;
3393 if (looking_at(buf, &i, "* s-shouts: ") ||
3394 looking_at(buf, &i, "* c-shouts: ")) {
3395 if (appData.colorize) {
3396 if (oldi > next_out) {
3397 SendToPlayer(&buf[next_out], oldi - next_out);
3400 Colorize(ColorSShout, FALSE);
3401 curColor = ColorSShout;
3404 started = STARTED_CHATTER;
3408 if (looking_at(buf, &i, "--->")) {
3413 if (looking_at(buf, &i, "* shouts: ") ||
3414 looking_at(buf, &i, "--> ")) {
3415 if (appData.colorize) {
3416 if (oldi > next_out) {
3417 SendToPlayer(&buf[next_out], oldi - next_out);
3420 Colorize(ColorShout, FALSE);
3421 curColor = ColorShout;
3424 started = STARTED_CHATTER;
3428 if (looking_at( buf, &i, "Challenge:")) {
3429 if (appData.colorize) {
3430 if (oldi > next_out) {
3431 SendToPlayer(&buf[next_out], oldi - next_out);
3434 Colorize(ColorChallenge, FALSE);
3435 curColor = ColorChallenge;
3441 if (looking_at(buf, &i, "* offers you") ||
3442 looking_at(buf, &i, "* offers to be") ||
3443 looking_at(buf, &i, "* would like to") ||
3444 looking_at(buf, &i, "* requests to") ||
3445 looking_at(buf, &i, "Your opponent offers") ||
3446 looking_at(buf, &i, "Your opponent requests")) {
3448 if (appData.colorize) {
3449 if (oldi > next_out) {
3450 SendToPlayer(&buf[next_out], oldi - next_out);
3453 Colorize(ColorRequest, FALSE);
3454 curColor = ColorRequest;
3459 if (looking_at(buf, &i, "* (*) seeking")) {
3460 if (appData.colorize) {
3461 if (oldi > next_out) {
3462 SendToPlayer(&buf[next_out], oldi - next_out);
3465 Colorize(ColorSeek, FALSE);
3466 curColor = ColorSeek;
3471 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3473 if (looking_at(buf, &i, "\\ ")) {
3474 if (prevColor != ColorNormal) {
3475 if (oldi > next_out) {
3476 SendToPlayer(&buf[next_out], oldi - next_out);
3479 Colorize(prevColor, TRUE);
3480 curColor = prevColor;
3482 if (savingComment) {
3483 parse_pos = i - oldi;
3484 memcpy(parse, &buf[oldi], parse_pos);
3485 parse[parse_pos] = NULLCHAR;
3486 started = STARTED_COMMENT;
3487 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3488 chattingPartner = savingComment - 3; // kludge to remember the box
3490 started = STARTED_CHATTER;
3495 if (looking_at(buf, &i, "Black Strength :") ||
3496 looking_at(buf, &i, "<<< style 10 board >>>") ||
3497 looking_at(buf, &i, "<10>") ||
3498 looking_at(buf, &i, "#@#")) {
3499 /* Wrong board style */
3501 SendToICS(ics_prefix);
3502 SendToICS("set style 12\n");
3503 SendToICS(ics_prefix);
3504 SendToICS("refresh\n");
3508 if (looking_at(buf, &i, "login:")) {
3509 if (!have_sent_ICS_logon) {
3511 have_sent_ICS_logon = 1;
3512 else // no init script was found
3513 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3514 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3515 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3520 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3521 (looking_at(buf, &i, "\n<12> ") ||
3522 looking_at(buf, &i, "<12> "))) {
3524 if (oldi > next_out) {
3525 SendToPlayer(&buf[next_out], oldi - next_out);
3528 started = STARTED_BOARD;
3533 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3534 looking_at(buf, &i, "<b1> ")) {
3535 if (oldi > next_out) {
3536 SendToPlayer(&buf[next_out], oldi - next_out);
3539 started = STARTED_HOLDINGS;
3544 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3546 /* Header for a move list -- first line */
3548 switch (ics_getting_history) {
3552 case BeginningOfGame:
3553 /* User typed "moves" or "oldmoves" while we
3554 were idle. Pretend we asked for these
3555 moves and soak them up so user can step
3556 through them and/or save them.
3559 gameMode = IcsObserving;
3562 ics_getting_history = H_GOT_UNREQ_HEADER;
3564 case EditGame: /*?*/
3565 case EditPosition: /*?*/
3566 /* Should above feature work in these modes too? */
3567 /* For now it doesn't */
3568 ics_getting_history = H_GOT_UNWANTED_HEADER;
3571 ics_getting_history = H_GOT_UNWANTED_HEADER;
3576 /* Is this the right one? */
3577 if (gameInfo.white && gameInfo.black &&
3578 strcmp(gameInfo.white, star_match[0]) == 0 &&
3579 strcmp(gameInfo.black, star_match[2]) == 0) {
3581 ics_getting_history = H_GOT_REQ_HEADER;
3584 case H_GOT_REQ_HEADER:
3585 case H_GOT_UNREQ_HEADER:
3586 case H_GOT_UNWANTED_HEADER:
3587 case H_GETTING_MOVES:
3588 /* Should not happen */
3589 DisplayError(_("Error gathering move list: two headers"), 0);
3590 ics_getting_history = H_FALSE;
3594 /* Save player ratings into gameInfo if needed */
3595 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3596 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3597 (gameInfo.whiteRating == -1 ||
3598 gameInfo.blackRating == -1)) {
3600 gameInfo.whiteRating = string_to_rating(star_match[1]);
3601 gameInfo.blackRating = string_to_rating(star_match[3]);
3602 if (appData.debugMode)
3603 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3604 gameInfo.whiteRating, gameInfo.blackRating);
3609 if (looking_at(buf, &i,
3610 "* * match, initial time: * minute*, increment: * second")) {
3611 /* Header for a move list -- second line */
3612 /* Initial board will follow if this is a wild game */
3613 if (gameInfo.event != NULL) free(gameInfo.event);
3614 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3615 gameInfo.event = StrSave(str);
3616 /* [HGM] we switched variant. Translate boards if needed. */
3617 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3621 if (looking_at(buf, &i, "Move ")) {
3622 /* Beginning of a move list */
3623 switch (ics_getting_history) {
3625 /* Normally should not happen */
3626 /* Maybe user hit reset while we were parsing */
3629 /* Happens if we are ignoring a move list that is not
3630 * the one we just requested. Common if the user
3631 * tries to observe two games without turning off
3634 case H_GETTING_MOVES:
3635 /* Should not happen */
3636 DisplayError(_("Error gathering move list: nested"), 0);
3637 ics_getting_history = H_FALSE;
3639 case H_GOT_REQ_HEADER:
3640 ics_getting_history = H_GETTING_MOVES;
3641 started = STARTED_MOVES;
3643 if (oldi > next_out) {
3644 SendToPlayer(&buf[next_out], oldi - next_out);
3647 case H_GOT_UNREQ_HEADER:
3648 ics_getting_history = H_GETTING_MOVES;
3649 started = STARTED_MOVES_NOHIDE;
3652 case H_GOT_UNWANTED_HEADER:
3653 ics_getting_history = H_FALSE;
3659 if (looking_at(buf, &i, "% ") ||
3660 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3661 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3662 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3663 soughtPending = FALSE;
3667 if(suppressKibitz) next_out = i;
3668 savingComment = FALSE;
3672 case STARTED_MOVES_NOHIDE:
3673 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3674 parse[parse_pos + i - oldi] = NULLCHAR;
3675 ParseGameHistory(parse);
3677 if (appData.zippyPlay && first.initDone) {
3678 FeedMovesToProgram(&first, forwardMostMove);
3679 if (gameMode == IcsPlayingWhite) {
3680 if (WhiteOnMove(forwardMostMove)) {
3681 if (first.sendTime) {
3682 if (first.useColors) {
3683 SendToProgram("black\n", &first);
3685 SendTimeRemaining(&first, TRUE);
3687 if (first.useColors) {
3688 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3690 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3691 first.maybeThinking = TRUE;
3693 if (first.usePlayother) {
3694 if (first.sendTime) {
3695 SendTimeRemaining(&first, TRUE);
3697 SendToProgram("playother\n", &first);
3703 } else if (gameMode == IcsPlayingBlack) {
3704 if (!WhiteOnMove(forwardMostMove)) {
3705 if (first.sendTime) {
3706 if (first.useColors) {
3707 SendToProgram("white\n", &first);
3709 SendTimeRemaining(&first, FALSE);
3711 if (first.useColors) {
3712 SendToProgram("black\n", &first);
3714 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3715 first.maybeThinking = TRUE;
3717 if (first.usePlayother) {
3718 if (first.sendTime) {
3719 SendTimeRemaining(&first, FALSE);
3721 SendToProgram("playother\n", &first);
3730 if (gameMode == IcsObserving && ics_gamenum == -1) {
3731 /* Moves came from oldmoves or moves command
3732 while we weren't doing anything else.
3734 currentMove = forwardMostMove;
3735 ClearHighlights();/*!!could figure this out*/
3736 flipView = appData.flipView;
3737 DrawPosition(TRUE, boards[currentMove]);
3738 DisplayBothClocks();
3739 snprintf(str, MSG_SIZ, "%s %s %s",
3740 gameInfo.white, _("vs."), gameInfo.black);
3744 /* Moves were history of an active game */
3745 if (gameInfo.resultDetails != NULL) {
3746 free(gameInfo.resultDetails);
3747 gameInfo.resultDetails = NULL;
3750 HistorySet(parseList, backwardMostMove,
3751 forwardMostMove, currentMove-1);
3752 DisplayMove(currentMove - 1);
3753 if (started == STARTED_MOVES) next_out = i;
3754 started = STARTED_NONE;
3755 ics_getting_history = H_FALSE;
3758 case STARTED_OBSERVE:
3759 started = STARTED_NONE;
3760 SendToICS(ics_prefix);
3761 SendToICS("refresh\n");
3767 if(bookHit) { // [HGM] book: simulate book reply
3768 static char bookMove[MSG_SIZ]; // a bit generous?
3770 programStats.nodes = programStats.depth = programStats.time =
3771 programStats.score = programStats.got_only_move = 0;
3772 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3774 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3775 strcat(bookMove, bookHit);
3776 HandleMachineMove(bookMove, &first);
3781 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3782 started == STARTED_HOLDINGS ||
3783 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3784 /* Accumulate characters in move list or board */
3785 parse[parse_pos++] = buf[i];
3788 /* Start of game messages. Mostly we detect start of game
3789 when the first board image arrives. On some versions
3790 of the ICS, though, we need to do a "refresh" after starting
3791 to observe in order to get the current board right away. */
3792 if (looking_at(buf, &i, "Adding game * to observation list")) {
3793 started = STARTED_OBSERVE;
3797 /* Handle auto-observe */
3798 if (appData.autoObserve &&
3799 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3800 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3802 /* Choose the player that was highlighted, if any. */
3803 if (star_match[0][0] == '\033' ||
3804 star_match[1][0] != '\033') {
3805 player = star_match[0];
3807 player = star_match[2];
3809 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3810 ics_prefix, StripHighlightAndTitle(player));
3813 /* Save ratings from notify string */
3814 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3815 player1Rating = string_to_rating(star_match[1]);
3816 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3817 player2Rating = string_to_rating(star_match[3]);
3819 if (appData.debugMode)
3821 "Ratings from 'Game notification:' %s %d, %s %d\n",
3822 player1Name, player1Rating,
3823 player2Name, player2Rating);
3828 /* Deal with automatic examine mode after a game,
3829 and with IcsObserving -> IcsExamining transition */
3830 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3831 looking_at(buf, &i, "has made you an examiner of game *")) {
3833 int gamenum = atoi(star_match[0]);
3834 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3835 gamenum == ics_gamenum) {
3836 /* We were already playing or observing this game;
3837 no need to refetch history */
3838 gameMode = IcsExamining;
3840 pauseExamForwardMostMove = forwardMostMove;
3841 } else if (currentMove < forwardMostMove) {
3842 ForwardInner(forwardMostMove);
3845 /* I don't think this case really can happen */
3846 SendToICS(ics_prefix);
3847 SendToICS("refresh\n");
3852 /* Error messages */
3853 // if (ics_user_moved) {
3854 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3855 if (looking_at(buf, &i, "Illegal move") ||
3856 looking_at(buf, &i, "Not a legal move") ||
3857 looking_at(buf, &i, "Your king is in check") ||
3858 looking_at(buf, &i, "It isn't your turn") ||
3859 looking_at(buf, &i, "It is not your move")) {
3861 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3862 currentMove = forwardMostMove-1;
3863 DisplayMove(currentMove - 1); /* before DMError */
3864 DrawPosition(FALSE, boards[currentMove]);
3865 SwitchClocks(forwardMostMove-1); // [HGM] race
3866 DisplayBothClocks();
3868 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3874 if (looking_at(buf, &i, "still have time") ||
3875 looking_at(buf, &i, "not out of time") ||
3876 looking_at(buf, &i, "either player is out of time") ||
3877 looking_at(buf, &i, "has timeseal; checking")) {
3878 /* We must have called his flag a little too soon */
3879 whiteFlag = blackFlag = FALSE;
3883 if (looking_at(buf, &i, "added * seconds to") ||
3884 looking_at(buf, &i, "seconds were added to")) {
3885 /* Update the clocks */
3886 SendToICS(ics_prefix);
3887 SendToICS("refresh\n");
3891 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3892 ics_clock_paused = TRUE;
3897 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3898 ics_clock_paused = FALSE;
3903 /* Grab player ratings from the Creating: message.
3904 Note we have to check for the special case when
3905 the ICS inserts things like [white] or [black]. */
3906 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3907 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3909 0 player 1 name (not necessarily white)
3911 2 empty, white, or black (IGNORED)
3912 3 player 2 name (not necessarily black)
3915 The names/ratings are sorted out when the game
3916 actually starts (below).
3918 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3919 player1Rating = string_to_rating(star_match[1]);
3920 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3921 player2Rating = string_to_rating(star_match[4]);
3923 if (appData.debugMode)
3925 "Ratings from 'Creating:' %s %d, %s %d\n",
3926 player1Name, player1Rating,
3927 player2Name, player2Rating);
3932 /* Improved generic start/end-of-game messages */
3933 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3934 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3935 /* If tkind == 0: */
3936 /* star_match[0] is the game number */
3937 /* [1] is the white player's name */
3938 /* [2] is the black player's name */
3939 /* For end-of-game: */
3940 /* [3] is the reason for the game end */
3941 /* [4] is a PGN end game-token, preceded by " " */
3942 /* For start-of-game: */
3943 /* [3] begins with "Creating" or "Continuing" */
3944 /* [4] is " *" or empty (don't care). */
3945 int gamenum = atoi(star_match[0]);
3946 char *whitename, *blackname, *why, *endtoken;
3947 ChessMove endtype = EndOfFile;
3950 whitename = star_match[1];
3951 blackname = star_match[2];
3952 why = star_match[3];
3953 endtoken = star_match[4];
3955 whitename = star_match[1];
3956 blackname = star_match[3];
3957 why = star_match[5];
3958 endtoken = star_match[6];
3961 /* Game start messages */
3962 if (strncmp(why, "Creating ", 9) == 0 ||
3963 strncmp(why, "Continuing ", 11) == 0) {
3964 gs_gamenum = gamenum;
3965 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3966 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3967 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3969 if (appData.zippyPlay) {
3970 ZippyGameStart(whitename, blackname);
3973 partnerBoardValid = FALSE; // [HGM] bughouse
3977 /* Game end messages */
3978 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3979 ics_gamenum != gamenum) {
3982 while (endtoken[0] == ' ') endtoken++;
3983 switch (endtoken[0]) {
3986 endtype = GameUnfinished;
3989 endtype = BlackWins;
3992 if (endtoken[1] == '/')
3993 endtype = GameIsDrawn;
3995 endtype = WhiteWins;
3998 GameEnds(endtype, why, GE_ICS);
4000 if (appData.zippyPlay && first.initDone) {
4001 ZippyGameEnd(endtype, why);
4002 if (first.pr == NoProc) {
4003 /* Start the next process early so that we'll
4004 be ready for the next challenge */
4005 StartChessProgram(&first);
4007 /* Send "new" early, in case this command takes
4008 a long time to finish, so that we'll be ready
4009 for the next challenge. */
4010 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4014 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4018 if (looking_at(buf, &i, "Removing game * from observation") ||
4019 looking_at(buf, &i, "no longer observing game *") ||
4020 looking_at(buf, &i, "Game * (*) has no examiners")) {
4021 if (gameMode == IcsObserving &&
4022 atoi(star_match[0]) == ics_gamenum)
4024 /* icsEngineAnalyze */
4025 if (appData.icsEngineAnalyze) {
4032 ics_user_moved = FALSE;
4037 if (looking_at(buf, &i, "no longer examining game *")) {
4038 if (gameMode == IcsExamining &&
4039 atoi(star_match[0]) == ics_gamenum)
4043 ics_user_moved = FALSE;
4048 /* Advance leftover_start past any newlines we find,
4049 so only partial lines can get reparsed */
4050 if (looking_at(buf, &i, "\n")) {
4051 prevColor = curColor;
4052 if (curColor != ColorNormal) {
4053 if (oldi > next_out) {
4054 SendToPlayer(&buf[next_out], oldi - next_out);
4057 Colorize(ColorNormal, FALSE);
4058 curColor = ColorNormal;
4060 if (started == STARTED_BOARD) {
4061 started = STARTED_NONE;
4062 parse[parse_pos] = NULLCHAR;
4063 ParseBoard12(parse);
4066 /* Send premove here */
4067 if (appData.premove) {
4069 if (currentMove == 0 &&
4070 gameMode == IcsPlayingWhite &&
4071 appData.premoveWhite) {
4072 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4073 if (appData.debugMode)
4074 fprintf(debugFP, "Sending premove:\n");
4076 } else if (currentMove == 1 &&
4077 gameMode == IcsPlayingBlack &&
4078 appData.premoveBlack) {
4079 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4080 if (appData.debugMode)
4081 fprintf(debugFP, "Sending premove:\n");
4083 } else if (gotPremove) {
4085 ClearPremoveHighlights();
4086 if (appData.debugMode)
4087 fprintf(debugFP, "Sending premove:\n");
4088 UserMoveEvent(premoveFromX, premoveFromY,
4089 premoveToX, premoveToY,
4094 /* Usually suppress following prompt */
4095 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4096 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4097 if (looking_at(buf, &i, "*% ")) {
4098 savingComment = FALSE;
4103 } else if (started == STARTED_HOLDINGS) {
4105 char new_piece[MSG_SIZ];
4106 started = STARTED_NONE;
4107 parse[parse_pos] = NULLCHAR;
4108 if (appData.debugMode)
4109 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4110 parse, currentMove);
4111 if (sscanf(parse, " game %d", &gamenum) == 1) {
4112 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4113 if (gameInfo.variant == VariantNormal) {
4114 /* [HGM] We seem to switch variant during a game!
4115 * Presumably no holdings were displayed, so we have
4116 * to move the position two files to the right to
4117 * create room for them!
4119 VariantClass newVariant;
4120 switch(gameInfo.boardWidth) { // base guess on board width
4121 case 9: newVariant = VariantShogi; break;
4122 case 10: newVariant = VariantGreat; break;
4123 default: newVariant = VariantCrazyhouse; break;
4125 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4126 /* Get a move list just to see the header, which
4127 will tell us whether this is really bug or zh */
4128 if (ics_getting_history == H_FALSE) {
4129 ics_getting_history = H_REQUESTED;
4130 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4134 new_piece[0] = NULLCHAR;
4135 sscanf(parse, "game %d white [%s black [%s <- %s",
4136 &gamenum, white_holding, black_holding,
4138 white_holding[strlen(white_holding)-1] = NULLCHAR;
4139 black_holding[strlen(black_holding)-1] = NULLCHAR;
4140 /* [HGM] copy holdings to board holdings area */
4141 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4142 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4143 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4145 if (appData.zippyPlay && first.initDone) {
4146 ZippyHoldings(white_holding, black_holding,
4150 if (tinyLayout || smallLayout) {
4151 char wh[16], bh[16];
4152 PackHolding(wh, white_holding);
4153 PackHolding(bh, black_holding);
4154 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4155 gameInfo.white, gameInfo.black);
4157 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4158 gameInfo.white, white_holding, _("vs."),
4159 gameInfo.black, black_holding);
4161 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4162 DrawPosition(FALSE, boards[currentMove]);
4164 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4165 sscanf(parse, "game %d white [%s black [%s <- %s",
4166 &gamenum, white_holding, black_holding,
4168 white_holding[strlen(white_holding)-1] = NULLCHAR;
4169 black_holding[strlen(black_holding)-1] = NULLCHAR;
4170 /* [HGM] copy holdings to partner-board holdings area */
4171 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4172 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4173 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4174 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4175 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4178 /* Suppress following prompt */
4179 if (looking_at(buf, &i, "*% ")) {
4180 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4181 savingComment = FALSE;
4189 i++; /* skip unparsed character and loop back */
4192 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4193 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4194 // SendToPlayer(&buf[next_out], i - next_out);
4195 started != STARTED_HOLDINGS && leftover_start > next_out) {
4196 SendToPlayer(&buf[next_out], leftover_start - next_out);
4200 leftover_len = buf_len - leftover_start;
4201 /* if buffer ends with something we couldn't parse,
4202 reparse it after appending the next read */
4204 } else if (count == 0) {
4205 RemoveInputSource(isr);
4206 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4208 DisplayFatalError(_("Error reading from ICS"), error, 1);
4213 /* Board style 12 looks like this:
4215 <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4217 * The "<12> " is stripped before it gets to this routine. The two
4218 * trailing 0's (flip state and clock ticking) are later addition, and
4219 * some chess servers may not have them, or may have only the first.
4220 * Additional trailing fields may be added in the future.
4223 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4225 #define RELATION_OBSERVING_PLAYED 0
4226 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4227 #define RELATION_PLAYING_MYMOVE 1
4228 #define RELATION_PLAYING_NOTMYMOVE -1
4229 #define RELATION_EXAMINING 2
4230 #define RELATION_ISOLATED_BOARD -3
4231 #define RELATION_STARTING_POSITION -4 /* FICS only */
4234 ParseBoard12 (char *string)
4238 char *bookHit = NULL; // [HGM] book
4240 GameMode newGameMode;
4241 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4242 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4243 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4244 char to_play, board_chars[200];
4245 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4246 char black[32], white[32];
4248 int prevMove = currentMove;
4251 int fromX, fromY, toX, toY;
4253 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4254 Boolean weird = FALSE, reqFlag = FALSE;
4256 fromX = fromY = toX = toY = -1;
4260 if (appData.debugMode)
4261 fprintf(debugFP, "Parsing board: %s\n", string);
4263 move_str[0] = NULLCHAR;
4264 elapsed_time[0] = NULLCHAR;
4265 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4267 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4268 if(string[i] == ' ') { ranks++; files = 0; }
4270 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4273 for(j = 0; j <i; j++) board_chars[j] = string[j];
4274 board_chars[i] = '\0';
4277 n = sscanf(string, PATTERN, &to_play, &double_push,
4278 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4279 &gamenum, white, black, &relation, &basetime, &increment,
4280 &white_stren, &black_stren, &white_time, &black_time,
4281 &moveNum, str, elapsed_time, move_str, &ics_flip,
4285 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4286 DisplayError(str, 0);
4290 /* Convert the move number to internal form */
4291 moveNum = (moveNum - 1) * 2;
4292 if (to_play == 'B') moveNum++;
4293 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4294 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4300 case RELATION_OBSERVING_PLAYED:
4301 case RELATION_OBSERVING_STATIC:
4302 if (gamenum == -1) {
4303 /* Old ICC buglet */
4304 relation = RELATION_OBSERVING_STATIC;
4306 newGameMode = IcsObserving;
4308 case RELATION_PLAYING_MYMOVE:
4309 case RELATION_PLAYING_NOTMYMOVE:
4311 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4312 IcsPlayingWhite : IcsPlayingBlack;
4313 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4315 case RELATION_EXAMINING:
4316 newGameMode = IcsExamining;
4318 case RELATION_ISOLATED_BOARD:
4320 /* Just display this board. If user was doing something else,
4321 we will forget about it until the next board comes. */
4322 newGameMode = IcsIdle;
4324 case RELATION_STARTING_POSITION:
4325 newGameMode = gameMode;
4329 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4330 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4331 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4332 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4333 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4334 static int lastBgGame = -1;
4336 for (k = 0; k < ranks; k++) {
4337 for (j = 0; j < files; j++)
4338 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4339 if(gameInfo.holdingsWidth > 1) {
4340 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4341 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4344 CopyBoard(partnerBoard, board);
4345 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4346 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4347 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4348 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4349 if(toSqr = strchr(str, '-')) {
4350 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4351 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4352 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4353 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4354 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4355 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4357 DisplayWhiteClock(white_time*fac, to_play == 'W');
4358 DisplayBlackClock(black_time*fac, to_play != 'W');
4359 activePartner = to_play;
4360 if(gamenum != lastBgGame) {
4362 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4365 lastBgGame = gamenum;
4366 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4367 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4368 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4369 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4370 if(!twoBoards) DisplayMessage(partnerStatus, "");
4371 partnerBoardValid = TRUE;
4375 if(appData.dualBoard && appData.bgObserve) {
4376 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4377 SendToICS(ics_prefix), SendToICS("pobserve\n");
4378 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4380 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4385 /* Modify behavior for initial board display on move listing
4388 switch (ics_getting_history) {
4392 case H_GOT_REQ_HEADER:
4393 case H_GOT_UNREQ_HEADER:
4394 /* This is the initial position of the current game */
4395 gamenum = ics_gamenum;
4396 moveNum = 0; /* old ICS bug workaround */
4397 if (to_play == 'B') {
4398 startedFromSetupPosition = TRUE;
4399 blackPlaysFirst = TRUE;
4401 if (forwardMostMove == 0) forwardMostMove = 1;
4402 if (backwardMostMove == 0) backwardMostMove = 1;
4403 if (currentMove == 0) currentMove = 1;
4405 newGameMode = gameMode;
4406 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4408 case H_GOT_UNWANTED_HEADER:
4409 /* This is an initial board that we don't want */
4411 case H_GETTING_MOVES:
4412 /* Should not happen */
4413 DisplayError(_("Error gathering move list: extra board"), 0);
4414 ics_getting_history = H_FALSE;
4418 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4419 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4420 weird && (int)gameInfo.variant < (int)VariantShogi) {
4421 /* [HGM] We seem to have switched variant unexpectedly
4422 * Try to guess new variant from board size
4424 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4425 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4426 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4427 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4428 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4429 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4430 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4431 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4432 /* Get a move list just to see the header, which
4433 will tell us whether this is really bug or zh */
4434 if (ics_getting_history == H_FALSE) {
4435 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4436 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4441 /* Take action if this is the first board of a new game, or of a
4442 different game than is currently being displayed. */
4443 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4444 relation == RELATION_ISOLATED_BOARD) {
4446 /* Forget the old game and get the history (if any) of the new one */
4447 if (gameMode != BeginningOfGame) {
4451 if (appData.autoRaiseBoard) BoardToTop();
4453 if (gamenum == -1) {
4454 newGameMode = IcsIdle;
4455 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4456 appData.getMoveList && !reqFlag) {
4457 /* Need to get game history */
4458 ics_getting_history = H_REQUESTED;
4459 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4463 /* Initially flip the board to have black on the bottom if playing
4464 black or if the ICS flip flag is set, but let the user change
4465 it with the Flip View button. */
4466 flipView = appData.autoFlipView ?
4467 (newGameMode == IcsPlayingBlack) || ics_flip :
4470 /* Done with values from previous mode; copy in new ones */
4471 gameMode = newGameMode;
4473 ics_gamenum = gamenum;
4474 if (gamenum == gs_gamenum) {
4475 int klen = strlen(gs_kind);
4476 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4477 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4478 gameInfo.event = StrSave(str);
4480 gameInfo.event = StrSave("ICS game");
4482 gameInfo.site = StrSave(appData.icsHost);
4483 gameInfo.date = PGNDate();
4484 gameInfo.round = StrSave("-");
4485 gameInfo.white = StrSave(white);
4486 gameInfo.black = StrSave(black);
4487 timeControl = basetime * 60 * 1000;
4489 timeIncrement = increment * 1000;
4490 movesPerSession = 0;
4491 gameInfo.timeControl = TimeControlTagValue();
4492 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4493 if (appData.debugMode) {
4494 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4495 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4496 setbuf(debugFP, NULL);
4499 gameInfo.outOfBook = NULL;
4501 /* Do we have the ratings? */
4502 if (strcmp(player1Name, white) == 0 &&
4503 strcmp(player2Name, black) == 0) {
4504 if (appData.debugMode)
4505 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4506 player1Rating, player2Rating);
4507 gameInfo.whiteRating = player1Rating;
4508 gameInfo.blackRating = player2Rating;
4509 } else if (strcmp(player2Name, white) == 0 &&
4510 strcmp(player1Name, black) == 0) {
4511 if (appData.debugMode)
4512 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4513 player2Rating, player1Rating);
4514 gameInfo.whiteRating = player2Rating;
4515 gameInfo.blackRating = player1Rating;
4517 player1Name[0] = player2Name[0] = NULLCHAR;
4519 /* Silence shouts if requested */
4520 if (appData.quietPlay &&
4521 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4522 SendToICS(ics_prefix);
4523 SendToICS("set shout 0\n");
4527 /* Deal with midgame name changes */
4529 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4530 if (gameInfo.white) free(gameInfo.white);
4531 gameInfo.white = StrSave(white);
4533 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4534 if (gameInfo.black) free(gameInfo.black);
4535 gameInfo.black = StrSave(black);
4539 /* Throw away game result if anything actually changes in examine mode */
4540 if (gameMode == IcsExamining && !newGame) {
4541 gameInfo.result = GameUnfinished;
4542 if (gameInfo.resultDetails != NULL) {
4543 free(gameInfo.resultDetails);
4544 gameInfo.resultDetails = NULL;
4548 /* In pausing && IcsExamining mode, we ignore boards coming
4549 in if they are in a different variation than we are. */
4550 if (pauseExamInvalid) return;
4551 if (pausing && gameMode == IcsExamining) {
4552 if (moveNum <= pauseExamForwardMostMove) {
4553 pauseExamInvalid = TRUE;
4554 forwardMostMove = pauseExamForwardMostMove;
4559 if (appData.debugMode) {
4560 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4562 /* Parse the board */
4563 for (k = 0; k < ranks; k++) {
4564 for (j = 0; j < files; j++)
4565 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4566 if(gameInfo.holdingsWidth > 1) {
4567 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4568 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4571 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4572 board[5][BOARD_RGHT+1] = WhiteAngel;
4573 board[6][BOARD_RGHT+1] = WhiteMarshall;
4574 board[1][0] = BlackMarshall;
4575 board[2][0] = BlackAngel;
4576 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4578 CopyBoard(boards[moveNum], board);
4579 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4581 startedFromSetupPosition =
4582 !CompareBoards(board, initialPosition);
4583 if(startedFromSetupPosition)
4584 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4587 /* [HGM] Set castling rights. Take the outermost Rooks,
4588 to make it also work for FRC opening positions. Note that board12
4589 is really defective for later FRC positions, as it has no way to
4590 indicate which Rook can castle if they are on the same side of King.
4591 For the initial position we grant rights to the outermost Rooks,
4592 and remember thos rights, and we then copy them on positions
4593 later in an FRC game. This means WB might not recognize castlings with
4594 Rooks that have moved back to their original position as illegal,
4595 but in ICS mode that is not its job anyway.
4597 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4598 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4600 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4601 if(board[0][i] == WhiteRook) j = i;
4602 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4603 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4604 if(board[0][i] == WhiteRook) j = i;
4605 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4606 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4607 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4608 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4609 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4610 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4611 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4613 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4614 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4615 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4616 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4617 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4618 if(board[BOARD_HEIGHT-1][k] == bKing)
4619 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4620 if(gameInfo.variant == VariantTwoKings) {
4621 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4622 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4623 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4626 r = boards[moveNum][CASTLING][0] = initialRights[0];
4627 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4628 r = boards[moveNum][CASTLING][1] = initialRights[1];
4629 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4630 r = boards[moveNum][CASTLING][3] = initialRights[3];
4631 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4632 r = boards[moveNum][CASTLING][4] = initialRights[4];
4633 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4634 /* wildcastle kludge: always assume King has rights */
4635 r = boards[moveNum][CASTLING][2] = initialRights[2];
4636 r = boards[moveNum][CASTLING][5] = initialRights[5];
4638 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4639 boards[moveNum][EP_STATUS] = EP_NONE;
4640 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4641 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4642 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4645 if (ics_getting_history == H_GOT_REQ_HEADER ||
4646 ics_getting_history == H_GOT_UNREQ_HEADER) {
4647 /* This was an initial position from a move list, not
4648 the current position */
4652 /* Update currentMove and known move number limits */
4653 newMove = newGame || moveNum > forwardMostMove;
4656 forwardMostMove = backwardMostMove = currentMove = moveNum;
4657 if (gameMode == IcsExamining && moveNum == 0) {
4658 /* Workaround for ICS limitation: we are not told the wild
4659 type when starting to examine a game. But if we ask for
4660 the move list, the move list header will tell us */
4661 ics_getting_history = H_REQUESTED;
4662 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4665 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4666 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4668 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4669 /* [HGM] applied this also to an engine that is silently watching */
4670 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4671 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4672 gameInfo.variant == currentlyInitializedVariant) {
4673 takeback = forwardMostMove - moveNum;
4674 for (i = 0; i < takeback; i++) {
4675 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4676 SendToProgram("undo\n", &first);
4681 forwardMostMove = moveNum;
4682 if (!pausing || currentMove > forwardMostMove)
4683 currentMove = forwardMostMove;
4685 /* New part of history that is not contiguous with old part */
4686 if (pausing && gameMode == IcsExamining) {
4687 pauseExamInvalid = TRUE;
4688 forwardMostMove = pauseExamForwardMostMove;
4691 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4693 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4694 // [HGM] when we will receive the move list we now request, it will be
4695 // fed to the engine from the first move on. So if the engine is not
4696 // in the initial position now, bring it there.
4697 InitChessProgram(&first, 0);
4700 ics_getting_history = H_REQUESTED;
4701 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4704 forwardMostMove = backwardMostMove = currentMove = moveNum;
4707 /* Update the clocks */
4708 if (strchr(elapsed_time, '.')) {
4710 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4711 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4713 /* Time is in seconds */
4714 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4715 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4720 if (appData.zippyPlay && newGame &&
4721 gameMode != IcsObserving && gameMode != IcsIdle &&
4722 gameMode != IcsExamining)
4723 ZippyFirstBoard(moveNum, basetime, increment);
4726 /* Put the move on the move list, first converting
4727 to canonical algebraic form. */
4729 if (appData.debugMode) {
4730 int f = forwardMostMove;
4731 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4732 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4733 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4734 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4735 fprintf(debugFP, "moveNum = %d\n", moveNum);
4736 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4737 setbuf(debugFP, NULL);
4739 if (moveNum <= backwardMostMove) {
4740 /* We don't know what the board looked like before
4742 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4743 strcat(parseList[moveNum - 1], " ");
4744 strcat(parseList[moveNum - 1], elapsed_time);
4745 moveList[moveNum - 1][0] = NULLCHAR;
4746 } else if (strcmp(move_str, "none") == 0) {
4747 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4748 /* Again, we don't know what the board looked like;
4749 this is really the start of the game. */
4750 parseList[moveNum - 1][0] = NULLCHAR;
4751 moveList[moveNum - 1][0] = NULLCHAR;
4752 backwardMostMove = moveNum;
4753 startedFromSetupPosition = TRUE;
4754 fromX = fromY = toX = toY = -1;
4756 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4757 // So we parse the long-algebraic move string in stead of the SAN move
4758 int valid; char buf[MSG_SIZ], *prom;
4760 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4761 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4762 // str looks something like "Q/a1-a2"; kill the slash
4764 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4765 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4766 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4767 strcat(buf, prom); // long move lacks promo specification!
4768 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4769 if(appData.debugMode)
4770 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4771 safeStrCpy(move_str, buf, MSG_SIZ);
4773 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4774 &fromX, &fromY, &toX, &toY, &promoChar)
4775 || ParseOneMove(buf, moveNum - 1, &moveType,
4776 &fromX, &fromY, &toX, &toY, &promoChar);
4777 // end of long SAN patch
4779 (void) CoordsToAlgebraic(boards[moveNum - 1],
4780 PosFlags(moveNum - 1),
4781 fromY, fromX, toY, toX, promoChar,
4782 parseList[moveNum-1]);
4783 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4789 if(gameInfo.variant != VariantShogi)
4790 strcat(parseList[moveNum - 1], "+");
4793 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4794 strcat(parseList[moveNum - 1], "#");
4797 strcat(parseList[moveNum - 1], " ");
4798 strcat(parseList[moveNum - 1], elapsed_time);
4799 /* currentMoveString is set as a side-effect of ParseOneMove */
4800 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4801 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4802 strcat(moveList[moveNum - 1], "\n");
4804 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4805 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4806 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4807 ChessSquare old, new = boards[moveNum][k][j];
4808 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4809 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4810 if(old == new) continue;
4811 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4812 else if(new == WhiteWazir || new == BlackWazir) {
4813 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4814 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4815 else boards[moveNum][k][j] = old; // preserve type of Gold
4816 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4817 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4820 /* Move from ICS was illegal!? Punt. */
4821 if (appData.debugMode) {
4822 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4823 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4825 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4826 strcat(parseList[moveNum - 1], " ");
4827 strcat(parseList[moveNum - 1], elapsed_time);
4828 moveList[moveNum - 1][0] = NULLCHAR;
4829 fromX = fromY = toX = toY = -1;
4832 if (appData.debugMode) {
4833 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4834 setbuf(debugFP, NULL);
4838 /* Send move to chess program (BEFORE animating it). */
4839 if (appData.zippyPlay && !newGame && newMove &&
4840 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4842 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4843 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4844 if (moveList[moveNum - 1][0] == NULLCHAR) {
4845 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4847 DisplayError(str, 0);
4849 if (first.sendTime) {
4850 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4852 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4853 if (firstMove && !bookHit) {
4855 if (first.useColors) {
4856 SendToProgram(gameMode == IcsPlayingWhite ?
4858 "black\ngo\n", &first);
4860 SendToProgram("go\n", &first);
4862 first.maybeThinking = TRUE;
4865 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4866 if (moveList[moveNum - 1][0] == NULLCHAR) {
4867 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4868 DisplayError(str, 0);
4870 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4871 SendMoveToProgram(moveNum - 1, &first);
4878 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4879 /* If move comes from a remote source, animate it. If it
4880 isn't remote, it will have already been animated. */
4881 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4882 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4884 if (!pausing && appData.highlightLastMove) {
4885 SetHighlights(fromX, fromY, toX, toY);
4889 /* Start the clocks */
4890 whiteFlag = blackFlag = FALSE;
4891 appData.clockMode = !(basetime == 0 && increment == 0);
4893 ics_clock_paused = TRUE;
4895 } else if (ticking == 1) {
4896 ics_clock_paused = FALSE;
4898 if (gameMode == IcsIdle ||
4899 relation == RELATION_OBSERVING_STATIC ||
4900 relation == RELATION_EXAMINING ||
4902 DisplayBothClocks();
4906 /* Display opponents and material strengths */
4907 if (gameInfo.variant != VariantBughouse &&
4908 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4909 if (tinyLayout || smallLayout) {
4910 if(gameInfo.variant == VariantNormal)
4911 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4912 gameInfo.white, white_stren, gameInfo.black, black_stren,
4913 basetime, increment);
4915 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4916 gameInfo.white, white_stren, gameInfo.black, black_stren,
4917 basetime, increment, (int) gameInfo.variant);
4919 if(gameInfo.variant == VariantNormal)
4920 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4921 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4922 basetime, increment);
4924 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4925 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4926 basetime, increment, VariantName(gameInfo.variant));
4929 if (appData.debugMode) {
4930 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4935 /* Display the board */
4936 if (!pausing && !appData.noGUI) {
4938 if (appData.premove)
4940 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4941 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4942 ClearPremoveHighlights();
4944 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4945 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4946 DrawPosition(j, boards[currentMove]);
4948 DisplayMove(moveNum - 1);
4949 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4950 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4951 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4952 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4956 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4958 if(bookHit) { // [HGM] book: simulate book reply
4959 static char bookMove[MSG_SIZ]; // a bit generous?
4961 programStats.nodes = programStats.depth = programStats.time =
4962 programStats.score = programStats.got_only_move = 0;
4963 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4965 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4966 strcat(bookMove, bookHit);
4967 HandleMachineMove(bookMove, &first);
4976 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4977 ics_getting_history = H_REQUESTED;
4978 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4984 SendToBoth (char *msg)
4985 { // to make it easy to keep two engines in step in dual analysis
4986 SendToProgram(msg, &first);
4987 if(second.analyzing) SendToProgram(msg, &second);
4991 AnalysisPeriodicEvent (int force)
4993 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4994 && !force) || !appData.periodicUpdates)
4997 /* Send . command to Crafty to collect stats */
5000 /* Don't send another until we get a response (this makes
5001 us stop sending to old Crafty's which don't understand
5002 the "." command (sending illegal cmds resets node count & time,
5003 which looks bad)) */
5004 programStats.ok_to_send = 0;
5008 ics_update_width (int new_width)
5010 ics_printf("set width %d\n", new_width);
5014 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5018 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5019 // null move in variant where engine does not understand it (for analysis purposes)
5020 SendBoard(cps, moveNum + 1); // send position after move in stead.
5023 if (cps->useUsermove) {
5024 SendToProgram("usermove ", cps);
5028 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5029 int len = space - parseList[moveNum];
5030 memcpy(buf, parseList[moveNum], len);
5032 buf[len] = NULLCHAR;
5034 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5036 SendToProgram(buf, cps);
5038 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5039 AlphaRank(moveList[moveNum], 4);
5040 SendToProgram(moveList[moveNum], cps);
5041 AlphaRank(moveList[moveNum], 4); // and back
5043 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5044 * the engine. It would be nice to have a better way to identify castle
5046 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5047 && cps->useOOCastle) {
5048 int fromX = moveList[moveNum][0] - AAA;
5049 int fromY = moveList[moveNum][1] - ONE;
5050 int toX = moveList[moveNum][2] - AAA;
5051 int toY = moveList[moveNum][3] - ONE;
5052 if((boards[moveNum][fromY][fromX] == WhiteKing
5053 && boards[moveNum][toY][toX] == WhiteRook)
5054 || (boards[moveNum][fromY][fromX] == BlackKing
5055 && boards[moveNum][toY][toX] == BlackRook)) {
5056 if(toX > fromX) SendToProgram("O-O\n", cps);
5057 else SendToProgram("O-O-O\n", cps);
5059 else SendToProgram(moveList[moveNum], cps);
5061 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5062 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5063 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5064 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5065 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5066 } else if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5067 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5068 moveList[moveNum][5], moveList[moveNum][6] - '0',
5069 moveList[moveNum][5], moveList[moveNum][6] - '0',
5070 moveList[moveNum][2], moveList[moveNum][3] - '0');
5072 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5073 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5074 SendToProgram(buf, cps);
5076 else SendToProgram(moveList[moveNum], cps);
5077 /* End of additions by Tord */
5080 /* [HGM] setting up the opening has brought engine in force mode! */
5081 /* Send 'go' if we are in a mode where machine should play. */
5082 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5083 (gameMode == TwoMachinesPlay ||
5085 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5087 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5088 SendToProgram("go\n", cps);
5089 if (appData.debugMode) {
5090 fprintf(debugFP, "(extra)\n");
5093 setboardSpoiledMachineBlack = 0;
5097 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5099 char user_move[MSG_SIZ];
5102 if(gameInfo.variant == VariantSChess && promoChar) {
5103 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5104 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5105 } else suffix[0] = NULLCHAR;
5109 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5110 (int)moveType, fromX, fromY, toX, toY);
5111 DisplayError(user_move + strlen("say "), 0);
5113 case WhiteKingSideCastle:
5114 case BlackKingSideCastle:
5115 case WhiteQueenSideCastleWild:
5116 case BlackQueenSideCastleWild:
5118 case WhiteHSideCastleFR:
5119 case BlackHSideCastleFR:
5121 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5123 case WhiteQueenSideCastle:
5124 case BlackQueenSideCastle:
5125 case WhiteKingSideCastleWild:
5126 case BlackKingSideCastleWild:
5128 case WhiteASideCastleFR:
5129 case BlackASideCastleFR:
5131 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5133 case WhiteNonPromotion:
5134 case BlackNonPromotion:
5135 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5137 case WhitePromotion:
5138 case BlackPromotion:
5139 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5140 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5141 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5142 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5143 PieceToChar(WhiteFerz));
5144 else if(gameInfo.variant == VariantGreat)
5145 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5146 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5147 PieceToChar(WhiteMan));
5149 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5150 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5156 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5157 ToUpper(PieceToChar((ChessSquare) fromX)),
5158 AAA + toX, ONE + toY);
5160 case IllegalMove: /* could be a variant we don't quite understand */
5161 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5163 case WhiteCapturesEnPassant:
5164 case BlackCapturesEnPassant:
5165 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5166 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5169 SendToICS(user_move);
5170 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5171 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5176 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5177 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5178 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5179 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5180 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5183 if(gameMode != IcsExamining) { // is this ever not the case?
5184 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5186 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5187 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5188 } else { // on FICS we must first go to general examine mode
5189 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5191 if(gameInfo.variant != VariantNormal) {
5192 // try figure out wild number, as xboard names are not always valid on ICS
5193 for(i=1; i<=36; i++) {
5194 snprintf(buf, MSG_SIZ, "wild/%d", i);
5195 if(StringToVariant(buf) == gameInfo.variant) break;
5197 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5198 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5199 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5200 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5201 SendToICS(ics_prefix);
5203 if(startedFromSetupPosition || backwardMostMove != 0) {
5204 fen = PositionToFEN(backwardMostMove, NULL, 1);
5205 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5206 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5208 } else { // FICS: everything has to set by separate bsetup commands
5209 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5210 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5212 if(!WhiteOnMove(backwardMostMove)) {
5213 SendToICS("bsetup tomove black\n");
5215 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5216 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5218 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5219 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5221 i = boards[backwardMostMove][EP_STATUS];
5222 if(i >= 0) { // set e.p.
5223 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5229 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5230 SendToICS("bsetup done\n"); // switch to normal examining.
5232 for(i = backwardMostMove; i<last; i++) {
5234 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5235 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5236 int len = strlen(moveList[i]);
5237 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5238 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5242 SendToICS(ics_prefix);
5243 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5246 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5249 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5251 if (rf == DROP_RANK) {
5252 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5253 sprintf(move, "%c@%c%c\n",
5254 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5256 if (promoChar == 'x' || promoChar == NULLCHAR) {
5257 sprintf(move, "%c%c%c%c\n",
5258 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5259 if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5261 sprintf(move, "%c%c%c%c%c\n",
5262 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5268 ProcessICSInitScript (FILE *f)
5272 while (fgets(buf, MSG_SIZ, f)) {
5273 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5280 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5281 static ClickType lastClickType;
5286 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5287 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5288 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5289 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5290 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5291 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5294 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5295 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5296 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5297 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5298 if(!step) step = -1;
5299 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5300 appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion) ||
5301 IS_SHOGI(gameInfo.variant) && promoSweep != CHUPROMOTED last && last != CHUPROMOTED promoSweep && last != promoSweep);
5303 int victim = boards[currentMove][toY][toX];
5304 boards[currentMove][toY][toX] = promoSweep;
5305 DrawPosition(FALSE, boards[currentMove]);
5306 boards[currentMove][toY][toX] = victim;
5308 ChangeDragPiece(promoSweep);
5312 PromoScroll (int x, int y)
5316 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5317 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5318 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5319 if(!step) return FALSE;
5320 lastX = x; lastY = y;
5321 if((promoSweep < BlackPawn) == flipView) step = -step;
5322 if(step > 0) selectFlag = 1;
5323 if(!selectFlag) Sweep(step);
5328 NextPiece (int step)
5330 ChessSquare piece = boards[currentMove][toY][toX];
5333 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5334 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5335 if(!step) step = -1;
5336 } while(PieceToChar(pieceSweep) == '.');
5337 boards[currentMove][toY][toX] = pieceSweep;
5338 DrawPosition(FALSE, boards[currentMove]);
5339 boards[currentMove][toY][toX] = piece;
5341 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5343 AlphaRank (char *move, int n)
5345 // char *p = move, c; int x, y;
5347 if (appData.debugMode) {
5348 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5352 move[2]>='0' && move[2]<='9' &&
5353 move[3]>='a' && move[3]<='x' ) {
5355 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5356 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5358 if(move[0]>='0' && move[0]<='9' &&
5359 move[1]>='a' && move[1]<='x' &&
5360 move[2]>='0' && move[2]<='9' &&
5361 move[3]>='a' && move[3]<='x' ) {
5362 /* input move, Shogi -> normal */
5363 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5364 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5365 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5366 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5369 move[3]>='0' && move[3]<='9' &&
5370 move[2]>='a' && move[2]<='x' ) {
5372 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5373 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5376 move[0]>='a' && move[0]<='x' &&
5377 move[3]>='0' && move[3]<='9' &&
5378 move[2]>='a' && move[2]<='x' ) {
5379 /* output move, normal -> Shogi */
5380 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5381 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5382 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5383 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5384 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5386 if (appData.debugMode) {
5387 fprintf(debugFP, " out = '%s'\n", move);
5391 char yy_textstr[8000];
5393 /* Parser for moves from gnuchess, ICS, or user typein box */
5395 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5397 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5399 switch (*moveType) {
5400 case WhitePromotion:
5401 case BlackPromotion:
5402 case WhiteNonPromotion:
5403 case BlackNonPromotion:
5406 case WhiteCapturesEnPassant:
5407 case BlackCapturesEnPassant:
5408 case WhiteKingSideCastle:
5409 case WhiteQueenSideCastle:
5410 case BlackKingSideCastle:
5411 case BlackQueenSideCastle:
5412 case WhiteKingSideCastleWild:
5413 case WhiteQueenSideCastleWild:
5414 case BlackKingSideCastleWild:
5415 case BlackQueenSideCastleWild:
5416 /* Code added by Tord: */
5417 case WhiteHSideCastleFR:
5418 case WhiteASideCastleFR:
5419 case BlackHSideCastleFR:
5420 case BlackASideCastleFR:
5421 /* End of code added by Tord */
5422 case IllegalMove: /* bug or odd chess variant */
5423 *fromX = currentMoveString[0] - AAA;
5424 *fromY = currentMoveString[1] - ONE;
5425 *toX = currentMoveString[2] - AAA;
5426 *toY = currentMoveString[3] - ONE;
5427 *promoChar = currentMoveString[4];
5428 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5429 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5430 if (appData.debugMode) {
5431 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5433 *fromX = *fromY = *toX = *toY = 0;
5436 if (appData.testLegality) {
5437 return (*moveType != IllegalMove);
5439 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5440 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5445 *fromX = *moveType == WhiteDrop ?
5446 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5447 (int) CharToPiece(ToLower(currentMoveString[0]));
5449 *toX = currentMoveString[2] - AAA;
5450 *toY = currentMoveString[3] - ONE;
5451 *promoChar = NULLCHAR;
5455 case ImpossibleMove:
5465 if (appData.debugMode) {
5466 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5469 *fromX = *fromY = *toX = *toY = 0;
5470 *promoChar = NULLCHAR;
5475 Boolean pushed = FALSE;
5476 char *lastParseAttempt;
5479 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5480 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5481 int fromX, fromY, toX, toY; char promoChar;
5486 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5487 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5488 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5491 endPV = forwardMostMove;
5493 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5494 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5495 lastParseAttempt = pv;
5496 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5497 if(!valid && nr == 0 &&
5498 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5499 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5500 // Hande case where played move is different from leading PV move
5501 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5502 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5503 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5504 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5505 endPV += 2; // if position different, keep this
5506 moveList[endPV-1][0] = fromX + AAA;
5507 moveList[endPV-1][1] = fromY + ONE;
5508 moveList[endPV-1][2] = toX + AAA;
5509 moveList[endPV-1][3] = toY + ONE;
5510 parseList[endPV-1][0] = NULLCHAR;
5511 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5514 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5515 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5516 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5517 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5518 valid++; // allow comments in PV
5522 if(endPV+1 > framePtr) break; // no space, truncate
5525 CopyBoard(boards[endPV], boards[endPV-1]);
5526 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5527 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5528 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5529 CoordsToAlgebraic(boards[endPV - 1],
5530 PosFlags(endPV - 1),
5531 fromY, fromX, toY, toX, promoChar,
5532 parseList[endPV - 1]);
5534 if(atEnd == 2) return; // used hidden, for PV conversion
5535 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5536 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5537 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5538 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5539 DrawPosition(TRUE, boards[currentMove]);
5543 MultiPV (ChessProgramState *cps)
5544 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5546 for(i=0; i<cps->nrOptions; i++)
5547 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5552 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5555 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5557 int startPV, multi, lineStart, origIndex = index;
5558 char *p, buf2[MSG_SIZ];
5559 ChessProgramState *cps = (pane ? &second : &first);
5561 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5562 lastX = x; lastY = y;
5563 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5564 lineStart = startPV = index;
5565 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5566 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5568 do{ while(buf[index] && buf[index] != '\n') index++;
5569 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5571 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5572 int n = cps->option[multi].value;
5573 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5574 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5575 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5576 cps->option[multi].value = n;
5579 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5580 ExcludeClick(origIndex - lineStart);
5583 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5584 *start = startPV; *end = index-1;
5585 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5592 static char buf[10*MSG_SIZ];
5593 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5595 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5596 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5597 for(i = forwardMostMove; i<endPV; i++){
5598 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5599 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5602 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5603 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5604 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5610 LoadPV (int x, int y)
5611 { // called on right mouse click to load PV
5612 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5613 lastX = x; lastY = y;
5614 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5622 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5623 if(endPV < 0) return;
5624 if(appData.autoCopyPV) CopyFENToClipboard();
5626 if(extendGame && currentMove > forwardMostMove) {
5627 Boolean saveAnimate = appData.animate;
5629 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5630 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5631 } else storedGames--; // abandon shelved tail of original game
5634 forwardMostMove = currentMove;
5635 currentMove = oldFMM;
5636 appData.animate = FALSE;
5637 ToNrEvent(forwardMostMove);
5638 appData.animate = saveAnimate;
5640 currentMove = forwardMostMove;
5641 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5642 ClearPremoveHighlights();
5643 DrawPosition(TRUE, boards[currentMove]);
5647 MovePV (int x, int y, int h)
5648 { // step through PV based on mouse coordinates (called on mouse move)
5649 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5651 // we must somehow check if right button is still down (might be released off board!)
5652 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5653 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5654 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5656 lastX = x; lastY = y;
5658 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5659 if(endPV < 0) return;
5660 if(y < margin) step = 1; else
5661 if(y > h - margin) step = -1;
5662 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5663 currentMove += step;
5664 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5665 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5666 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5667 DrawPosition(FALSE, boards[currentMove]);
5671 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5672 // All positions will have equal probability, but the current method will not provide a unique
5673 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5679 int piecesLeft[(int)BlackPawn];
5680 int seed, nrOfShuffles;
5683 GetPositionNumber ()
5684 { // sets global variable seed
5687 seed = appData.defaultFrcPosition;
5688 if(seed < 0) { // randomize based on time for negative FRC position numbers
5689 for(i=0; i<50; i++) seed += random();
5690 seed = random() ^ random() >> 8 ^ random() << 8;
5691 if(seed<0) seed = -seed;
5696 put (Board board, int pieceType, int rank, int n, int shade)
5697 // put the piece on the (n-1)-th empty squares of the given shade
5701 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5702 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5703 board[rank][i] = (ChessSquare) pieceType;
5704 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5706 piecesLeft[pieceType]--;
5715 AddOnePiece (Board board, int pieceType, int rank, int shade)
5716 // calculate where the next piece goes, (any empty square), and put it there
5720 i = seed % squaresLeft[shade];
5721 nrOfShuffles *= squaresLeft[shade];
5722 seed /= squaresLeft[shade];
5723 put(board, pieceType, rank, i, shade);
5727 AddTwoPieces (Board board, int pieceType, int rank)
5728 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5730 int i, n=squaresLeft[ANY], j=n-1, k;
5732 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5733 i = seed % k; // pick one
5736 while(i >= j) i -= j--;
5737 j = n - 1 - j; i += j;
5738 put(board, pieceType, rank, j, ANY);
5739 put(board, pieceType, rank, i, ANY);
5743 SetUpShuffle (Board board, int number)
5747 GetPositionNumber(); nrOfShuffles = 1;
5749 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5750 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5751 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5753 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5755 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5756 p = (int) board[0][i];
5757 if(p < (int) BlackPawn) piecesLeft[p] ++;
5758 board[0][i] = EmptySquare;
5761 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5762 // shuffles restricted to allow normal castling put KRR first
5763 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5764 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5765 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5766 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5767 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5768 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5769 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5770 put(board, WhiteRook, 0, 0, ANY);
5771 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5774 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5775 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5776 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5777 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5778 while(piecesLeft[p] >= 2) {
5779 AddOnePiece(board, p, 0, LITE);
5780 AddOnePiece(board, p, 0, DARK);
5782 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5785 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5786 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5787 // but we leave King and Rooks for last, to possibly obey FRC restriction
5788 if(p == (int)WhiteRook) continue;
5789 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5790 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5793 // now everything is placed, except perhaps King (Unicorn) and Rooks
5795 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5796 // Last King gets castling rights
5797 while(piecesLeft[(int)WhiteUnicorn]) {
5798 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5799 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5802 while(piecesLeft[(int)WhiteKing]) {
5803 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5804 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5809 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5810 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5813 // Only Rooks can be left; simply place them all
5814 while(piecesLeft[(int)WhiteRook]) {
5815 i = put(board, WhiteRook, 0, 0, ANY);
5816 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5819 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5821 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5824 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5825 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5828 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5832 SetCharTable (char *table, const char * map)
5833 /* [HGM] moved here from winboard.c because of its general usefulness */
5834 /* Basically a safe strcpy that uses the last character as King */
5836 int result = FALSE; int NrPieces;
5838 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5839 && NrPieces >= 12 && !(NrPieces&1)) {
5840 int i; /* [HGM] Accept even length from 12 to 34 */
5842 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5843 for( i=0; i<NrPieces/2-1; i++ ) {
5845 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5847 table[(int) WhiteKing] = map[NrPieces/2-1];
5848 table[(int) BlackKing] = map[NrPieces-1];
5857 Prelude (Board board)
5858 { // [HGM] superchess: random selection of exo-pieces
5859 int i, j, k; ChessSquare p;
5860 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5862 GetPositionNumber(); // use FRC position number
5864 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5865 SetCharTable(pieceToChar, appData.pieceToCharTable);
5866 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5867 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5870 j = seed%4; seed /= 4;
5871 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5872 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5873 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5874 j = seed%3 + (seed%3 >= j); seed /= 3;
5875 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5876 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5877 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5878 j = seed%3; seed /= 3;
5879 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5880 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5881 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5882 j = seed%2 + (seed%2 >= j); seed /= 2;
5883 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5884 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5885 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5886 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5887 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5888 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5889 put(board, exoPieces[0], 0, 0, ANY);
5890 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5894 InitPosition (int redraw)
5896 ChessSquare (* pieces)[BOARD_FILES];
5897 int i, j, pawnRow=1, pieceRows=1, overrule,
5898 oldx = gameInfo.boardWidth,
5899 oldy = gameInfo.boardHeight,
5900 oldh = gameInfo.holdingsWidth;
5903 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5905 /* [AS] Initialize pv info list [HGM] and game status */
5907 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5908 pvInfoList[i].depth = 0;
5909 boards[i][EP_STATUS] = EP_NONE;
5910 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5913 initialRulePlies = 0; /* 50-move counter start */
5915 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5916 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5920 /* [HGM] logic here is completely changed. In stead of full positions */
5921 /* the initialized data only consist of the two backranks. The switch */
5922 /* selects which one we will use, which is than copied to the Board */
5923 /* initialPosition, which for the rest is initialized by Pawns and */
5924 /* empty squares. This initial position is then copied to boards[0], */
5925 /* possibly after shuffling, so that it remains available. */
5927 gameInfo.holdingsWidth = 0; /* default board sizes */
5928 gameInfo.boardWidth = 8;
5929 gameInfo.boardHeight = 8;
5930 gameInfo.holdingsSize = 0;
5931 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5932 for(i=0; i<BOARD_FILES-2; i++)
5933 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5934 initialPosition[EP_STATUS] = EP_NONE;
5935 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5936 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5937 SetCharTable(pieceNickName, appData.pieceNickNames);
5938 else SetCharTable(pieceNickName, "............");
5941 switch (gameInfo.variant) {
5942 case VariantFischeRandom:
5943 shuffleOpenings = TRUE;
5946 case VariantShatranj:
5947 pieces = ShatranjArray;
5948 nrCastlingRights = 0;
5949 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5952 pieces = makrukArray;
5953 nrCastlingRights = 0;
5954 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5957 pieces = aseanArray;
5958 nrCastlingRights = 0;
5959 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5961 case VariantTwoKings:
5962 pieces = twoKingsArray;
5965 pieces = GrandArray;
5966 nrCastlingRights = 0;
5967 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5968 gameInfo.boardWidth = 10;
5969 gameInfo.boardHeight = 10;
5970 gameInfo.holdingsSize = 7;
5972 case VariantCapaRandom:
5973 shuffleOpenings = TRUE;
5974 case VariantCapablanca:
5975 pieces = CapablancaArray;
5976 gameInfo.boardWidth = 10;
5977 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5980 pieces = GothicArray;
5981 gameInfo.boardWidth = 10;
5982 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5985 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5986 gameInfo.holdingsSize = 7;
5987 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5990 pieces = JanusArray;
5991 gameInfo.boardWidth = 10;
5992 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5993 nrCastlingRights = 6;
5994 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5995 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5996 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5997 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5998 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5999 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6002 pieces = FalconArray;
6003 gameInfo.boardWidth = 10;
6004 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6006 case VariantXiangqi:
6007 pieces = XiangqiArray;
6008 gameInfo.boardWidth = 9;
6009 gameInfo.boardHeight = 10;
6010 nrCastlingRights = 0;
6011 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6014 pieces = ShogiArray;
6015 gameInfo.boardWidth = 9;
6016 gameInfo.boardHeight = 9;
6017 gameInfo.holdingsSize = 7;
6018 nrCastlingRights = 0;
6019 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6022 pieces = ChuArray; pieceRows = 3;
6023 gameInfo.boardWidth = 12;
6024 gameInfo.boardHeight = 12;
6025 nrCastlingRights = 0;
6026 SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6027 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6029 case VariantCourier:
6030 pieces = CourierArray;
6031 gameInfo.boardWidth = 12;
6032 nrCastlingRights = 0;
6033 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6035 case VariantKnightmate:
6036 pieces = KnightmateArray;
6037 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6039 case VariantSpartan:
6040 pieces = SpartanArray;
6041 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6045 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6048 pieces = fairyArray;
6049 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6052 pieces = GreatArray;
6053 gameInfo.boardWidth = 10;
6054 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6055 gameInfo.holdingsSize = 8;
6059 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6060 gameInfo.holdingsSize = 8;
6061 startedFromSetupPosition = TRUE;
6063 case VariantCrazyhouse:
6064 case VariantBughouse:
6066 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6067 gameInfo.holdingsSize = 5;
6069 case VariantWildCastle:
6071 /* !!?shuffle with kings guaranteed to be on d or e file */
6072 shuffleOpenings = 1;
6074 case VariantNoCastle:
6076 nrCastlingRights = 0;
6077 /* !!?unconstrained back-rank shuffle */
6078 shuffleOpenings = 1;
6083 if(appData.NrFiles >= 0) {
6084 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6085 gameInfo.boardWidth = appData.NrFiles;
6087 if(appData.NrRanks >= 0) {
6088 gameInfo.boardHeight = appData.NrRanks;
6090 if(appData.holdingsSize >= 0) {
6091 i = appData.holdingsSize;
6092 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6093 gameInfo.holdingsSize = i;
6095 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6096 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6097 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6099 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6100 if(pawnRow < 1) pawnRow = 1;
6101 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6102 if(gameInfo.variant == VariantChu) pawnRow = 3;
6104 /* User pieceToChar list overrules defaults */
6105 if(appData.pieceToCharTable != NULL)
6106 SetCharTable(pieceToChar, appData.pieceToCharTable);
6108 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6110 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6111 s = (ChessSquare) 0; /* account holding counts in guard band */
6112 for( i=0; i<BOARD_HEIGHT; i++ )
6113 initialPosition[i][j] = s;
6115 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6116 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6117 initialPosition[pawnRow][j] = WhitePawn;
6118 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6119 if(gameInfo.variant == VariantXiangqi) {
6121 initialPosition[pawnRow][j] =
6122 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6123 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6124 initialPosition[2][j] = WhiteCannon;
6125 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6129 if(gameInfo.variant == VariantChu) {
6130 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6131 initialPosition[pawnRow+1][j] = WhiteCobra,
6132 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6133 for(i=1; i<pieceRows; i++) {
6134 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6135 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6138 if(gameInfo.variant == VariantGrand) {
6139 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6140 initialPosition[0][j] = WhiteRook;
6141 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6144 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
6146 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6149 initialPosition[1][j] = WhiteBishop;
6150 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6152 initialPosition[1][j] = WhiteRook;
6153 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6156 if( nrCastlingRights == -1) {
6157 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6158 /* This sets default castling rights from none to normal corners */
6159 /* Variants with other castling rights must set them themselves above */
6160 nrCastlingRights = 6;
6162 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6163 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6164 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6165 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6166 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6167 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6170 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6171 if(gameInfo.variant == VariantGreat) { // promotion commoners
6172 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6173 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6174 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6175 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6177 if( gameInfo.variant == VariantSChess ) {
6178 initialPosition[1][0] = BlackMarshall;
6179 initialPosition[2][0] = BlackAngel;
6180 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6181 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6182 initialPosition[1][1] = initialPosition[2][1] =
6183 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6185 if (appData.debugMode) {
6186 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6188 if(shuffleOpenings) {
6189 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6190 startedFromSetupPosition = TRUE;
6192 if(startedFromPositionFile) {
6193 /* [HGM] loadPos: use PositionFile for every new game */
6194 CopyBoard(initialPosition, filePosition);
6195 for(i=0; i<nrCastlingRights; i++)
6196 initialRights[i] = filePosition[CASTLING][i];
6197 startedFromSetupPosition = TRUE;
6200 CopyBoard(boards[0], initialPosition);
6202 if(oldx != gameInfo.boardWidth ||
6203 oldy != gameInfo.boardHeight ||
6204 oldv != gameInfo.variant ||
6205 oldh != gameInfo.holdingsWidth
6207 InitDrawingSizes(-2 ,0);
6209 oldv = gameInfo.variant;
6211 DrawPosition(TRUE, boards[currentMove]);
6215 SendBoard (ChessProgramState *cps, int moveNum)
6217 char message[MSG_SIZ];
6219 if (cps->useSetboard) {
6220 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6221 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6222 SendToProgram(message, cps);
6227 int i, j, left=0, right=BOARD_WIDTH;
6228 /* Kludge to set black to move, avoiding the troublesome and now
6229 * deprecated "black" command.
6231 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6232 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6234 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6236 SendToProgram("edit\n", cps);
6237 SendToProgram("#\n", cps);
6238 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6239 bp = &boards[moveNum][i][left];
6240 for (j = left; j < right; j++, bp++) {
6241 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6242 if ((int) *bp < (int) BlackPawn) {
6243 if(j == BOARD_RGHT+1)
6244 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6245 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6246 if(message[0] == '+' || message[0] == '~') {
6247 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6248 PieceToChar((ChessSquare)(DEMOTED *bp)),
6251 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6252 message[1] = BOARD_RGHT - 1 - j + '1';
6253 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6255 SendToProgram(message, cps);
6260 SendToProgram("c\n", cps);
6261 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6262 bp = &boards[moveNum][i][left];
6263 for (j = left; j < right; j++, bp++) {
6264 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6265 if (((int) *bp != (int) EmptySquare)
6266 && ((int) *bp >= (int) BlackPawn)) {
6267 if(j == BOARD_LEFT-2)
6268 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6269 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6271 if(message[0] == '+' || message[0] == '~') {
6272 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6273 PieceToChar((ChessSquare)(DEMOTED *bp)),
6276 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6277 message[1] = BOARD_RGHT - 1 - j + '1';
6278 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6280 SendToProgram(message, cps);
6285 SendToProgram(".\n", cps);
6287 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6290 char exclusionHeader[MSG_SIZ];
6291 int exCnt, excludePtr;
6292 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6293 static Exclusion excluTab[200];
6294 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6300 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6301 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6307 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6308 excludePtr = 24; exCnt = 0;
6313 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6314 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6315 char buf[2*MOVE_LEN], *p;
6316 Exclusion *e = excluTab;
6318 for(i=0; i<exCnt; i++)
6319 if(e[i].ff == fromX && e[i].fr == fromY &&
6320 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6321 if(i == exCnt) { // was not in exclude list; add it
6322 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6323 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6324 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6327 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6328 excludePtr++; e[i].mark = excludePtr++;
6329 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6332 exclusionHeader[e[i].mark] = state;
6336 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6337 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6341 if((signed char)promoChar == -1) { // kludge to indicate best move
6342 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6343 return 1; // if unparsable, abort
6345 // update exclusion map (resolving toggle by consulting existing state)
6346 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6348 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6349 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6350 excludeMap[k] |= 1<<j;
6351 else excludeMap[k] &= ~(1<<j);
6353 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6355 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6356 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6358 return (state == '+');
6362 ExcludeClick (int index)
6365 Exclusion *e = excluTab;
6366 if(index < 25) { // none, best or tail clicked
6367 if(index < 13) { // none: include all
6368 WriteMap(0); // clear map
6369 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6370 SendToBoth("include all\n"); // and inform engine
6371 } else if(index > 18) { // tail
6372 if(exclusionHeader[19] == '-') { // tail was excluded
6373 SendToBoth("include all\n");
6374 WriteMap(0); // clear map completely
6375 // now re-exclude selected moves
6376 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6377 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6378 } else { // tail was included or in mixed state
6379 SendToBoth("exclude all\n");
6380 WriteMap(0xFF); // fill map completely
6381 // now re-include selected moves
6382 j = 0; // count them
6383 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6384 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6385 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6388 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6391 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6392 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6393 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6400 DefaultPromoChoice (int white)
6403 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6404 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6405 result = WhiteFerz; // no choice
6406 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6407 result= WhiteKing; // in Suicide Q is the last thing we want
6408 else if(gameInfo.variant == VariantSpartan)
6409 result = white ? WhiteQueen : WhiteAngel;
6410 else result = WhiteQueen;
6411 if(!white) result = WHITE_TO_BLACK result;
6415 static int autoQueen; // [HGM] oneclick
6418 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6420 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6421 /* [HGM] add Shogi promotions */
6422 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6427 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6428 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6430 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6431 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6434 piece = boards[currentMove][fromY][fromX];
6435 if(gameInfo.variant == VariantChu) {
6436 int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6437 promotionZoneSize = BOARD_HEIGHT/3;
6438 highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6439 } else if(gameInfo.variant == VariantShogi) {
6440 promotionZoneSize = BOARD_HEIGHT/3;
6441 highestPromotingPiece = (int)WhiteAlfil;
6442 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6443 promotionZoneSize = 3;
6446 // Treat Lance as Pawn when it is not representing Amazon
6447 if(gameInfo.variant != VariantSuper) {
6448 if(piece == WhiteLance) piece = WhitePawn; else
6449 if(piece == BlackLance) piece = BlackPawn;
6452 // next weed out all moves that do not touch the promotion zone at all
6453 if((int)piece >= BlackPawn) {
6454 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6456 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6458 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6459 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6462 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6464 // weed out mandatory Shogi promotions
6465 if(gameInfo.variant == VariantShogi) {
6466 if(piece >= BlackPawn) {
6467 if(toY == 0 && piece == BlackPawn ||
6468 toY == 0 && piece == BlackQueen ||
6469 toY <= 1 && piece == BlackKnight) {
6474 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6475 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6476 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6483 // weed out obviously illegal Pawn moves
6484 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6485 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6486 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6487 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6488 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6489 // note we are not allowed to test for valid (non-)capture, due to premove
6492 // we either have a choice what to promote to, or (in Shogi) whether to promote
6493 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6494 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6495 *promoChoice = PieceToChar(BlackFerz); // no choice
6498 // no sense asking what we must promote to if it is going to explode...
6499 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6500 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6503 // give caller the default choice even if we will not make it
6504 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6505 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6506 if( sweepSelect && gameInfo.variant != VariantGreat
6507 && gameInfo.variant != VariantGrand
6508 && gameInfo.variant != VariantSuper) return FALSE;
6509 if(autoQueen) return FALSE; // predetermined
6511 // suppress promotion popup on illegal moves that are not premoves
6512 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6513 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6514 if(appData.testLegality && !premove) {
6515 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6516 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) ? '+' : NULLCHAR);
6517 if(moveType != WhitePromotion && moveType != BlackPromotion)
6525 InPalace (int row, int column)
6526 { /* [HGM] for Xiangqi */
6527 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6528 column < (BOARD_WIDTH + 4)/2 &&
6529 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6534 PieceForSquare (int x, int y)
6536 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6539 return boards[currentMove][y][x];
6543 OKToStartUserMove (int x, int y)
6545 ChessSquare from_piece;
6548 if (matchMode) return FALSE;
6549 if (gameMode == EditPosition) return TRUE;
6551 if (x >= 0 && y >= 0)
6552 from_piece = boards[currentMove][y][x];
6554 from_piece = EmptySquare;
6556 if (from_piece == EmptySquare) return FALSE;
6558 white_piece = (int)from_piece >= (int)WhitePawn &&
6559 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6563 case TwoMachinesPlay:
6571 case MachinePlaysWhite:
6572 case IcsPlayingBlack:
6573 if (appData.zippyPlay) return FALSE;
6575 DisplayMoveError(_("You are playing Black"));
6580 case MachinePlaysBlack:
6581 case IcsPlayingWhite:
6582 if (appData.zippyPlay) return FALSE;
6584 DisplayMoveError(_("You are playing White"));
6589 case PlayFromGameFile:
6590 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6592 if (!white_piece && WhiteOnMove(currentMove)) {
6593 DisplayMoveError(_("It is White's turn"));
6596 if (white_piece && !WhiteOnMove(currentMove)) {
6597 DisplayMoveError(_("It is Black's turn"));
6600 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6601 /* Editing correspondence game history */
6602 /* Could disallow this or prompt for confirmation */
6607 case BeginningOfGame:
6608 if (appData.icsActive) return FALSE;
6609 if (!appData.noChessProgram) {
6611 DisplayMoveError(_("You are playing White"));
6618 if (!white_piece && WhiteOnMove(currentMove)) {
6619 DisplayMoveError(_("It is White's turn"));
6622 if (white_piece && !WhiteOnMove(currentMove)) {
6623 DisplayMoveError(_("It is Black's turn"));
6632 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6633 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6634 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6635 && gameMode != AnalyzeFile && gameMode != Training) {
6636 DisplayMoveError(_("Displayed position is not current"));
6643 OnlyMove (int *x, int *y, Boolean captures)
6645 DisambiguateClosure cl;
6646 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6648 case MachinePlaysBlack:
6649 case IcsPlayingWhite:
6650 case BeginningOfGame:
6651 if(!WhiteOnMove(currentMove)) return FALSE;
6653 case MachinePlaysWhite:
6654 case IcsPlayingBlack:
6655 if(WhiteOnMove(currentMove)) return FALSE;
6662 cl.pieceIn = EmptySquare;
6667 cl.promoCharIn = NULLCHAR;
6668 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6669 if( cl.kind == NormalMove ||
6670 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6671 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6672 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6679 if(cl.kind != ImpossibleMove) return FALSE;
6680 cl.pieceIn = EmptySquare;
6685 cl.promoCharIn = NULLCHAR;
6686 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6687 if( cl.kind == NormalMove ||
6688 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6689 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6690 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6695 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6701 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6702 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6703 int lastLoadGameUseList = FALSE;
6704 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6705 ChessMove lastLoadGameStart = EndOfFile;
6709 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6713 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6715 /* Check if the user is playing in turn. This is complicated because we
6716 let the user "pick up" a piece before it is his turn. So the piece he
6717 tried to pick up may have been captured by the time he puts it down!
6718 Therefore we use the color the user is supposed to be playing in this
6719 test, not the color of the piece that is currently on the starting
6720 square---except in EditGame mode, where the user is playing both
6721 sides; fortunately there the capture race can't happen. (It can
6722 now happen in IcsExamining mode, but that's just too bad. The user
6723 will get a somewhat confusing message in that case.)
6728 case TwoMachinesPlay:
6732 /* We switched into a game mode where moves are not accepted,
6733 perhaps while the mouse button was down. */
6736 case MachinePlaysWhite:
6737 /* User is moving for Black */
6738 if (WhiteOnMove(currentMove)) {
6739 DisplayMoveError(_("It is White's turn"));
6744 case MachinePlaysBlack:
6745 /* User is moving for White */
6746 if (!WhiteOnMove(currentMove)) {
6747 DisplayMoveError(_("It is Black's turn"));
6752 case PlayFromGameFile:
6753 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6756 case BeginningOfGame:
6759 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6760 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6761 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6762 /* User is moving for Black */
6763 if (WhiteOnMove(currentMove)) {
6764 DisplayMoveError(_("It is White's turn"));
6768 /* User is moving for White */
6769 if (!WhiteOnMove(currentMove)) {
6770 DisplayMoveError(_("It is Black's turn"));
6776 case IcsPlayingBlack:
6777 /* User is moving for Black */
6778 if (WhiteOnMove(currentMove)) {
6779 if (!appData.premove) {
6780 DisplayMoveError(_("It is White's turn"));
6781 } else if (toX >= 0 && toY >= 0) {
6784 premoveFromX = fromX;
6785 premoveFromY = fromY;
6786 premovePromoChar = promoChar;
6788 if (appData.debugMode)
6789 fprintf(debugFP, "Got premove: fromX %d,"
6790 "fromY %d, toX %d, toY %d\n",
6791 fromX, fromY, toX, toY);
6797 case IcsPlayingWhite:
6798 /* User is moving for White */
6799 if (!WhiteOnMove(currentMove)) {
6800 if (!appData.premove) {
6801 DisplayMoveError(_("It is Black's turn"));
6802 } else if (toX >= 0 && toY >= 0) {
6805 premoveFromX = fromX;
6806 premoveFromY = fromY;
6807 premovePromoChar = promoChar;
6809 if (appData.debugMode)
6810 fprintf(debugFP, "Got premove: fromX %d,"
6811 "fromY %d, toX %d, toY %d\n",
6812 fromX, fromY, toX, toY);
6822 /* EditPosition, empty square, or different color piece;
6823 click-click move is possible */
6824 if (toX == -2 || toY == -2) {
6825 boards[0][fromY][fromX] = EmptySquare;
6826 DrawPosition(FALSE, boards[currentMove]);
6828 } else if (toX >= 0 && toY >= 0) {
6829 boards[0][toY][toX] = boards[0][fromY][fromX];
6830 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6831 if(boards[0][fromY][0] != EmptySquare) {
6832 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6833 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6836 if(fromX == BOARD_RGHT+1) {
6837 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6838 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6839 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6842 boards[0][fromY][fromX] = gatingPiece;
6843 DrawPosition(FALSE, boards[currentMove]);
6849 if(toX < 0 || toY < 0) return;
6850 pup = boards[currentMove][toY][toX];
6852 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6853 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6854 if( pup != EmptySquare ) return;
6855 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6856 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6857 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6858 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6859 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6860 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6861 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6865 /* [HGM] always test for legality, to get promotion info */
6866 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6867 fromY, fromX, toY, toX, promoChar);
6869 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6871 /* [HGM] but possibly ignore an IllegalMove result */
6872 if (appData.testLegality) {
6873 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6874 DisplayMoveError(_("Illegal move"));
6879 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6880 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6881 ClearPremoveHighlights(); // was included
6882 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6886 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6889 /* Common tail of UserMoveEvent and DropMenuEvent */
6891 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6895 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6896 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6897 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6898 if(WhiteOnMove(currentMove)) {
6899 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6901 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6905 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6906 move type in caller when we know the move is a legal promotion */
6907 if(moveType == NormalMove && promoChar)
6908 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6910 /* [HGM] <popupFix> The following if has been moved here from
6911 UserMoveEvent(). Because it seemed to belong here (why not allow
6912 piece drops in training games?), and because it can only be
6913 performed after it is known to what we promote. */
6914 if (gameMode == Training) {
6915 /* compare the move played on the board to the next move in the
6916 * game. If they match, display the move and the opponent's response.
6917 * If they don't match, display an error message.
6921 CopyBoard(testBoard, boards[currentMove]);
6922 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6924 if (CompareBoards(testBoard, boards[currentMove+1])) {
6925 ForwardInner(currentMove+1);
6927 /* Autoplay the opponent's response.
6928 * if appData.animate was TRUE when Training mode was entered,
6929 * the response will be animated.
6931 saveAnimate = appData.animate;
6932 appData.animate = animateTraining;
6933 ForwardInner(currentMove+1);
6934 appData.animate = saveAnimate;
6936 /* check for the end of the game */
6937 if (currentMove >= forwardMostMove) {
6938 gameMode = PlayFromGameFile;
6940 SetTrainingModeOff();
6941 DisplayInformation(_("End of game"));
6944 DisplayError(_("Incorrect move"), 0);
6949 /* Ok, now we know that the move is good, so we can kill
6950 the previous line in Analysis Mode */
6951 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6952 && currentMove < forwardMostMove) {
6953 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6954 else forwardMostMove = currentMove;
6959 /* If we need the chess program but it's dead, restart it */
6960 ResurrectChessProgram();
6962 /* A user move restarts a paused game*/
6966 thinkOutput[0] = NULLCHAR;
6968 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6970 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6971 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6975 if (gameMode == BeginningOfGame) {
6976 if (appData.noChessProgram) {
6977 gameMode = EditGame;
6981 gameMode = MachinePlaysBlack;
6984 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6986 if (first.sendName) {
6987 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6988 SendToProgram(buf, &first);
6995 /* Relay move to ICS or chess engine */
6996 if (appData.icsActive) {
6997 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6998 gameMode == IcsExamining) {
6999 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7000 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7002 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7004 // also send plain move, in case ICS does not understand atomic claims
7005 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7009 if (first.sendTime && (gameMode == BeginningOfGame ||
7010 gameMode == MachinePlaysWhite ||
7011 gameMode == MachinePlaysBlack)) {
7012 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7014 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7015 // [HGM] book: if program might be playing, let it use book
7016 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7017 first.maybeThinking = TRUE;
7018 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7019 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7020 SendBoard(&first, currentMove+1);
7021 if(second.analyzing) {
7022 if(!second.useSetboard) SendToProgram("undo\n", &second);
7023 SendBoard(&second, currentMove+1);
7026 SendMoveToProgram(forwardMostMove-1, &first);
7027 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7029 if (currentMove == cmailOldMove + 1) {
7030 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7034 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7038 if(appData.testLegality)
7039 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7045 if (WhiteOnMove(currentMove)) {
7046 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7048 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7052 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7057 case MachinePlaysBlack:
7058 case MachinePlaysWhite:
7059 /* disable certain menu options while machine is thinking */
7060 SetMachineThinkingEnables();
7067 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7068 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7070 if(bookHit) { // [HGM] book: simulate book reply
7071 static char bookMove[MSG_SIZ]; // a bit generous?
7073 programStats.nodes = programStats.depth = programStats.time =
7074 programStats.score = programStats.got_only_move = 0;
7075 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7077 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7078 strcat(bookMove, bookHit);
7079 HandleMachineMove(bookMove, &first);
7085 MarkByFEN(char *fen)
7088 if(!appData.markers || !appData.highlightDragging) return;
7089 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7090 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7094 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7095 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7096 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7097 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7098 if(*fen == 'T') marker[r][f++] = 0; else
7099 if(*fen == 'Y') marker[r][f++] = 1; else
7100 if(*fen == 'G') marker[r][f++] = 3; else
7101 if(*fen == 'B') marker[r][f++] = 4; else
7102 if(*fen == 'C') marker[r][f++] = 5; else
7103 if(*fen == 'M') marker[r][f++] = 6; else
7104 if(*fen == 'W') marker[r][f++] = 7; else
7105 if(*fen == 'D') marker[r][f++] = 8; else
7106 if(*fen == 'R') marker[r][f++] = 2; else {
7107 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7110 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7114 DrawPosition(TRUE, NULL);
7118 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7120 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7121 Markers *m = (Markers *) closure;
7122 if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7123 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7124 || kind == WhiteCapturesEnPassant
7125 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7126 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7130 MarkTargetSquares (int clear)
7133 if(clear) { // no reason to ever suppress clearing
7134 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7135 if(!sum) return; // nothing was cleared,no redraw needed
7138 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7139 !appData.testLegality || gameMode == EditPosition) return;
7140 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7141 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7142 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7144 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7147 DrawPosition(FALSE, NULL);
7151 Explode (Board board, int fromX, int fromY, int toX, int toY)
7153 if(gameInfo.variant == VariantAtomic &&
7154 (board[toY][toX] != EmptySquare || // capture?
7155 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7156 board[fromY][fromX] == BlackPawn )
7158 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7164 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7167 CanPromote (ChessSquare piece, int y)
7169 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7170 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7171 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7172 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7173 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7174 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7175 return (piece == BlackPawn && y == 1 ||
7176 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7177 piece == BlackLance && y == 1 ||
7178 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7182 HoverEvent (int xPix, int yPix, int x, int y)
7184 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7185 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7187 if(dragging == 2) DragPieceMove(xPix, yPix); // [HGM] lion: drag without button for second leg
7188 if(!first.highlight) return;
7189 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7190 if(x == oldX && y == oldY) return; // only do something if we enter new square
7191 oldFromX = fromX; oldFromY = fromY;
7192 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7193 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7194 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7195 else if(oldX != x || oldY != y) {
7196 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7197 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7198 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7199 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7201 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7202 SendToProgram(buf, &first);
7205 // SetHighlights(fromX, fromY, x, y);
7209 void ReportClick(char *action, int x, int y)
7211 char buf[MSG_SIZ]; // Inform engine of what user does
7213 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7214 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7215 if(!first.highlight || gameMode == EditPosition) return;
7216 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7217 SendToProgram(buf, &first);
7221 LeftClick (ClickType clickType, int xPix, int yPix)
7224 Boolean saveAnimate;
7225 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7226 char promoChoice = NULLCHAR;
7228 static TimeMark lastClickTime, prevClickTime;
7230 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7232 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7234 if (clickType == Press) ErrorPopDown();
7235 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7237 x = EventToSquare(xPix, BOARD_WIDTH);
7238 y = EventToSquare(yPix, BOARD_HEIGHT);
7239 if (!flipView && y >= 0) {
7240 y = BOARD_HEIGHT - 1 - y;
7242 if (flipView && x >= 0) {
7243 x = BOARD_WIDTH - 1 - x;
7246 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7247 defaultPromoChoice = promoSweep;
7248 promoSweep = EmptySquare; // terminate sweep
7249 promoDefaultAltered = TRUE;
7250 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7253 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7254 if(clickType == Release) return; // ignore upclick of click-click destination
7255 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7256 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7257 if(gameInfo.holdingsWidth &&
7258 (WhiteOnMove(currentMove)
7259 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7260 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7261 // click in right holdings, for determining promotion piece
7262 ChessSquare p = boards[currentMove][y][x];
7263 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7264 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7265 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7266 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7271 DrawPosition(FALSE, boards[currentMove]);
7275 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7276 if(clickType == Press
7277 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7278 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7279 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7282 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7283 // could be static click on premove from-square: abort premove
7285 ClearPremoveHighlights();
7288 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7289 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7291 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7292 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7293 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7294 defaultPromoChoice = DefaultPromoChoice(side);
7297 autoQueen = appData.alwaysPromoteToQueen;
7301 gatingPiece = EmptySquare;
7302 if (clickType != Press) {
7303 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7304 DragPieceEnd(xPix, yPix); dragging = 0;
7305 DrawPosition(FALSE, NULL);
7309 doubleClick = FALSE;
7310 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7311 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7313 fromX = x; fromY = y; toX = toY = killX = killY = -1;
7314 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7315 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7316 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7318 if (OKToStartUserMove(fromX, fromY)) {
7320 ReportClick("lift", x, y);
7321 MarkTargetSquares(0);
7322 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7323 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7324 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7325 promoSweep = defaultPromoChoice;
7326 selectFlag = 0; lastX = xPix; lastY = yPix;
7327 Sweep(0); // Pawn that is going to promote: preview promotion piece
7328 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7330 if (appData.highlightDragging) {
7331 SetHighlights(fromX, fromY, -1, -1);
7335 } else fromX = fromY = -1;
7341 if (clickType == Press && gameMode != EditPosition) {
7346 // ignore off-board to clicks
7347 if(y < 0 || x < 0) return;
7349 /* Check if clicking again on the same color piece */
7350 fromP = boards[currentMove][fromY][fromX];
7351 toP = boards[currentMove][y][x];
7352 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7353 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7354 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7355 WhitePawn <= toP && toP <= WhiteKing &&
7356 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7357 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7358 (BlackPawn <= fromP && fromP <= BlackKing &&
7359 BlackPawn <= toP && toP <= BlackKing &&
7360 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7361 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7362 /* Clicked again on same color piece -- changed his mind */
7363 second = (x == fromX && y == fromY);
7365 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7366 second = FALSE; // first double-click rather than scond click
7367 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7369 promoDefaultAltered = FALSE;
7370 MarkTargetSquares(1);
7371 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7372 if (appData.highlightDragging) {
7373 SetHighlights(x, y, -1, -1);
7377 if (OKToStartUserMove(x, y)) {
7378 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7379 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7380 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7381 gatingPiece = boards[currentMove][fromY][fromX];
7382 else gatingPiece = doubleClick ? fromP : EmptySquare;
7384 fromY = y; dragging = 1;
7385 ReportClick("lift", x, y);
7386 MarkTargetSquares(0);
7387 DragPieceBegin(xPix, yPix, FALSE);
7388 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7389 promoSweep = defaultPromoChoice;
7390 selectFlag = 0; lastX = xPix; lastY = yPix;
7391 Sweep(0); // Pawn that is going to promote: preview promotion piece
7395 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7398 // ignore clicks on holdings
7399 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7402 if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7403 DragPieceEnd(xPix, yPix); dragging = 0;
7405 // a deferred attempt to click-click move an empty square on top of a piece
7406 boards[currentMove][y][x] = EmptySquare;
7408 DrawPosition(FALSE, boards[currentMove]);
7409 fromX = fromY = -1; clearFlag = 0;
7412 if (appData.animateDragging) {
7413 /* Undo animation damage if any */
7414 DrawPosition(FALSE, NULL);
7416 if (second || sweepSelecting) {
7417 /* Second up/down in same square; just abort move */
7418 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7419 second = sweepSelecting = 0;
7421 gatingPiece = EmptySquare;
7422 MarkTargetSquares(1);
7425 ClearPremoveHighlights();
7427 /* First upclick in same square; start click-click mode */
7428 SetHighlights(x, y, -1, -1);
7435 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7436 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7437 DisplayMessage(_("only marked squares are legal"),"");
7438 DrawPosition(TRUE, NULL);
7439 return; // ignore to-click
7442 /* we now have a different from- and (possibly off-board) to-square */
7443 /* Completed move */
7444 if(!sweepSelecting) {
7449 saveAnimate = appData.animate;
7450 if (clickType == Press) {
7451 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7452 // must be Edit Position mode with empty-square selected
7453 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7454 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7457 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7461 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7462 killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7464 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7465 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7466 if(appData.sweepSelect) {
7467 ChessSquare piece = boards[currentMove][fromY][fromX];
7468 promoSweep = defaultPromoChoice;
7469 if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7470 selectFlag = 0; lastX = xPix; lastY = yPix;
7471 Sweep(0); // Pawn that is going to promote: preview promotion piece
7473 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7474 MarkTargetSquares(1);
7476 return; // promo popup appears on up-click
7478 /* Finish clickclick move */
7479 if (appData.animate || appData.highlightLastMove) {
7480 SetHighlights(fromX, fromY, toX, toY);
7484 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7486 if (appData.animate || appData.highlightLastMove) {
7487 SetHighlights(fromX, fromY, toX, toY);
7493 // [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
7494 /* Finish drag move */
7495 if (appData.highlightLastMove) {
7496 SetHighlights(fromX, fromY, toX, toY);
7501 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7502 dragging *= 2; // flag button-less dragging if we are dragging
7503 MarkTargetSquares(1);
7504 if(x == killX && y == killY) killX = killY = -1; else {
7505 killX = x; killY = y; //remeber this square as intermediate
7506 MarkTargetSquares(0);
7507 ReportClick("put", x, y); // and inform engine
7508 ReportClick("lift", x, y);
7512 DragPieceEnd(xPix, yPix); dragging = 0;
7513 /* Don't animate move and drag both */
7514 appData.animate = FALSE;
7517 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7518 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7519 ChessSquare piece = boards[currentMove][fromY][fromX];
7520 if(gameMode == EditPosition && piece != EmptySquare &&
7521 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7524 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7525 n = PieceToNumber(piece - (int)BlackPawn);
7526 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7527 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7528 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7530 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7531 n = PieceToNumber(piece);
7532 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7533 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7534 boards[currentMove][n][BOARD_WIDTH-2]++;
7536 boards[currentMove][fromY][fromX] = EmptySquare;
7540 MarkTargetSquares(1);
7541 DrawPosition(TRUE, boards[currentMove]);
7545 // off-board moves should not be highlighted
7546 if(x < 0 || y < 0) ClearHighlights();
7547 else ReportClick("put", x, y);
7549 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7551 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7552 SetHighlights(fromX, fromY, toX, toY);
7553 MarkTargetSquares(1);
7554 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7555 // [HGM] super: promotion to captured piece selected from holdings
7556 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7557 promotionChoice = TRUE;
7558 // kludge follows to temporarily execute move on display, without promoting yet
7559 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7560 boards[currentMove][toY][toX] = p;
7561 DrawPosition(FALSE, boards[currentMove]);
7562 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7563 boards[currentMove][toY][toX] = q;
7564 DisplayMessage("Click in holdings to choose piece", "");
7569 int oldMove = currentMove;
7570 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7571 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7572 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7573 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7574 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7575 DrawPosition(TRUE, boards[currentMove]);
7576 MarkTargetSquares(1);
7579 appData.animate = saveAnimate;
7580 if (appData.animate || appData.animateDragging) {
7581 /* Undo animation damage if needed */
7582 DrawPosition(FALSE, NULL);
7587 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7588 { // front-end-free part taken out of PieceMenuPopup
7589 int whichMenu; int xSqr, ySqr;
7591 if(seekGraphUp) { // [HGM] seekgraph
7592 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7593 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7597 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7598 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7599 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7600 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7601 if(action == Press) {
7602 originalFlip = flipView;
7603 flipView = !flipView; // temporarily flip board to see game from partners perspective
7604 DrawPosition(TRUE, partnerBoard);
7605 DisplayMessage(partnerStatus, "");
7607 } else if(action == Release) {
7608 flipView = originalFlip;
7609 DrawPosition(TRUE, boards[currentMove]);
7615 xSqr = EventToSquare(x, BOARD_WIDTH);
7616 ySqr = EventToSquare(y, BOARD_HEIGHT);
7617 if (action == Release) {
7618 if(pieceSweep != EmptySquare) {
7619 EditPositionMenuEvent(pieceSweep, toX, toY);
7620 pieceSweep = EmptySquare;
7621 } else UnLoadPV(); // [HGM] pv
7623 if (action != Press) return -2; // return code to be ignored
7626 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7628 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7629 if (xSqr < 0 || ySqr < 0) return -1;
7630 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7631 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7632 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7633 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7637 if(!appData.icsEngineAnalyze) return -1;
7638 case IcsPlayingWhite:
7639 case IcsPlayingBlack:
7640 if(!appData.zippyPlay) goto noZip;
7643 case MachinePlaysWhite:
7644 case MachinePlaysBlack:
7645 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7646 if (!appData.dropMenu) {
7648 return 2; // flag front-end to grab mouse events
7650 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7651 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7654 if (xSqr < 0 || ySqr < 0) return -1;
7655 if (!appData.dropMenu || appData.testLegality &&
7656 gameInfo.variant != VariantBughouse &&
7657 gameInfo.variant != VariantCrazyhouse) return -1;
7658 whichMenu = 1; // drop menu
7664 if (((*fromX = xSqr) < 0) ||
7665 ((*fromY = ySqr) < 0)) {
7666 *fromX = *fromY = -1;
7670 *fromX = BOARD_WIDTH - 1 - *fromX;
7672 *fromY = BOARD_HEIGHT - 1 - *fromY;
7678 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7680 // char * hint = lastHint;
7681 FrontEndProgramStats stats;
7683 stats.which = cps == &first ? 0 : 1;
7684 stats.depth = cpstats->depth;
7685 stats.nodes = cpstats->nodes;
7686 stats.score = cpstats->score;
7687 stats.time = cpstats->time;
7688 stats.pv = cpstats->movelist;
7689 stats.hint = lastHint;
7690 stats.an_move_index = 0;
7691 stats.an_move_count = 0;
7693 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7694 stats.hint = cpstats->move_name;
7695 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7696 stats.an_move_count = cpstats->nr_moves;
7699 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
7701 SetProgramStats( &stats );
7705 ClearEngineOutputPane (int which)
7707 static FrontEndProgramStats dummyStats;
7708 dummyStats.which = which;
7709 dummyStats.pv = "#";
7710 SetProgramStats( &dummyStats );
7713 #define MAXPLAYERS 500
7716 TourneyStandings (int display)
7718 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7719 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7720 char result, *p, *names[MAXPLAYERS];
7722 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7723 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7724 names[0] = p = strdup(appData.participants);
7725 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7727 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7729 while(result = appData.results[nr]) {
7730 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7731 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7732 wScore = bScore = 0;
7734 case '+': wScore = 2; break;
7735 case '-': bScore = 2; break;
7736 case '=': wScore = bScore = 1; break;
7738 case '*': return strdup("busy"); // tourney not finished
7746 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7747 for(w=0; w<nPlayers; w++) {
7749 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7750 ranking[w] = b; points[w] = bScore; score[b] = -2;
7752 p = malloc(nPlayers*34+1);
7753 for(w=0; w<nPlayers && w<display; w++)
7754 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7760 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7761 { // count all piece types
7763 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7764 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7765 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7768 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7769 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7770 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7771 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7772 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7773 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7778 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7780 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7781 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7783 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7784 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7785 if(myPawns == 2 && nMine == 3) // KPP
7786 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7787 if(myPawns == 1 && nMine == 2) // KP
7788 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7789 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7790 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7791 if(myPawns) return FALSE;
7792 if(pCnt[WhiteRook+side])
7793 return pCnt[BlackRook-side] ||
7794 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7795 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7796 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7797 if(pCnt[WhiteCannon+side]) {
7798 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7799 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7801 if(pCnt[WhiteKnight+side])
7802 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7807 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7809 VariantClass v = gameInfo.variant;
7811 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7812 if(v == VariantShatranj) return TRUE; // always winnable through baring
7813 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7814 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7816 if(v == VariantXiangqi) {
7817 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7819 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7820 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7821 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7822 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7823 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7824 if(stale) // we have at least one last-rank P plus perhaps C
7825 return majors // KPKX
7826 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7828 return pCnt[WhiteFerz+side] // KCAK
7829 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7830 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7831 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7833 } else if(v == VariantKnightmate) {
7834 if(nMine == 1) return FALSE;
7835 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7836 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7837 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7839 if(nMine == 1) return FALSE; // bare King
7840 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
7841 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7842 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7843 // by now we have King + 1 piece (or multiple Bishops on the same color)
7844 if(pCnt[WhiteKnight+side])
7845 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7846 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7847 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7849 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7850 if(pCnt[WhiteAlfil+side])
7851 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7852 if(pCnt[WhiteWazir+side])
7853 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7860 CompareWithRights (Board b1, Board b2)
7863 if(!CompareBoards(b1, b2)) return FALSE;
7864 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7865 /* compare castling rights */
7866 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7867 rights++; /* King lost rights, while rook still had them */
7868 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7869 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7870 rights++; /* but at least one rook lost them */
7872 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7874 if( b1[CASTLING][5] != NoRights ) {
7875 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7882 Adjudicate (ChessProgramState *cps)
7883 { // [HGM] some adjudications useful with buggy engines
7884 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7885 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7886 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7887 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7888 int k, drop, count = 0; static int bare = 1;
7889 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7890 Boolean canAdjudicate = !appData.icsActive;
7892 // most tests only when we understand the game, i.e. legality-checking on
7893 if( appData.testLegality )
7894 { /* [HGM] Some more adjudications for obstinate engines */
7895 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7896 static int moveCount = 6;
7898 char *reason = NULL;
7900 /* Count what is on board. */
7901 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7903 /* Some material-based adjudications that have to be made before stalemate test */
7904 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7905 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7906 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7907 if(canAdjudicate && appData.checkMates) {
7909 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7910 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7911 "Xboard adjudication: King destroyed", GE_XBOARD );
7916 /* Bare King in Shatranj (loses) or Losers (wins) */
7917 if( nrW == 1 || nrB == 1) {
7918 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7919 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7920 if(canAdjudicate && appData.checkMates) {
7922 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7923 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7924 "Xboard adjudication: Bare king", GE_XBOARD );
7928 if( gameInfo.variant == VariantShatranj && --bare < 0)
7930 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7931 if(canAdjudicate && appData.checkMates) {
7932 /* but only adjudicate if adjudication enabled */
7934 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7935 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7936 "Xboard adjudication: Bare king", GE_XBOARD );
7943 // don't wait for engine to announce game end if we can judge ourselves
7944 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7946 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7947 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7948 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7949 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7952 reason = "Xboard adjudication: 3rd check";
7953 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7963 reason = "Xboard adjudication: Stalemate";
7964 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7965 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7966 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7967 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7968 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7969 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7970 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7971 EP_CHECKMATE : EP_WINS);
7972 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7973 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7977 reason = "Xboard adjudication: Checkmate";
7978 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7979 if(gameInfo.variant == VariantShogi) {
7980 if(forwardMostMove > backwardMostMove
7981 && moveList[forwardMostMove-1][1] == '@'
7982 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7983 reason = "XBoard adjudication: pawn-drop mate";
7984 boards[forwardMostMove][EP_STATUS] = EP_WINS;
7990 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7992 result = GameIsDrawn; break;
7994 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7996 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8000 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8002 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8003 GameEnds( result, reason, GE_XBOARD );
8007 /* Next absolutely insufficient mating material. */
8008 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8009 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8010 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8012 /* always flag draws, for judging claims */
8013 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8015 if(canAdjudicate && appData.materialDraws) {
8016 /* but only adjudicate them if adjudication enabled */
8017 if(engineOpponent) {
8018 SendToProgram("force\n", engineOpponent); // suppress reply
8019 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8021 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8026 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8027 if(gameInfo.variant == VariantXiangqi ?
8028 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8030 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8031 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8032 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8033 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8035 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8036 { /* if the first 3 moves do not show a tactical win, declare draw */
8037 if(engineOpponent) {
8038 SendToProgram("force\n", engineOpponent); // suppress reply
8039 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8041 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8044 } else moveCount = 6;
8047 // Repetition draws and 50-move rule can be applied independently of legality testing
8049 /* Check for rep-draws */
8051 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8052 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8053 for(k = forwardMostMove-2;
8054 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8055 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8056 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8059 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8060 /* compare castling rights */
8061 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8062 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8063 rights++; /* King lost rights, while rook still had them */
8064 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8065 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8066 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8067 rights++; /* but at least one rook lost them */
8069 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8070 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8072 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8073 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8074 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8077 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8078 && appData.drawRepeats > 1) {
8079 /* adjudicate after user-specified nr of repeats */
8080 int result = GameIsDrawn;
8081 char *details = "XBoard adjudication: repetition draw";
8082 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8083 // [HGM] xiangqi: check for forbidden perpetuals
8084 int m, ourPerpetual = 1, hisPerpetual = 1;
8085 for(m=forwardMostMove; m>k; m-=2) {
8086 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8087 ourPerpetual = 0; // the current mover did not always check
8088 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8089 hisPerpetual = 0; // the opponent did not always check
8091 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8092 ourPerpetual, hisPerpetual);
8093 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8094 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8095 details = "Xboard adjudication: perpetual checking";
8097 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8098 break; // (or we would have caught him before). Abort repetition-checking loop.
8100 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8101 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8103 details = "Xboard adjudication: repetition";
8105 } else // it must be XQ
8106 // Now check for perpetual chases
8107 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8108 hisPerpetual = PerpetualChase(k, forwardMostMove);
8109 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8110 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8111 static char resdet[MSG_SIZ];
8112 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8114 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8116 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8117 break; // Abort repetition-checking loop.
8119 // if neither of us is checking or chasing all the time, or both are, it is draw
8121 if(engineOpponent) {
8122 SendToProgram("force\n", engineOpponent); // suppress reply
8123 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8125 GameEnds( result, details, GE_XBOARD );
8128 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8129 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8133 /* Now we test for 50-move draws. Determine ply count */
8134 count = forwardMostMove;
8135 /* look for last irreversble move */
8136 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8138 /* if we hit starting position, add initial plies */
8139 if( count == backwardMostMove )
8140 count -= initialRulePlies;
8141 count = forwardMostMove - count;
8142 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8143 // adjust reversible move counter for checks in Xiangqi
8144 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8145 if(i < backwardMostMove) i = backwardMostMove;
8146 while(i <= forwardMostMove) {
8147 lastCheck = inCheck; // check evasion does not count
8148 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8149 if(inCheck || lastCheck) count--; // check does not count
8154 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8155 /* this is used to judge if draw claims are legal */
8156 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8157 if(engineOpponent) {
8158 SendToProgram("force\n", engineOpponent); // suppress reply
8159 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8161 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8165 /* if draw offer is pending, treat it as a draw claim
8166 * when draw condition present, to allow engines a way to
8167 * claim draws before making their move to avoid a race
8168 * condition occurring after their move
8170 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8172 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8173 p = "Draw claim: 50-move rule";
8174 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8175 p = "Draw claim: 3-fold repetition";
8176 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8177 p = "Draw claim: insufficient mating material";
8178 if( p != NULL && canAdjudicate) {
8179 if(engineOpponent) {
8180 SendToProgram("force\n", engineOpponent); // suppress reply
8181 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8183 GameEnds( GameIsDrawn, p, GE_XBOARD );
8188 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8189 if(engineOpponent) {
8190 SendToProgram("force\n", engineOpponent); // suppress reply
8191 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8193 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8200 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8201 { // [HGM] book: this routine intercepts moves to simulate book replies
8202 char *bookHit = NULL;
8204 //first determine if the incoming move brings opponent into his book
8205 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8206 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8207 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8208 if(bookHit != NULL && !cps->bookSuspend) {
8209 // make sure opponent is not going to reply after receiving move to book position
8210 SendToProgram("force\n", cps);
8211 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8213 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8214 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8215 // now arrange restart after book miss
8217 // after a book hit we never send 'go', and the code after the call to this routine
8218 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8219 char buf[MSG_SIZ], *move = bookHit;
8221 int fromX, fromY, toX, toY;
8225 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8226 &fromX, &fromY, &toX, &toY, &promoChar)) {
8227 (void) CoordsToAlgebraic(boards[forwardMostMove],
8228 PosFlags(forwardMostMove),
8229 fromY, fromX, toY, toX, promoChar, move);
8231 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8235 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8236 SendToProgram(buf, cps);
8237 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8238 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8239 SendToProgram("go\n", cps);
8240 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8241 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8242 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8243 SendToProgram("go\n", cps);
8244 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8246 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8250 LoadError (char *errmess, ChessProgramState *cps)
8251 { // unloads engine and switches back to -ncp mode if it was first
8252 if(cps->initDone) return FALSE;
8253 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8254 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8257 appData.noChessProgram = TRUE;
8258 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8259 gameMode = BeginningOfGame; ModeHighlight();
8262 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8263 DisplayMessage("", ""); // erase waiting message
8264 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8269 ChessProgramState *savedState;
8271 DeferredBookMove (void)
8273 if(savedState->lastPing != savedState->lastPong)
8274 ScheduleDelayedEvent(DeferredBookMove, 10);
8276 HandleMachineMove(savedMessage, savedState);
8279 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8280 static ChessProgramState *stalledEngine;
8281 static char stashedInputMove[MSG_SIZ];
8284 HandleMachineMove (char *message, ChessProgramState *cps)
8286 static char firstLeg[20];
8287 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8288 char realname[MSG_SIZ];
8289 int fromX, fromY, toX, toY;
8291 char promoChar, roar;
8293 int machineWhite, oldError;
8296 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8297 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8298 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8299 DisplayError(_("Invalid pairing from pairing engine"), 0);
8302 pairingReceived = 1;
8304 return; // Skim the pairing messages here.
8307 oldError = cps->userError; cps->userError = 0;
8309 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8311 * Kludge to ignore BEL characters
8313 while (*message == '\007') message++;
8316 * [HGM] engine debug message: ignore lines starting with '#' character
8318 if(cps->debug && *message == '#') return;
8321 * Look for book output
8323 if (cps == &first && bookRequested) {
8324 if (message[0] == '\t' || message[0] == ' ') {
8325 /* Part of the book output is here; append it */
8326 strcat(bookOutput, message);
8327 strcat(bookOutput, " \n");
8329 } else if (bookOutput[0] != NULLCHAR) {
8330 /* All of book output has arrived; display it */
8331 char *p = bookOutput;
8332 while (*p != NULLCHAR) {
8333 if (*p == '\t') *p = ' ';
8336 DisplayInformation(bookOutput);
8337 bookRequested = FALSE;
8338 /* Fall through to parse the current output */
8343 * Look for machine move.
8345 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8346 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8348 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8349 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8350 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8351 stalledEngine = cps;
8352 if(appData.ponderNextMove) { // bring opponent out of ponder
8353 if(gameMode == TwoMachinesPlay) {
8354 if(cps->other->pause)
8355 PauseEngine(cps->other);
8357 SendToProgram("easy\n", cps->other);
8364 /* This method is only useful on engines that support ping */
8365 if (cps->lastPing != cps->lastPong) {
8366 if (gameMode == BeginningOfGame) {
8367 /* Extra move from before last new; ignore */
8368 if (appData.debugMode) {
8369 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8372 if (appData.debugMode) {
8373 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8374 cps->which, gameMode);
8377 SendToProgram("undo\n", cps);
8383 case BeginningOfGame:
8384 /* Extra move from before last reset; ignore */
8385 if (appData.debugMode) {
8386 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8393 /* Extra move after we tried to stop. The mode test is
8394 not a reliable way of detecting this problem, but it's
8395 the best we can do on engines that don't support ping.
8397 if (appData.debugMode) {
8398 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8399 cps->which, gameMode);
8401 SendToProgram("undo\n", cps);
8404 case MachinePlaysWhite:
8405 case IcsPlayingWhite:
8406 machineWhite = TRUE;
8409 case MachinePlaysBlack:
8410 case IcsPlayingBlack:
8411 machineWhite = FALSE;
8414 case TwoMachinesPlay:
8415 machineWhite = (cps->twoMachinesColor[0] == 'w');
8418 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8419 if (appData.debugMode) {
8421 "Ignoring move out of turn by %s, gameMode %d"
8422 ", forwardMost %d\n",
8423 cps->which, gameMode, forwardMostMove);
8428 if(cps->alphaRank) AlphaRank(machineMove, 4);
8430 // [HGM] lion: (some very limited) support for Alien protocol
8432 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8433 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8435 } else if(firstLeg[0]) { // there was a previous leg;
8436 // only support case where same piece makes two step (and don't even test that!)
8437 char buf[20], *p = machineMove+1, *q = buf+1, f;
8438 safeStrCpy(buf, machineMove, 20);
8439 while(isdigit(*q)) q++; // find start of to-square
8440 safeStrCpy(machineMove, firstLeg, 20);
8441 while(isdigit(*p)) p++;
8442 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8443 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8444 firstLeg[0] = NULLCHAR;
8447 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8448 &fromX, &fromY, &toX, &toY, &promoChar)) {
8449 /* Machine move could not be parsed; ignore it. */
8450 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8451 machineMove, _(cps->which));
8452 DisplayMoveError(buf1);
8453 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8454 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8455 if (gameMode == TwoMachinesPlay) {
8456 GameEnds(machineWhite ? BlackWins : WhiteWins,
8462 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8463 /* So we have to redo legality test with true e.p. status here, */
8464 /* to make sure an illegal e.p. capture does not slip through, */
8465 /* to cause a forfeit on a justified illegal-move complaint */
8466 /* of the opponent. */
8467 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8469 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8470 fromY, fromX, toY, toX, promoChar);
8471 if(moveType == IllegalMove) {
8472 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8473 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8474 GameEnds(machineWhite ? BlackWins : WhiteWins,
8477 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8478 /* [HGM] Kludge to handle engines that send FRC-style castling
8479 when they shouldn't (like TSCP-Gothic) */
8481 case WhiteASideCastleFR:
8482 case BlackASideCastleFR:
8484 currentMoveString[2]++;
8486 case WhiteHSideCastleFR:
8487 case BlackHSideCastleFR:
8489 currentMoveString[2]--;
8491 default: ; // nothing to do, but suppresses warning of pedantic compilers
8494 hintRequested = FALSE;
8495 lastHint[0] = NULLCHAR;
8496 bookRequested = FALSE;
8497 /* Program may be pondering now */
8498 cps->maybeThinking = TRUE;
8499 if (cps->sendTime == 2) cps->sendTime = 1;
8500 if (cps->offeredDraw) cps->offeredDraw--;
8502 /* [AS] Save move info*/
8503 pvInfoList[ forwardMostMove ].score = programStats.score;
8504 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8505 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8507 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8509 /* Test suites abort the 'game' after one move */
8510 if(*appData.finger) {
8512 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8513 if(!f) f = fopen(appData.finger, "w");
8514 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8515 else { DisplayFatalError("Bad output file", errno, 0); return; }
8517 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8520 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8521 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8524 while( count < adjudicateLossPlies ) {
8525 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8528 score = -score; /* Flip score for winning side */
8531 if( score > adjudicateLossThreshold ) {
8538 if( count >= adjudicateLossPlies ) {
8539 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8541 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8542 "Xboard adjudication",
8549 if(Adjudicate(cps)) {
8550 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8551 return; // [HGM] adjudicate: for all automatic game ends
8555 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8557 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8558 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8560 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8562 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8564 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8565 char buf[3*MSG_SIZ];
8567 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8568 programStats.score / 100.,
8570 programStats.time / 100.,
8571 (unsigned int)programStats.nodes,
8572 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8573 programStats.movelist);
8575 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8580 /* [AS] Clear stats for next move */
8581 ClearProgramStats();
8582 thinkOutput[0] = NULLCHAR;
8583 hiddenThinkOutputState = 0;
8586 if (gameMode == TwoMachinesPlay) {
8587 /* [HGM] relaying draw offers moved to after reception of move */
8588 /* and interpreting offer as claim if it brings draw condition */
8589 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8590 SendToProgram("draw\n", cps->other);
8592 if (cps->other->sendTime) {
8593 SendTimeRemaining(cps->other,
8594 cps->other->twoMachinesColor[0] == 'w');
8596 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8597 if (firstMove && !bookHit) {
8599 if (cps->other->useColors) {
8600 SendToProgram(cps->other->twoMachinesColor, cps->other);
8602 SendToProgram("go\n", cps->other);
8604 cps->other->maybeThinking = TRUE;
8607 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8609 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8611 if (!pausing && appData.ringBellAfterMoves) {
8612 if(!roar) RingBell();
8616 * Reenable menu items that were disabled while
8617 * machine was thinking
8619 if (gameMode != TwoMachinesPlay)
8620 SetUserThinkingEnables();
8622 // [HGM] book: after book hit opponent has received move and is now in force mode
8623 // force the book reply into it, and then fake that it outputted this move by jumping
8624 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8626 static char bookMove[MSG_SIZ]; // a bit generous?
8628 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8629 strcat(bookMove, bookHit);
8632 programStats.nodes = programStats.depth = programStats.time =
8633 programStats.score = programStats.got_only_move = 0;
8634 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8636 if(cps->lastPing != cps->lastPong) {
8637 savedMessage = message; // args for deferred call
8639 ScheduleDelayedEvent(DeferredBookMove, 10);
8648 /* Set special modes for chess engines. Later something general
8649 * could be added here; for now there is just one kludge feature,
8650 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8651 * when "xboard" is given as an interactive command.
8653 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8654 cps->useSigint = FALSE;
8655 cps->useSigterm = FALSE;
8657 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8658 ParseFeatures(message+8, cps);
8659 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8662 if (!strncmp(message, "setup ", 6) &&
8663 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8664 ) { // [HGM] allow first engine to define opening position
8665 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8666 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8668 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8669 if(startedFromSetupPosition) return;
8670 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8672 while(message[s] && message[s++] != ' ');
8673 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8674 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8675 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8676 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8677 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8678 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8681 ParseFEN(boards[0], &dummy, message+s, FALSE);
8682 DrawPosition(TRUE, boards[0]);
8683 startedFromSetupPosition = TRUE;
8686 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8687 * want this, I was asked to put it in, and obliged.
8689 if (!strncmp(message, "setboard ", 9)) {
8690 Board initial_position;
8692 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8694 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8695 DisplayError(_("Bad FEN received from engine"), 0);
8699 CopyBoard(boards[0], initial_position);
8700 initialRulePlies = FENrulePlies;
8701 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8702 else gameMode = MachinePlaysBlack;
8703 DrawPosition(FALSE, boards[currentMove]);
8709 * Look for communication commands
8711 if (!strncmp(message, "telluser ", 9)) {
8712 if(message[9] == '\\' && message[10] == '\\')
8713 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8715 DisplayNote(message + 9);
8718 if (!strncmp(message, "tellusererror ", 14)) {
8720 if(message[14] == '\\' && message[15] == '\\')
8721 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8723 DisplayError(message + 14, 0);
8726 if (!strncmp(message, "tellopponent ", 13)) {
8727 if (appData.icsActive) {
8729 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8733 DisplayNote(message + 13);
8737 if (!strncmp(message, "tellothers ", 11)) {
8738 if (appData.icsActive) {
8740 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8743 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8746 if (!strncmp(message, "tellall ", 8)) {
8747 if (appData.icsActive) {
8749 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8753 DisplayNote(message + 8);
8757 if (strncmp(message, "warning", 7) == 0) {
8758 /* Undocumented feature, use tellusererror in new code */
8759 DisplayError(message, 0);
8762 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8763 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8764 strcat(realname, " query");
8765 AskQuestion(realname, buf2, buf1, cps->pr);
8768 /* Commands from the engine directly to ICS. We don't allow these to be
8769 * sent until we are logged on. Crafty kibitzes have been known to
8770 * interfere with the login process.
8773 if (!strncmp(message, "tellics ", 8)) {
8774 SendToICS(message + 8);
8778 if (!strncmp(message, "tellicsnoalias ", 15)) {
8779 SendToICS(ics_prefix);
8780 SendToICS(message + 15);
8784 /* The following are for backward compatibility only */
8785 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8786 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8787 SendToICS(ics_prefix);
8793 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8796 if(!strncmp(message, "highlight ", 10)) {
8797 if(appData.testLegality && appData.markers) return;
8798 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8801 if(!strncmp(message, "click ", 6)) {
8802 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8803 if(appData.testLegality || !appData.oneClick) return;
8804 sscanf(message+6, "%c%d%c", &f, &y, &c);
8805 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8806 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8807 x = x*squareSize + (x+1)*lineGap + squareSize/2;
8808 y = y*squareSize + (y+1)*lineGap + squareSize/2;
8809 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8810 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8811 LeftClick(Release, lastLeftX, lastLeftY);
8812 controlKey = (c == ',');
8813 LeftClick(Press, x, y);
8814 LeftClick(Release, x, y);
8815 first.highlight = f;
8819 * If the move is illegal, cancel it and redraw the board.
8820 * Also deal with other error cases. Matching is rather loose
8821 * here to accommodate engines written before the spec.
8823 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8824 strncmp(message, "Error", 5) == 0) {
8825 if (StrStr(message, "name") ||
8826 StrStr(message, "rating") || StrStr(message, "?") ||
8827 StrStr(message, "result") || StrStr(message, "board") ||
8828 StrStr(message, "bk") || StrStr(message, "computer") ||
8829 StrStr(message, "variant") || StrStr(message, "hint") ||
8830 StrStr(message, "random") || StrStr(message, "depth") ||
8831 StrStr(message, "accepted")) {
8834 if (StrStr(message, "protover")) {
8835 /* Program is responding to input, so it's apparently done
8836 initializing, and this error message indicates it is
8837 protocol version 1. So we don't need to wait any longer
8838 for it to initialize and send feature commands. */
8839 FeatureDone(cps, 1);
8840 cps->protocolVersion = 1;
8843 cps->maybeThinking = FALSE;
8845 if (StrStr(message, "draw")) {
8846 /* Program doesn't have "draw" command */
8847 cps->sendDrawOffers = 0;
8850 if (cps->sendTime != 1 &&
8851 (StrStr(message, "time") || StrStr(message, "otim"))) {
8852 /* Program apparently doesn't have "time" or "otim" command */
8856 if (StrStr(message, "analyze")) {
8857 cps->analysisSupport = FALSE;
8858 cps->analyzing = FALSE;
8859 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8860 EditGameEvent(); // [HGM] try to preserve loaded game
8861 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8862 DisplayError(buf2, 0);
8865 if (StrStr(message, "(no matching move)st")) {
8866 /* Special kludge for GNU Chess 4 only */
8867 cps->stKludge = TRUE;
8868 SendTimeControl(cps, movesPerSession, timeControl,
8869 timeIncrement, appData.searchDepth,
8873 if (StrStr(message, "(no matching move)sd")) {
8874 /* Special kludge for GNU Chess 4 only */
8875 cps->sdKludge = TRUE;
8876 SendTimeControl(cps, movesPerSession, timeControl,
8877 timeIncrement, appData.searchDepth,
8881 if (!StrStr(message, "llegal")) {
8884 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8885 gameMode == IcsIdle) return;
8886 if (forwardMostMove <= backwardMostMove) return;
8887 if (pausing) PauseEvent();
8888 if(appData.forceIllegal) {
8889 // [HGM] illegal: machine refused move; force position after move into it
8890 SendToProgram("force\n", cps);
8891 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8892 // we have a real problem now, as SendBoard will use the a2a3 kludge
8893 // when black is to move, while there might be nothing on a2 or black
8894 // might already have the move. So send the board as if white has the move.
8895 // But first we must change the stm of the engine, as it refused the last move
8896 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8897 if(WhiteOnMove(forwardMostMove)) {
8898 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8899 SendBoard(cps, forwardMostMove); // kludgeless board
8901 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8902 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8903 SendBoard(cps, forwardMostMove+1); // kludgeless board
8905 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8906 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8907 gameMode == TwoMachinesPlay)
8908 SendToProgram("go\n", cps);
8911 if (gameMode == PlayFromGameFile) {
8912 /* Stop reading this game file */
8913 gameMode = EditGame;
8916 /* [HGM] illegal-move claim should forfeit game when Xboard */
8917 /* only passes fully legal moves */
8918 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8919 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8920 "False illegal-move claim", GE_XBOARD );
8921 return; // do not take back move we tested as valid
8923 currentMove = forwardMostMove-1;
8924 DisplayMove(currentMove-1); /* before DisplayMoveError */
8925 SwitchClocks(forwardMostMove-1); // [HGM] race
8926 DisplayBothClocks();
8927 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8928 parseList[currentMove], _(cps->which));
8929 DisplayMoveError(buf1);
8930 DrawPosition(FALSE, boards[currentMove]);
8932 SetUserThinkingEnables();
8935 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8936 /* Program has a broken "time" command that
8937 outputs a string not ending in newline.
8943 * If chess program startup fails, exit with an error message.
8944 * Attempts to recover here are futile. [HGM] Well, we try anyway
8946 if ((StrStr(message, "unknown host") != NULL)
8947 || (StrStr(message, "No remote directory") != NULL)
8948 || (StrStr(message, "not found") != NULL)
8949 || (StrStr(message, "No such file") != NULL)
8950 || (StrStr(message, "can't alloc") != NULL)
8951 || (StrStr(message, "Permission denied") != NULL)) {
8953 cps->maybeThinking = FALSE;
8954 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8955 _(cps->which), cps->program, cps->host, message);
8956 RemoveInputSource(cps->isr);
8957 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8958 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8959 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8965 * Look for hint output
8967 if (sscanf(message, "Hint: %s", buf1) == 1) {
8968 if (cps == &first && hintRequested) {
8969 hintRequested = FALSE;
8970 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8971 &fromX, &fromY, &toX, &toY, &promoChar)) {
8972 (void) CoordsToAlgebraic(boards[forwardMostMove],
8973 PosFlags(forwardMostMove),
8974 fromY, fromX, toY, toX, promoChar, buf1);
8975 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8976 DisplayInformation(buf2);
8978 /* Hint move could not be parsed!? */
8979 snprintf(buf2, sizeof(buf2),
8980 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8981 buf1, _(cps->which));
8982 DisplayError(buf2, 0);
8985 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8991 * Ignore other messages if game is not in progress
8993 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8994 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8997 * look for win, lose, draw, or draw offer
8999 if (strncmp(message, "1-0", 3) == 0) {
9000 char *p, *q, *r = "";
9001 p = strchr(message, '{');
9009 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9011 } else if (strncmp(message, "0-1", 3) == 0) {
9012 char *p, *q, *r = "";
9013 p = strchr(message, '{');
9021 /* Kludge for Arasan 4.1 bug */
9022 if (strcmp(r, "Black resigns") == 0) {
9023 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9026 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9028 } else if (strncmp(message, "1/2", 3) == 0) {
9029 char *p, *q, *r = "";
9030 p = strchr(message, '{');
9039 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9042 } else if (strncmp(message, "White resign", 12) == 0) {
9043 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9045 } else if (strncmp(message, "Black resign", 12) == 0) {
9046 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9048 } else if (strncmp(message, "White matches", 13) == 0 ||
9049 strncmp(message, "Black matches", 13) == 0 ) {
9050 /* [HGM] ignore GNUShogi noises */
9052 } else if (strncmp(message, "White", 5) == 0 &&
9053 message[5] != '(' &&
9054 StrStr(message, "Black") == NULL) {
9055 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9057 } else if (strncmp(message, "Black", 5) == 0 &&
9058 message[5] != '(') {
9059 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9061 } else if (strcmp(message, "resign") == 0 ||
9062 strcmp(message, "computer resigns") == 0) {
9064 case MachinePlaysBlack:
9065 case IcsPlayingBlack:
9066 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9068 case MachinePlaysWhite:
9069 case IcsPlayingWhite:
9070 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9072 case TwoMachinesPlay:
9073 if (cps->twoMachinesColor[0] == 'w')
9074 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9076 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9083 } else if (strncmp(message, "opponent mates", 14) == 0) {
9085 case MachinePlaysBlack:
9086 case IcsPlayingBlack:
9087 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9089 case MachinePlaysWhite:
9090 case IcsPlayingWhite:
9091 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9093 case TwoMachinesPlay:
9094 if (cps->twoMachinesColor[0] == 'w')
9095 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9097 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9104 } else if (strncmp(message, "computer mates", 14) == 0) {
9106 case MachinePlaysBlack:
9107 case IcsPlayingBlack:
9108 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9110 case MachinePlaysWhite:
9111 case IcsPlayingWhite:
9112 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9114 case TwoMachinesPlay:
9115 if (cps->twoMachinesColor[0] == 'w')
9116 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9118 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9125 } else if (strncmp(message, "checkmate", 9) == 0) {
9126 if (WhiteOnMove(forwardMostMove)) {
9127 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9129 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9132 } else if (strstr(message, "Draw") != NULL ||
9133 strstr(message, "game is a draw") != NULL) {
9134 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9136 } else if (strstr(message, "offer") != NULL &&
9137 strstr(message, "draw") != NULL) {
9139 if (appData.zippyPlay && first.initDone) {
9140 /* Relay offer to ICS */
9141 SendToICS(ics_prefix);
9142 SendToICS("draw\n");
9145 cps->offeredDraw = 2; /* valid until this engine moves twice */
9146 if (gameMode == TwoMachinesPlay) {
9147 if (cps->other->offeredDraw) {
9148 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9149 /* [HGM] in two-machine mode we delay relaying draw offer */
9150 /* until after we also have move, to see if it is really claim */
9152 } else if (gameMode == MachinePlaysWhite ||
9153 gameMode == MachinePlaysBlack) {
9154 if (userOfferedDraw) {
9155 DisplayInformation(_("Machine accepts your draw offer"));
9156 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9158 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9165 * Look for thinking output
9167 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9168 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9170 int plylev, mvleft, mvtot, curscore, time;
9171 char mvname[MOVE_LEN];
9175 int prefixHint = FALSE;
9176 mvname[0] = NULLCHAR;
9179 case MachinePlaysBlack:
9180 case IcsPlayingBlack:
9181 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9183 case MachinePlaysWhite:
9184 case IcsPlayingWhite:
9185 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9190 case IcsObserving: /* [DM] icsEngineAnalyze */
9191 if (!appData.icsEngineAnalyze) ignore = TRUE;
9193 case TwoMachinesPlay:
9194 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9204 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9206 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9207 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9209 if (plyext != ' ' && plyext != '\t') {
9213 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9214 if( cps->scoreIsAbsolute &&
9215 ( gameMode == MachinePlaysBlack ||
9216 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9217 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9218 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9219 !WhiteOnMove(currentMove)
9222 curscore = -curscore;
9225 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9227 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9230 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9231 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9232 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9233 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9234 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9235 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9239 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9240 DisplayError(_("failed writing PV"), 0);
9243 tempStats.depth = plylev;
9244 tempStats.nodes = nodes;
9245 tempStats.time = time;
9246 tempStats.score = curscore;
9247 tempStats.got_only_move = 0;
9249 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9252 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9253 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9254 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9255 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9256 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9257 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9258 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9259 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9262 /* Buffer overflow protection */
9263 if (pv[0] != NULLCHAR) {
9264 if (strlen(pv) >= sizeof(tempStats.movelist)
9265 && appData.debugMode) {
9267 "PV is too long; using the first %u bytes.\n",
9268 (unsigned) sizeof(tempStats.movelist) - 1);
9271 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9273 sprintf(tempStats.movelist, " no PV\n");
9276 if (tempStats.seen_stat) {
9277 tempStats.ok_to_send = 1;
9280 if (strchr(tempStats.movelist, '(') != NULL) {
9281 tempStats.line_is_book = 1;
9282 tempStats.nr_moves = 0;
9283 tempStats.moves_left = 0;
9285 tempStats.line_is_book = 0;
9288 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9289 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9291 SendProgramStatsToFrontend( cps, &tempStats );
9294 [AS] Protect the thinkOutput buffer from overflow... this
9295 is only useful if buf1 hasn't overflowed first!
9297 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9299 (gameMode == TwoMachinesPlay ?
9300 ToUpper(cps->twoMachinesColor[0]) : ' '),
9301 ((double) curscore) / 100.0,
9302 prefixHint ? lastHint : "",
9303 prefixHint ? " " : "" );
9305 if( buf1[0] != NULLCHAR ) {
9306 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9308 if( strlen(pv) > max_len ) {
9309 if( appData.debugMode) {
9310 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9312 pv[max_len+1] = '\0';
9315 strcat( thinkOutput, pv);
9318 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9319 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9320 DisplayMove(currentMove - 1);
9324 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9325 /* crafty (9.25+) says "(only move) <move>"
9326 * if there is only 1 legal move
9328 sscanf(p, "(only move) %s", buf1);
9329 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9330 sprintf(programStats.movelist, "%s (only move)", buf1);
9331 programStats.depth = 1;
9332 programStats.nr_moves = 1;
9333 programStats.moves_left = 1;
9334 programStats.nodes = 1;
9335 programStats.time = 1;
9336 programStats.got_only_move = 1;
9338 /* Not really, but we also use this member to
9339 mean "line isn't going to change" (Crafty
9340 isn't searching, so stats won't change) */
9341 programStats.line_is_book = 1;
9343 SendProgramStatsToFrontend( cps, &programStats );
9345 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9346 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9347 DisplayMove(currentMove - 1);
9350 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9351 &time, &nodes, &plylev, &mvleft,
9352 &mvtot, mvname) >= 5) {
9353 /* The stat01: line is from Crafty (9.29+) in response
9354 to the "." command */
9355 programStats.seen_stat = 1;
9356 cps->maybeThinking = TRUE;
9358 if (programStats.got_only_move || !appData.periodicUpdates)
9361 programStats.depth = plylev;
9362 programStats.time = time;
9363 programStats.nodes = nodes;
9364 programStats.moves_left = mvleft;
9365 programStats.nr_moves = mvtot;
9366 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9367 programStats.ok_to_send = 1;
9368 programStats.movelist[0] = '\0';
9370 SendProgramStatsToFrontend( cps, &programStats );
9374 } else if (strncmp(message,"++",2) == 0) {
9375 /* Crafty 9.29+ outputs this */
9376 programStats.got_fail = 2;
9379 } else if (strncmp(message,"--",2) == 0) {
9380 /* Crafty 9.29+ outputs this */
9381 programStats.got_fail = 1;
9384 } else if (thinkOutput[0] != NULLCHAR &&
9385 strncmp(message, " ", 4) == 0) {
9386 unsigned message_len;
9389 while (*p && *p == ' ') p++;
9391 message_len = strlen( p );
9393 /* [AS] Avoid buffer overflow */
9394 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9395 strcat(thinkOutput, " ");
9396 strcat(thinkOutput, p);
9399 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9400 strcat(programStats.movelist, " ");
9401 strcat(programStats.movelist, p);
9404 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9405 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9406 DisplayMove(currentMove - 1);
9414 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9415 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9417 ChessProgramStats cpstats;
9419 if (plyext != ' ' && plyext != '\t') {
9423 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9424 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9425 curscore = -curscore;
9428 cpstats.depth = plylev;
9429 cpstats.nodes = nodes;
9430 cpstats.time = time;
9431 cpstats.score = curscore;
9432 cpstats.got_only_move = 0;
9433 cpstats.movelist[0] = '\0';
9435 if (buf1[0] != NULLCHAR) {
9436 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9439 cpstats.ok_to_send = 0;
9440 cpstats.line_is_book = 0;
9441 cpstats.nr_moves = 0;
9442 cpstats.moves_left = 0;
9444 SendProgramStatsToFrontend( cps, &cpstats );
9451 /* Parse a game score from the character string "game", and
9452 record it as the history of the current game. The game
9453 score is NOT assumed to start from the standard position.
9454 The display is not updated in any way.
9457 ParseGameHistory (char *game)
9460 int fromX, fromY, toX, toY, boardIndex;
9465 if (appData.debugMode)
9466 fprintf(debugFP, "Parsing game history: %s\n", game);
9468 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9469 gameInfo.site = StrSave(appData.icsHost);
9470 gameInfo.date = PGNDate();
9471 gameInfo.round = StrSave("-");
9473 /* Parse out names of players */
9474 while (*game == ' ') game++;
9476 while (*game != ' ') *p++ = *game++;
9478 gameInfo.white = StrSave(buf);
9479 while (*game == ' ') game++;
9481 while (*game != ' ' && *game != '\n') *p++ = *game++;
9483 gameInfo.black = StrSave(buf);
9486 boardIndex = blackPlaysFirst ? 1 : 0;
9489 yyboardindex = boardIndex;
9490 moveType = (ChessMove) Myylex();
9492 case IllegalMove: /* maybe suicide chess, etc. */
9493 if (appData.debugMode) {
9494 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9495 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9496 setbuf(debugFP, NULL);
9498 case WhitePromotion:
9499 case BlackPromotion:
9500 case WhiteNonPromotion:
9501 case BlackNonPromotion:
9504 case WhiteCapturesEnPassant:
9505 case BlackCapturesEnPassant:
9506 case WhiteKingSideCastle:
9507 case WhiteQueenSideCastle:
9508 case BlackKingSideCastle:
9509 case BlackQueenSideCastle:
9510 case WhiteKingSideCastleWild:
9511 case WhiteQueenSideCastleWild:
9512 case BlackKingSideCastleWild:
9513 case BlackQueenSideCastleWild:
9515 case WhiteHSideCastleFR:
9516 case WhiteASideCastleFR:
9517 case BlackHSideCastleFR:
9518 case BlackASideCastleFR:
9520 fromX = currentMoveString[0] - AAA;
9521 fromY = currentMoveString[1] - ONE;
9522 toX = currentMoveString[2] - AAA;
9523 toY = currentMoveString[3] - ONE;
9524 promoChar = currentMoveString[4];
9528 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9529 fromX = moveType == WhiteDrop ?
9530 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9531 (int) CharToPiece(ToLower(currentMoveString[0]));
9533 toX = currentMoveString[2] - AAA;
9534 toY = currentMoveString[3] - ONE;
9535 promoChar = NULLCHAR;
9539 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9540 if (appData.debugMode) {
9541 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9542 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9543 setbuf(debugFP, NULL);
9545 DisplayError(buf, 0);
9547 case ImpossibleMove:
9549 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9550 if (appData.debugMode) {
9551 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9552 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9553 setbuf(debugFP, NULL);
9555 DisplayError(buf, 0);
9558 if (boardIndex < backwardMostMove) {
9559 /* Oops, gap. How did that happen? */
9560 DisplayError(_("Gap in move list"), 0);
9563 backwardMostMove = blackPlaysFirst ? 1 : 0;
9564 if (boardIndex > forwardMostMove) {
9565 forwardMostMove = boardIndex;
9569 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9570 strcat(parseList[boardIndex-1], " ");
9571 strcat(parseList[boardIndex-1], yy_text);
9583 case GameUnfinished:
9584 if (gameMode == IcsExamining) {
9585 if (boardIndex < backwardMostMove) {
9586 /* Oops, gap. How did that happen? */
9589 backwardMostMove = blackPlaysFirst ? 1 : 0;
9592 gameInfo.result = moveType;
9593 p = strchr(yy_text, '{');
9594 if (p == NULL) p = strchr(yy_text, '(');
9597 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9599 q = strchr(p, *p == '{' ? '}' : ')');
9600 if (q != NULL) *q = NULLCHAR;
9603 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9604 gameInfo.resultDetails = StrSave(p);
9607 if (boardIndex >= forwardMostMove &&
9608 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9609 backwardMostMove = blackPlaysFirst ? 1 : 0;
9612 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9613 fromY, fromX, toY, toX, promoChar,
9614 parseList[boardIndex]);
9615 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9616 /* currentMoveString is set as a side-effect of yylex */
9617 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9618 strcat(moveList[boardIndex], "\n");
9620 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9621 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9627 if(gameInfo.variant != VariantShogi)
9628 strcat(parseList[boardIndex - 1], "+");
9632 strcat(parseList[boardIndex - 1], "#");
9639 /* Apply a move to the given board */
9641 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9643 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9644 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9646 /* [HGM] compute & store e.p. status and castling rights for new position */
9647 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9649 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9650 oldEP = (signed char)board[EP_STATUS];
9651 board[EP_STATUS] = EP_NONE;
9653 if (fromY == DROP_RANK) {
9655 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9656 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9659 piece = board[toY][toX] = (ChessSquare) fromX;
9664 if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9665 victim = board[killY][killX],
9666 board[killY][killX] = EmptySquare,
9667 board[EP_STATUS] = EP_CAPTURE;
9669 if( board[toY][toX] != EmptySquare ) {
9670 board[EP_STATUS] = EP_CAPTURE;
9671 if( (fromX != toX || fromY != toY) && // not igui!
9672 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9673 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
9674 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9678 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9679 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9680 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9682 if( board[fromY][fromX] == WhitePawn ) {
9683 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9684 board[EP_STATUS] = EP_PAWN_MOVE;
9686 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9687 gameInfo.variant != VariantBerolina || toX < fromX)
9688 board[EP_STATUS] = toX | berolina;
9689 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9690 gameInfo.variant != VariantBerolina || toX > fromX)
9691 board[EP_STATUS] = toX;
9694 if( board[fromY][fromX] == BlackPawn ) {
9695 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9696 board[EP_STATUS] = EP_PAWN_MOVE;
9697 if( toY-fromY== -2) {
9698 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9699 gameInfo.variant != VariantBerolina || toX < fromX)
9700 board[EP_STATUS] = toX | berolina;
9701 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9702 gameInfo.variant != VariantBerolina || toX > fromX)
9703 board[EP_STATUS] = toX;
9707 for(i=0; i<nrCastlingRights; i++) {
9708 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9709 board[CASTLING][i] == toX && castlingRank[i] == toY
9710 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9713 if(gameInfo.variant == VariantSChess) { // update virginity
9714 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9715 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9716 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9717 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9720 if (fromX == toX && fromY == toY) return;
9722 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9723 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9724 if(gameInfo.variant == VariantKnightmate)
9725 king += (int) WhiteUnicorn - (int) WhiteKing;
9727 /* Code added by Tord: */
9728 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9729 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9730 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9731 board[fromY][fromX] = EmptySquare;
9732 board[toY][toX] = EmptySquare;
9733 if((toX > fromX) != (piece == WhiteRook)) {
9734 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9736 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9738 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9739 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9740 board[fromY][fromX] = EmptySquare;
9741 board[toY][toX] = EmptySquare;
9742 if((toX > fromX) != (piece == BlackRook)) {
9743 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9745 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9747 /* End of code added by Tord */
9749 } else if (board[fromY][fromX] == king
9750 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9751 && toY == fromY && toX > fromX+1) {
9752 board[fromY][fromX] = EmptySquare;
9753 board[toY][toX] = king;
9754 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9755 board[fromY][BOARD_RGHT-1] = EmptySquare;
9756 } else if (board[fromY][fromX] == king
9757 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9758 && toY == fromY && toX < fromX-1) {
9759 board[fromY][fromX] = EmptySquare;
9760 board[toY][toX] = king;
9761 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9762 board[fromY][BOARD_LEFT] = EmptySquare;
9763 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9764 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9765 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9767 /* white pawn promotion */
9768 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9769 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9770 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9771 board[fromY][fromX] = EmptySquare;
9772 } else if ((fromY >= BOARD_HEIGHT>>1)
9773 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9775 && gameInfo.variant != VariantXiangqi
9776 && gameInfo.variant != VariantBerolina
9777 && (board[fromY][fromX] == WhitePawn)
9778 && (board[toY][toX] == EmptySquare)) {
9779 board[fromY][fromX] = EmptySquare;
9780 board[toY][toX] = WhitePawn;
9781 captured = board[toY - 1][toX];
9782 board[toY - 1][toX] = EmptySquare;
9783 } else if ((fromY == BOARD_HEIGHT-4)
9785 && gameInfo.variant == VariantBerolina
9786 && (board[fromY][fromX] == WhitePawn)
9787 && (board[toY][toX] == EmptySquare)) {
9788 board[fromY][fromX] = EmptySquare;
9789 board[toY][toX] = WhitePawn;
9790 if(oldEP & EP_BEROLIN_A) {
9791 captured = board[fromY][fromX-1];
9792 board[fromY][fromX-1] = EmptySquare;
9793 }else{ captured = board[fromY][fromX+1];
9794 board[fromY][fromX+1] = EmptySquare;
9796 } else if (board[fromY][fromX] == king
9797 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9798 && toY == fromY && toX > fromX+1) {
9799 board[fromY][fromX] = EmptySquare;
9800 board[toY][toX] = king;
9801 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9802 board[fromY][BOARD_RGHT-1] = EmptySquare;
9803 } else if (board[fromY][fromX] == king
9804 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9805 && toY == fromY && toX < fromX-1) {
9806 board[fromY][fromX] = EmptySquare;
9807 board[toY][toX] = king;
9808 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9809 board[fromY][BOARD_LEFT] = EmptySquare;
9810 } else if (fromY == 7 && fromX == 3
9811 && board[fromY][fromX] == BlackKing
9812 && toY == 7 && toX == 5) {
9813 board[fromY][fromX] = EmptySquare;
9814 board[toY][toX] = BlackKing;
9815 board[fromY][7] = EmptySquare;
9816 board[toY][4] = BlackRook;
9817 } else if (fromY == 7 && fromX == 3
9818 && board[fromY][fromX] == BlackKing
9819 && toY == 7 && toX == 1) {
9820 board[fromY][fromX] = EmptySquare;
9821 board[toY][toX] = BlackKing;
9822 board[fromY][0] = EmptySquare;
9823 board[toY][2] = BlackRook;
9824 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9825 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9826 && toY < promoRank && promoChar
9828 /* black pawn promotion */
9829 board[toY][toX] = CharToPiece(ToLower(promoChar));
9830 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9831 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9832 board[fromY][fromX] = EmptySquare;
9833 } else if ((fromY < BOARD_HEIGHT>>1)
9834 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9836 && gameInfo.variant != VariantXiangqi
9837 && gameInfo.variant != VariantBerolina
9838 && (board[fromY][fromX] == BlackPawn)
9839 && (board[toY][toX] == EmptySquare)) {
9840 board[fromY][fromX] = EmptySquare;
9841 board[toY][toX] = BlackPawn;
9842 captured = board[toY + 1][toX];
9843 board[toY + 1][toX] = EmptySquare;
9844 } else if ((fromY == 3)
9846 && gameInfo.variant == VariantBerolina
9847 && (board[fromY][fromX] == BlackPawn)
9848 && (board[toY][toX] == EmptySquare)) {
9849 board[fromY][fromX] = EmptySquare;
9850 board[toY][toX] = BlackPawn;
9851 if(oldEP & EP_BEROLIN_A) {
9852 captured = board[fromY][fromX-1];
9853 board[fromY][fromX-1] = EmptySquare;
9854 }else{ captured = board[fromY][fromX+1];
9855 board[fromY][fromX+1] = EmptySquare;
9858 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9859 board[fromY][fromX] = EmptySquare;
9860 board[toY][toX] = piece;
9864 if (gameInfo.holdingsWidth != 0) {
9866 /* !!A lot more code needs to be written to support holdings */
9867 /* [HGM] OK, so I have written it. Holdings are stored in the */
9868 /* penultimate board files, so they are automaticlly stored */
9869 /* in the game history. */
9870 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9871 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9872 /* Delete from holdings, by decreasing count */
9873 /* and erasing image if necessary */
9874 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9875 if(p < (int) BlackPawn) { /* white drop */
9876 p -= (int)WhitePawn;
9877 p = PieceToNumber((ChessSquare)p);
9878 if(p >= gameInfo.holdingsSize) p = 0;
9879 if(--board[p][BOARD_WIDTH-2] <= 0)
9880 board[p][BOARD_WIDTH-1] = EmptySquare;
9881 if((int)board[p][BOARD_WIDTH-2] < 0)
9882 board[p][BOARD_WIDTH-2] = 0;
9883 } else { /* black drop */
9884 p -= (int)BlackPawn;
9885 p = PieceToNumber((ChessSquare)p);
9886 if(p >= gameInfo.holdingsSize) p = 0;
9887 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9888 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9889 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9890 board[BOARD_HEIGHT-1-p][1] = 0;
9893 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9894 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9895 /* [HGM] holdings: Add to holdings, if holdings exist */
9896 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9897 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9898 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9901 if (p >= (int) BlackPawn) {
9902 p -= (int)BlackPawn;
9903 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9904 /* in Shogi restore piece to its original first */
9905 captured = (ChessSquare) (DEMOTED captured);
9908 p = PieceToNumber((ChessSquare)p);
9909 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9910 board[p][BOARD_WIDTH-2]++;
9911 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9913 p -= (int)WhitePawn;
9914 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9915 captured = (ChessSquare) (DEMOTED captured);
9918 p = PieceToNumber((ChessSquare)p);
9919 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9920 board[BOARD_HEIGHT-1-p][1]++;
9921 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9924 } else if (gameInfo.variant == VariantAtomic) {
9925 if (captured != EmptySquare) {
9927 for (y = toY-1; y <= toY+1; y++) {
9928 for (x = toX-1; x <= toX+1; x++) {
9929 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9930 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9931 board[y][x] = EmptySquare;
9935 board[toY][toX] = EmptySquare;
9938 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9939 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9941 if(promoChar == '+') {
9942 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9943 board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9944 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9945 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9946 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9947 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9948 board[toY][toX] = newPiece;
9950 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9951 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9952 // [HGM] superchess: take promotion piece out of holdings
9953 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9954 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9955 if(!--board[k][BOARD_WIDTH-2])
9956 board[k][BOARD_WIDTH-1] = EmptySquare;
9958 if(!--board[BOARD_HEIGHT-1-k][1])
9959 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9964 /* Updates forwardMostMove */
9966 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9968 int x = toX, y = toY;
9969 char *s = parseList[forwardMostMove];
9970 ChessSquare p = boards[forwardMostMove][toY][toX];
9971 // forwardMostMove++; // [HGM] bare: moved downstream
9973 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
9974 (void) CoordsToAlgebraic(boards[forwardMostMove],
9975 PosFlags(forwardMostMove),
9976 fromY, fromX, y, x, promoChar,
9978 if(killX >= 0 && killY >= 0)
9979 sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
9981 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9982 int timeLeft; static int lastLoadFlag=0; int king, piece;
9983 piece = boards[forwardMostMove][fromY][fromX];
9984 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9985 if(gameInfo.variant == VariantKnightmate)
9986 king += (int) WhiteUnicorn - (int) WhiteKing;
9987 if(forwardMostMove == 0) {
9988 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9989 fprintf(serverMoves, "%s;", UserName());
9990 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9991 fprintf(serverMoves, "%s;", second.tidy);
9992 fprintf(serverMoves, "%s;", first.tidy);
9993 if(gameMode == MachinePlaysWhite)
9994 fprintf(serverMoves, "%s;", UserName());
9995 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9996 fprintf(serverMoves, "%s;", second.tidy);
9997 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9998 lastLoadFlag = loadFlag;
10000 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10001 // print castling suffix
10002 if( toY == fromY && piece == king ) {
10004 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10006 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10009 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10010 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10011 boards[forwardMostMove][toY][toX] == EmptySquare
10012 && fromX != toX && fromY != toY)
10013 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10014 // promotion suffix
10015 if(promoChar != NULLCHAR) {
10016 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10017 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10018 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10019 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10022 char buf[MOVE_LEN*2], *p; int len;
10023 fprintf(serverMoves, "/%d/%d",
10024 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10025 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10026 else timeLeft = blackTimeRemaining/1000;
10027 fprintf(serverMoves, "/%d", timeLeft);
10028 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10029 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10030 if(p = strchr(buf, '=')) *p = NULLCHAR;
10031 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10032 fprintf(serverMoves, "/%s", buf);
10034 fflush(serverMoves);
10037 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10038 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10041 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10042 if (commentList[forwardMostMove+1] != NULL) {
10043 free(commentList[forwardMostMove+1]);
10044 commentList[forwardMostMove+1] = NULL;
10046 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10047 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10048 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10049 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10050 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10051 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10052 adjustedClock = FALSE;
10053 gameInfo.result = GameUnfinished;
10054 if (gameInfo.resultDetails != NULL) {
10055 free(gameInfo.resultDetails);
10056 gameInfo.resultDetails = NULL;
10058 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10059 moveList[forwardMostMove - 1]);
10060 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10066 if(gameInfo.variant != VariantShogi)
10067 strcat(parseList[forwardMostMove - 1], "+");
10071 strcat(parseList[forwardMostMove - 1], "#");
10076 /* Updates currentMove if not pausing */
10078 ShowMove (int fromX, int fromY, int toX, int toY)
10080 int instant = (gameMode == PlayFromGameFile) ?
10081 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10082 if(appData.noGUI) return;
10083 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10085 if (forwardMostMove == currentMove + 1) {
10086 AnimateMove(boards[forwardMostMove - 1],
10087 fromX, fromY, toX, toY);
10090 currentMove = forwardMostMove;
10093 killX = killY = -1; // [HGM] lion: used up
10095 if (instant) return;
10097 DisplayMove(currentMove - 1);
10098 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10099 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10100 SetHighlights(fromX, fromY, toX, toY);
10103 DrawPosition(FALSE, boards[currentMove]);
10104 DisplayBothClocks();
10105 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10109 SendEgtPath (ChessProgramState *cps)
10110 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10111 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10113 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10116 char c, *q = name+1, *r, *s;
10118 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10119 while(*p && *p != ',') *q++ = *p++;
10120 *q++ = ':'; *q = 0;
10121 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10122 strcmp(name, ",nalimov:") == 0 ) {
10123 // take nalimov path from the menu-changeable option first, if it is defined
10124 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10125 SendToProgram(buf,cps); // send egtbpath command for nalimov
10127 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10128 (s = StrStr(appData.egtFormats, name)) != NULL) {
10129 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10130 s = r = StrStr(s, ":") + 1; // beginning of path info
10131 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10132 c = *r; *r = 0; // temporarily null-terminate path info
10133 *--q = 0; // strip of trailig ':' from name
10134 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10136 SendToProgram(buf,cps); // send egtbpath command for this format
10138 if(*p == ',') p++; // read away comma to position for next format name
10143 NonStandardBoardSize ()
10145 /* [HGM] Awkward testing. Should really be a table */
10146 int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10147 if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10148 if( gameInfo.variant == VariantXiangqi )
10149 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10150 if( gameInfo.variant == VariantShogi )
10151 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10152 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10153 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10154 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10155 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10156 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10157 if( gameInfo.variant == VariantCourier )
10158 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10159 if( gameInfo.variant == VariantSuper )
10160 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10161 if( gameInfo.variant == VariantGreat )
10162 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10163 if( gameInfo.variant == VariantSChess )
10164 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10165 if( gameInfo.variant == VariantGrand )
10166 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10167 if( gameInfo.variant == VariantChu )
10168 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
10173 InitChessProgram (ChessProgramState *cps, int setup)
10174 /* setup needed to setup FRC opening position */
10176 char buf[MSG_SIZ], b[MSG_SIZ];
10177 if (appData.noChessProgram) return;
10178 hintRequested = FALSE;
10179 bookRequested = FALSE;
10181 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10182 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10183 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10184 if(cps->memSize) { /* [HGM] memory */
10185 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10186 SendToProgram(buf, cps);
10188 SendEgtPath(cps); /* [HGM] EGT */
10189 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10190 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10191 SendToProgram(buf, cps);
10194 SendToProgram(cps->initString, cps);
10195 if (gameInfo.variant != VariantNormal &&
10196 gameInfo.variant != VariantLoadable
10197 /* [HGM] also send variant if board size non-standard */
10198 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10200 char *v = VariantName(gameInfo.variant);
10201 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10202 /* [HGM] in protocol 1 we have to assume all variants valid */
10203 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10204 DisplayFatalError(buf, 0, 1);
10208 if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10209 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10210 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10211 /* [HGM] varsize: try first if this defiant size variant is specifically known */
10212 if(StrStr(cps->variants, b) == NULL) {
10213 // specific sized variant not known, check if general sizing allowed
10214 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10215 if(StrStr(cps->variants, "boardsize") == NULL) {
10216 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10217 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10218 DisplayFatalError(buf, 0, 1);
10221 /* [HGM] here we really should compare with the maximum supported board size */
10224 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10225 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10226 SendToProgram(buf, cps);
10228 currentlyInitializedVariant = gameInfo.variant;
10230 /* [HGM] send opening position in FRC to first engine */
10232 SendToProgram("force\n", cps);
10234 /* engine is now in force mode! Set flag to wake it up after first move. */
10235 setboardSpoiledMachineBlack = 1;
10238 if (cps->sendICS) {
10239 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10240 SendToProgram(buf, cps);
10242 cps->maybeThinking = FALSE;
10243 cps->offeredDraw = 0;
10244 if (!appData.icsActive) {
10245 SendTimeControl(cps, movesPerSession, timeControl,
10246 timeIncrement, appData.searchDepth,
10249 if (appData.showThinking
10250 // [HGM] thinking: four options require thinking output to be sent
10251 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10253 SendToProgram("post\n", cps);
10255 SendToProgram("hard\n", cps);
10256 if (!appData.ponderNextMove) {
10257 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10258 it without being sure what state we are in first. "hard"
10259 is not a toggle, so that one is OK.
10261 SendToProgram("easy\n", cps);
10263 if (cps->usePing) {
10264 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10265 SendToProgram(buf, cps);
10267 cps->initDone = TRUE;
10268 ClearEngineOutputPane(cps == &second);
10273 ResendOptions (ChessProgramState *cps)
10274 { // send the stored value of the options
10277 Option *opt = cps->option;
10278 for(i=0; i<cps->nrOptions; i++, opt++) {
10279 switch(opt->type) {
10283 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10286 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10289 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10295 SendToProgram(buf, cps);
10300 StartChessProgram (ChessProgramState *cps)
10305 if (appData.noChessProgram) return;
10306 cps->initDone = FALSE;
10308 if (strcmp(cps->host, "localhost") == 0) {
10309 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10310 } else if (*appData.remoteShell == NULLCHAR) {
10311 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10313 if (*appData.remoteUser == NULLCHAR) {
10314 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10317 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10318 cps->host, appData.remoteUser, cps->program);
10320 err = StartChildProcess(buf, "", &cps->pr);
10324 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10325 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10326 if(cps != &first) return;
10327 appData.noChessProgram = TRUE;
10330 // DisplayFatalError(buf, err, 1);
10331 // cps->pr = NoProc;
10332 // cps->isr = NULL;
10336 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10337 if (cps->protocolVersion > 1) {
10338 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10339 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10340 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10341 cps->comboCnt = 0; // and values of combo boxes
10343 SendToProgram(buf, cps);
10344 if(cps->reload) ResendOptions(cps);
10346 SendToProgram("xboard\n", cps);
10351 TwoMachinesEventIfReady P((void))
10353 static int curMess = 0;
10354 if (first.lastPing != first.lastPong) {
10355 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10356 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10359 if (second.lastPing != second.lastPong) {
10360 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10361 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10364 DisplayMessage("", ""); curMess = 0;
10365 TwoMachinesEvent();
10369 MakeName (char *template)
10373 static char buf[MSG_SIZ];
10377 clock = time((time_t *)NULL);
10378 tm = localtime(&clock);
10380 while(*p++ = *template++) if(p[-1] == '%') {
10381 switch(*template++) {
10382 case 0: *p = 0; return buf;
10383 case 'Y': i = tm->tm_year+1900; break;
10384 case 'y': i = tm->tm_year-100; break;
10385 case 'M': i = tm->tm_mon+1; break;
10386 case 'd': i = tm->tm_mday; break;
10387 case 'h': i = tm->tm_hour; break;
10388 case 'm': i = tm->tm_min; break;
10389 case 's': i = tm->tm_sec; break;
10392 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10398 CountPlayers (char *p)
10401 while(p = strchr(p, '\n')) p++, n++; // count participants
10406 WriteTourneyFile (char *results, FILE *f)
10407 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10408 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10409 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10410 // create a file with tournament description
10411 fprintf(f, "-participants {%s}\n", appData.participants);
10412 fprintf(f, "-seedBase %d\n", appData.seedBase);
10413 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10414 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10415 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10416 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10417 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10418 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10419 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10420 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10421 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10422 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10423 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10424 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10425 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10426 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10427 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10428 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10429 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10430 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10431 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10432 fprintf(f, "-smpCores %d\n", appData.smpCores);
10434 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10436 fprintf(f, "-mps %d\n", appData.movesPerSession);
10437 fprintf(f, "-tc %s\n", appData.timeControl);
10438 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10440 fprintf(f, "-results \"%s\"\n", results);
10445 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10448 Substitute (char *participants, int expunge)
10450 int i, changed, changes=0, nPlayers=0;
10451 char *p, *q, *r, buf[MSG_SIZ];
10452 if(participants == NULL) return;
10453 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10454 r = p = participants; q = appData.participants;
10455 while(*p && *p == *q) {
10456 if(*p == '\n') r = p+1, nPlayers++;
10459 if(*p) { // difference
10460 while(*p && *p++ != '\n');
10461 while(*q && *q++ != '\n');
10462 changed = nPlayers;
10463 changes = 1 + (strcmp(p, q) != 0);
10465 if(changes == 1) { // a single engine mnemonic was changed
10466 q = r; while(*q) nPlayers += (*q++ == '\n');
10467 p = buf; while(*r && (*p = *r++) != '\n') p++;
10469 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10470 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10471 if(mnemonic[i]) { // The substitute is valid
10473 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10474 flock(fileno(f), LOCK_EX);
10475 ParseArgsFromFile(f);
10476 fseek(f, 0, SEEK_SET);
10477 FREE(appData.participants); appData.participants = participants;
10478 if(expunge) { // erase results of replaced engine
10479 int len = strlen(appData.results), w, b, dummy;
10480 for(i=0; i<len; i++) {
10481 Pairing(i, nPlayers, &w, &b, &dummy);
10482 if((w == changed || b == changed) && appData.results[i] == '*') {
10483 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10488 for(i=0; i<len; i++) {
10489 Pairing(i, nPlayers, &w, &b, &dummy);
10490 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10493 WriteTourneyFile(appData.results, f);
10494 fclose(f); // release lock
10497 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10499 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10500 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10501 free(participants);
10506 CheckPlayers (char *participants)
10509 char buf[MSG_SIZ], *p;
10510 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10511 while(p = strchr(participants, '\n')) {
10513 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10515 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10517 DisplayError(buf, 0);
10521 participants = p + 1;
10527 CreateTourney (char *name)
10530 if(matchMode && strcmp(name, appData.tourneyFile)) {
10531 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10533 if(name[0] == NULLCHAR) {
10534 if(appData.participants[0])
10535 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10538 f = fopen(name, "r");
10539 if(f) { // file exists
10540 ASSIGN(appData.tourneyFile, name);
10541 ParseArgsFromFile(f); // parse it
10543 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10544 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10545 DisplayError(_("Not enough participants"), 0);
10548 if(CheckPlayers(appData.participants)) return 0;
10549 ASSIGN(appData.tourneyFile, name);
10550 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10551 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10554 appData.noChessProgram = FALSE;
10555 appData.clockMode = TRUE;
10561 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10563 char buf[MSG_SIZ], *p, *q;
10564 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10565 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10566 skip = !all && group[0]; // if group requested, we start in skip mode
10567 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10568 p = names; q = buf; header = 0;
10569 while(*p && *p != '\n') *q++ = *p++;
10571 if(*p == '\n') p++;
10572 if(buf[0] == '#') {
10573 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10574 depth++; // we must be entering a new group
10575 if(all) continue; // suppress printing group headers when complete list requested
10577 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10579 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10580 if(engineList[i]) free(engineList[i]);
10581 engineList[i] = strdup(buf);
10582 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10583 if(engineMnemonic[i]) free(engineMnemonic[i]);
10584 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10586 sscanf(q + 8, "%s", buf + strlen(buf));
10589 engineMnemonic[i] = strdup(buf);
10592 engineList[i] = engineMnemonic[i] = NULL;
10596 // following implemented as macro to avoid type limitations
10597 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10600 SwapEngines (int n)
10601 { // swap settings for first engine and other engine (so far only some selected options)
10606 SWAP(chessProgram, p)
10608 SWAP(hasOwnBookUCI, h)
10609 SWAP(protocolVersion, h)
10611 SWAP(scoreIsAbsolute, h)
10616 SWAP(engOptions, p)
10617 SWAP(engInitString, p)
10618 SWAP(computerString, p)
10620 SWAP(fenOverride, p)
10622 SWAP(accumulateTC, h)
10627 GetEngineLine (char *s, int n)
10631 extern char *icsNames;
10632 if(!s || !*s) return 0;
10633 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10634 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10635 if(!mnemonic[i]) return 0;
10636 if(n == 11) return 1; // just testing if there was a match
10637 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10638 if(n == 1) SwapEngines(n);
10639 ParseArgsFromString(buf);
10640 if(n == 1) SwapEngines(n);
10641 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10642 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10643 ParseArgsFromString(buf);
10649 SetPlayer (int player, char *p)
10650 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10652 char buf[MSG_SIZ], *engineName;
10653 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10654 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10655 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10657 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10658 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10659 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10660 ParseArgsFromString(buf);
10661 } else { // no engine with this nickname is installed!
10662 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10663 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10664 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10666 DisplayError(buf, 0);
10673 char *recentEngines;
10676 RecentEngineEvent (int nr)
10679 // SwapEngines(1); // bump first to second
10680 // ReplaceEngine(&second, 1); // and load it there
10681 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10682 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10683 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10684 ReplaceEngine(&first, 0);
10685 FloatToFront(&appData.recentEngineList, command[n]);
10690 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10691 { // determine players from game number
10692 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10694 if(appData.tourneyType == 0) {
10695 roundsPerCycle = (nPlayers - 1) | 1;
10696 pairingsPerRound = nPlayers / 2;
10697 } else if(appData.tourneyType > 0) {
10698 roundsPerCycle = nPlayers - appData.tourneyType;
10699 pairingsPerRound = appData.tourneyType;
10701 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10702 gamesPerCycle = gamesPerRound * roundsPerCycle;
10703 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10704 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10705 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10706 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10707 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10708 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10710 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10711 if(appData.roundSync) *syncInterval = gamesPerRound;
10713 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10715 if(appData.tourneyType == 0) {
10716 if(curPairing == (nPlayers-1)/2 ) {
10717 *whitePlayer = curRound;
10718 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10720 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10721 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10722 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10723 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10725 } else if(appData.tourneyType > 1) {
10726 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10727 *whitePlayer = curRound + appData.tourneyType;
10728 } else if(appData.tourneyType > 0) {
10729 *whitePlayer = curPairing;
10730 *blackPlayer = curRound + appData.tourneyType;
10733 // take care of white/black alternation per round.
10734 // For cycles and games this is already taken care of by default, derived from matchGame!
10735 return curRound & 1;
10739 NextTourneyGame (int nr, int *swapColors)
10740 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10742 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10744 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10745 tf = fopen(appData.tourneyFile, "r");
10746 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10747 ParseArgsFromFile(tf); fclose(tf);
10748 InitTimeControls(); // TC might be altered from tourney file
10750 nPlayers = CountPlayers(appData.participants); // count participants
10751 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10752 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10755 p = q = appData.results;
10756 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10757 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10758 DisplayMessage(_("Waiting for other game(s)"),"");
10759 waitingForGame = TRUE;
10760 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10763 waitingForGame = FALSE;
10766 if(appData.tourneyType < 0) {
10767 if(nr>=0 && !pairingReceived) {
10769 if(pairing.pr == NoProc) {
10770 if(!appData.pairingEngine[0]) {
10771 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10774 StartChessProgram(&pairing); // starts the pairing engine
10776 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10777 SendToProgram(buf, &pairing);
10778 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10779 SendToProgram(buf, &pairing);
10780 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10782 pairingReceived = 0; // ... so we continue here
10784 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10785 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10786 matchGame = 1; roundNr = nr / syncInterval + 1;
10789 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10791 // redefine engines, engine dir, etc.
10792 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10793 if(first.pr == NoProc) {
10794 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10795 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10797 if(second.pr == NoProc) {
10799 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10800 SwapEngines(1); // and make that valid for second engine by swapping
10801 InitEngine(&second, 1);
10803 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10804 UpdateLogos(FALSE); // leave display to ModeHiglight()
10810 { // performs game initialization that does not invoke engines, and then tries to start the game
10811 int res, firstWhite, swapColors = 0;
10812 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10813 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
10815 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10816 if(strcmp(buf, currentDebugFile)) { // name has changed
10817 FILE *f = fopen(buf, "w");
10818 if(f) { // if opening the new file failed, just keep using the old one
10819 ASSIGN(currentDebugFile, buf);
10823 if(appData.serverFileName) {
10824 if(serverFP) fclose(serverFP);
10825 serverFP = fopen(appData.serverFileName, "w");
10826 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10827 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10831 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10832 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10833 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10834 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10835 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10836 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10837 Reset(FALSE, first.pr != NoProc);
10838 res = LoadGameOrPosition(matchGame); // setup game
10839 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10840 if(!res) return; // abort when bad game/pos file
10841 TwoMachinesEvent();
10845 UserAdjudicationEvent (int result)
10847 ChessMove gameResult = GameIsDrawn;
10850 gameResult = WhiteWins;
10852 else if( result < 0 ) {
10853 gameResult = BlackWins;
10856 if( gameMode == TwoMachinesPlay ) {
10857 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10862 // [HGM] save: calculate checksum of game to make games easily identifiable
10864 StringCheckSum (char *s)
10867 if(s==NULL) return 0;
10868 while(*s) i = i*259 + *s++;
10876 for(i=backwardMostMove; i<forwardMostMove; i++) {
10877 sum += pvInfoList[i].depth;
10878 sum += StringCheckSum(parseList[i]);
10879 sum += StringCheckSum(commentList[i]);
10882 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10883 return sum + StringCheckSum(commentList[i]);
10884 } // end of save patch
10887 GameEnds (ChessMove result, char *resultDetails, int whosays)
10889 GameMode nextGameMode;
10891 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10893 if(endingGame) return; /* [HGM] crash: forbid recursion */
10895 if(twoBoards) { // [HGM] dual: switch back to one board
10896 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10897 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10899 if (appData.debugMode) {
10900 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10901 result, resultDetails ? resultDetails : "(null)", whosays);
10904 fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10906 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10908 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10909 /* If we are playing on ICS, the server decides when the
10910 game is over, but the engine can offer to draw, claim
10914 if (appData.zippyPlay && first.initDone) {
10915 if (result == GameIsDrawn) {
10916 /* In case draw still needs to be claimed */
10917 SendToICS(ics_prefix);
10918 SendToICS("draw\n");
10919 } else if (StrCaseStr(resultDetails, "resign")) {
10920 SendToICS(ics_prefix);
10921 SendToICS("resign\n");
10925 endingGame = 0; /* [HGM] crash */
10929 /* If we're loading the game from a file, stop */
10930 if (whosays == GE_FILE) {
10931 (void) StopLoadGameTimer();
10935 /* Cancel draw offers */
10936 first.offeredDraw = second.offeredDraw = 0;
10938 /* If this is an ICS game, only ICS can really say it's done;
10939 if not, anyone can. */
10940 isIcsGame = (gameMode == IcsPlayingWhite ||
10941 gameMode == IcsPlayingBlack ||
10942 gameMode == IcsObserving ||
10943 gameMode == IcsExamining);
10945 if (!isIcsGame || whosays == GE_ICS) {
10946 /* OK -- not an ICS game, or ICS said it was done */
10948 if (!isIcsGame && !appData.noChessProgram)
10949 SetUserThinkingEnables();
10951 /* [HGM] if a machine claims the game end we verify this claim */
10952 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10953 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10955 ChessMove trueResult = (ChessMove) -1;
10957 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10958 first.twoMachinesColor[0] :
10959 second.twoMachinesColor[0] ;
10961 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10962 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10963 /* [HGM] verify: engine mate claims accepted if they were flagged */
10964 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10966 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10967 /* [HGM] verify: engine mate claims accepted if they were flagged */
10968 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10970 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10971 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10974 // now verify win claims, but not in drop games, as we don't understand those yet
10975 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10976 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10977 (result == WhiteWins && claimer == 'w' ||
10978 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10979 if (appData.debugMode) {
10980 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10981 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10983 if(result != trueResult) {
10984 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10985 result = claimer == 'w' ? BlackWins : WhiteWins;
10986 resultDetails = buf;
10989 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10990 && (forwardMostMove <= backwardMostMove ||
10991 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10992 (claimer=='b')==(forwardMostMove&1))
10994 /* [HGM] verify: draws that were not flagged are false claims */
10995 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10996 result = claimer == 'w' ? BlackWins : WhiteWins;
10997 resultDetails = buf;
10999 /* (Claiming a loss is accepted no questions asked!) */
11000 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11001 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11002 result = GameUnfinished;
11003 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11005 /* [HGM] bare: don't allow bare King to win */
11006 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11007 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11008 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11009 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11010 && result != GameIsDrawn)
11011 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11012 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11013 int p = (signed char)boards[forwardMostMove][i][j] - color;
11014 if(p >= 0 && p <= (int)WhiteKing) k++;
11016 if (appData.debugMode) {
11017 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11018 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11021 result = GameIsDrawn;
11022 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11023 resultDetails = buf;
11029 if(serverMoves != NULL && !loadFlag) { char c = '=';
11030 if(result==WhiteWins) c = '+';
11031 if(result==BlackWins) c = '-';
11032 if(resultDetails != NULL)
11033 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11035 if (resultDetails != NULL) {
11036 gameInfo.result = result;
11037 gameInfo.resultDetails = StrSave(resultDetails);
11039 /* display last move only if game was not loaded from file */
11040 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11041 DisplayMove(currentMove - 1);
11043 if (forwardMostMove != 0) {
11044 if (gameMode != PlayFromGameFile && gameMode != EditGame
11045 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11047 if (*appData.saveGameFile != NULLCHAR) {
11048 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11049 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11051 SaveGameToFile(appData.saveGameFile, TRUE);
11052 } else if (appData.autoSaveGames) {
11053 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11055 if (*appData.savePositionFile != NULLCHAR) {
11056 SavePositionToFile(appData.savePositionFile);
11058 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11062 /* Tell program how game ended in case it is learning */
11063 /* [HGM] Moved this to after saving the PGN, just in case */
11064 /* engine died and we got here through time loss. In that */
11065 /* case we will get a fatal error writing the pipe, which */
11066 /* would otherwise lose us the PGN. */
11067 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11068 /* output during GameEnds should never be fatal anymore */
11069 if (gameMode == MachinePlaysWhite ||
11070 gameMode == MachinePlaysBlack ||
11071 gameMode == TwoMachinesPlay ||
11072 gameMode == IcsPlayingWhite ||
11073 gameMode == IcsPlayingBlack ||
11074 gameMode == BeginningOfGame) {
11076 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11078 if (first.pr != NoProc) {
11079 SendToProgram(buf, &first);
11081 if (second.pr != NoProc &&
11082 gameMode == TwoMachinesPlay) {
11083 SendToProgram(buf, &second);
11088 if (appData.icsActive) {
11089 if (appData.quietPlay &&
11090 (gameMode == IcsPlayingWhite ||
11091 gameMode == IcsPlayingBlack)) {
11092 SendToICS(ics_prefix);
11093 SendToICS("set shout 1\n");
11095 nextGameMode = IcsIdle;
11096 ics_user_moved = FALSE;
11097 /* clean up premove. It's ugly when the game has ended and the
11098 * premove highlights are still on the board.
11101 gotPremove = FALSE;
11102 ClearPremoveHighlights();
11103 DrawPosition(FALSE, boards[currentMove]);
11105 if (whosays == GE_ICS) {
11108 if (gameMode == IcsPlayingWhite)
11110 else if(gameMode == IcsPlayingBlack)
11111 PlayIcsLossSound();
11114 if (gameMode == IcsPlayingBlack)
11116 else if(gameMode == IcsPlayingWhite)
11117 PlayIcsLossSound();
11120 PlayIcsDrawSound();
11123 PlayIcsUnfinishedSound();
11126 if(appData.quitNext) { ExitEvent(0); return; }
11127 } else if (gameMode == EditGame ||
11128 gameMode == PlayFromGameFile ||
11129 gameMode == AnalyzeMode ||
11130 gameMode == AnalyzeFile) {
11131 nextGameMode = gameMode;
11133 nextGameMode = EndOfGame;
11138 nextGameMode = gameMode;
11141 if (appData.noChessProgram) {
11142 gameMode = nextGameMode;
11144 endingGame = 0; /* [HGM] crash */
11149 /* Put first chess program into idle state */
11150 if (first.pr != NoProc &&
11151 (gameMode == MachinePlaysWhite ||
11152 gameMode == MachinePlaysBlack ||
11153 gameMode == TwoMachinesPlay ||
11154 gameMode == IcsPlayingWhite ||
11155 gameMode == IcsPlayingBlack ||
11156 gameMode == BeginningOfGame)) {
11157 SendToProgram("force\n", &first);
11158 if (first.usePing) {
11160 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11161 SendToProgram(buf, &first);
11164 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11165 /* Kill off first chess program */
11166 if (first.isr != NULL)
11167 RemoveInputSource(first.isr);
11170 if (first.pr != NoProc) {
11172 DoSleep( appData.delayBeforeQuit );
11173 SendToProgram("quit\n", &first);
11174 DoSleep( appData.delayAfterQuit );
11175 DestroyChildProcess(first.pr, first.useSigterm);
11176 first.reload = TRUE;
11180 if (second.reuse) {
11181 /* Put second chess program into idle state */
11182 if (second.pr != NoProc &&
11183 gameMode == TwoMachinesPlay) {
11184 SendToProgram("force\n", &second);
11185 if (second.usePing) {
11187 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11188 SendToProgram(buf, &second);
11191 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11192 /* Kill off second chess program */
11193 if (second.isr != NULL)
11194 RemoveInputSource(second.isr);
11197 if (second.pr != NoProc) {
11198 DoSleep( appData.delayBeforeQuit );
11199 SendToProgram("quit\n", &second);
11200 DoSleep( appData.delayAfterQuit );
11201 DestroyChildProcess(second.pr, second.useSigterm);
11202 second.reload = TRUE;
11204 second.pr = NoProc;
11207 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11208 char resChar = '=';
11212 if (first.twoMachinesColor[0] == 'w') {
11215 second.matchWins++;
11220 if (first.twoMachinesColor[0] == 'b') {
11223 second.matchWins++;
11226 case GameUnfinished:
11232 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11233 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11234 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11235 ReserveGame(nextGame, resChar); // sets nextGame
11236 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11237 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11238 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11240 if (nextGame <= appData.matchGames && !abortMatch) {
11241 gameMode = nextGameMode;
11242 matchGame = nextGame; // this will be overruled in tourney mode!
11243 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11244 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11245 endingGame = 0; /* [HGM] crash */
11248 gameMode = nextGameMode;
11249 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11250 first.tidy, second.tidy,
11251 first.matchWins, second.matchWins,
11252 appData.matchGames - (first.matchWins + second.matchWins));
11253 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11254 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11255 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11256 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11257 first.twoMachinesColor = "black\n";
11258 second.twoMachinesColor = "white\n";
11260 first.twoMachinesColor = "white\n";
11261 second.twoMachinesColor = "black\n";
11265 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11266 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11268 gameMode = nextGameMode;
11270 endingGame = 0; /* [HGM] crash */
11271 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11272 if(matchMode == TRUE) { // match through command line: exit with or without popup
11274 ToNrEvent(forwardMostMove);
11275 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11277 } else DisplayFatalError(buf, 0, 0);
11278 } else { // match through menu; just stop, with or without popup
11279 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11282 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11283 } else DisplayNote(buf);
11285 if(ranking) free(ranking);
11289 /* Assumes program was just initialized (initString sent).
11290 Leaves program in force mode. */
11292 FeedMovesToProgram (ChessProgramState *cps, int upto)
11296 if (appData.debugMode)
11297 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11298 startedFromSetupPosition ? "position and " : "",
11299 backwardMostMove, upto, cps->which);
11300 if(currentlyInitializedVariant != gameInfo.variant) {
11302 // [HGM] variantswitch: make engine aware of new variant
11303 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11304 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11305 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11306 SendToProgram(buf, cps);
11307 currentlyInitializedVariant = gameInfo.variant;
11309 SendToProgram("force\n", cps);
11310 if (startedFromSetupPosition) {
11311 SendBoard(cps, backwardMostMove);
11312 if (appData.debugMode) {
11313 fprintf(debugFP, "feedMoves\n");
11316 for (i = backwardMostMove; i < upto; i++) {
11317 SendMoveToProgram(i, cps);
11323 ResurrectChessProgram ()
11325 /* The chess program may have exited.
11326 If so, restart it and feed it all the moves made so far. */
11327 static int doInit = 0;
11329 if (appData.noChessProgram) return 1;
11331 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11332 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11333 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11334 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11336 if (first.pr != NoProc) return 1;
11337 StartChessProgram(&first);
11339 InitChessProgram(&first, FALSE);
11340 FeedMovesToProgram(&first, currentMove);
11342 if (!first.sendTime) {
11343 /* can't tell gnuchess what its clock should read,
11344 so we bow to its notion. */
11346 timeRemaining[0][currentMove] = whiteTimeRemaining;
11347 timeRemaining[1][currentMove] = blackTimeRemaining;
11350 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11351 appData.icsEngineAnalyze) && first.analysisSupport) {
11352 SendToProgram("analyze\n", &first);
11353 first.analyzing = TRUE;
11359 * Button procedures
11362 Reset (int redraw, int init)
11366 if (appData.debugMode) {
11367 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11368 redraw, init, gameMode);
11370 CleanupTail(); // [HGM] vari: delete any stored variations
11371 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11372 pausing = pauseExamInvalid = FALSE;
11373 startedFromSetupPosition = blackPlaysFirst = FALSE;
11375 whiteFlag = blackFlag = FALSE;
11376 userOfferedDraw = FALSE;
11377 hintRequested = bookRequested = FALSE;
11378 first.maybeThinking = FALSE;
11379 second.maybeThinking = FALSE;
11380 first.bookSuspend = FALSE; // [HGM] book
11381 second.bookSuspend = FALSE;
11382 thinkOutput[0] = NULLCHAR;
11383 lastHint[0] = NULLCHAR;
11384 ClearGameInfo(&gameInfo);
11385 gameInfo.variant = StringToVariant(appData.variant);
11386 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11387 ics_user_moved = ics_clock_paused = FALSE;
11388 ics_getting_history = H_FALSE;
11390 white_holding[0] = black_holding[0] = NULLCHAR;
11391 ClearProgramStats();
11392 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11396 flipView = appData.flipView;
11397 ClearPremoveHighlights();
11398 gotPremove = FALSE;
11399 alarmSounded = FALSE;
11400 killX = killY = -1; // [HGM] lion
11402 GameEnds(EndOfFile, NULL, GE_PLAYER);
11403 if(appData.serverMovesName != NULL) {
11404 /* [HGM] prepare to make moves file for broadcasting */
11405 clock_t t = clock();
11406 if(serverMoves != NULL) fclose(serverMoves);
11407 serverMoves = fopen(appData.serverMovesName, "r");
11408 if(serverMoves != NULL) {
11409 fclose(serverMoves);
11410 /* delay 15 sec before overwriting, so all clients can see end */
11411 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11413 serverMoves = fopen(appData.serverMovesName, "w");
11417 gameMode = BeginningOfGame;
11419 if(appData.icsActive) gameInfo.variant = VariantNormal;
11420 currentMove = forwardMostMove = backwardMostMove = 0;
11421 MarkTargetSquares(1);
11422 InitPosition(redraw);
11423 for (i = 0; i < MAX_MOVES; i++) {
11424 if (commentList[i] != NULL) {
11425 free(commentList[i]);
11426 commentList[i] = NULL;
11430 timeRemaining[0][0] = whiteTimeRemaining;
11431 timeRemaining[1][0] = blackTimeRemaining;
11433 if (first.pr == NoProc) {
11434 StartChessProgram(&first);
11437 InitChessProgram(&first, startedFromSetupPosition);
11440 DisplayMessage("", "");
11441 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11442 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11443 ClearMap(); // [HGM] exclude: invalidate map
11447 AutoPlayGameLoop ()
11450 if (!AutoPlayOneMove())
11452 if (matchMode || appData.timeDelay == 0)
11454 if (appData.timeDelay < 0)
11456 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11464 ReloadGame(1); // next game
11470 int fromX, fromY, toX, toY;
11472 if (appData.debugMode) {
11473 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11476 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11479 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11480 pvInfoList[currentMove].depth = programStats.depth;
11481 pvInfoList[currentMove].score = programStats.score;
11482 pvInfoList[currentMove].time = 0;
11483 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11484 else { // append analysis of final position as comment
11486 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11487 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11489 programStats.depth = 0;
11492 if (currentMove >= forwardMostMove) {
11493 if(gameMode == AnalyzeFile) {
11494 if(appData.loadGameIndex == -1) {
11495 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11496 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11498 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11501 // gameMode = EndOfGame;
11502 // ModeHighlight();
11504 /* [AS] Clear current move marker at the end of a game */
11505 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11510 toX = moveList[currentMove][2] - AAA;
11511 toY = moveList[currentMove][3] - ONE;
11513 if (moveList[currentMove][1] == '@') {
11514 if (appData.highlightLastMove) {
11515 SetHighlights(-1, -1, toX, toY);
11518 fromX = moveList[currentMove][0] - AAA;
11519 fromY = moveList[currentMove][1] - ONE;
11521 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11523 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11525 if (appData.highlightLastMove) {
11526 SetHighlights(fromX, fromY, toX, toY);
11529 DisplayMove(currentMove);
11530 SendMoveToProgram(currentMove++, &first);
11531 DisplayBothClocks();
11532 DrawPosition(FALSE, boards[currentMove]);
11533 // [HGM] PV info: always display, routine tests if empty
11534 DisplayComment(currentMove - 1, commentList[currentMove]);
11540 LoadGameOneMove (ChessMove readAhead)
11542 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11543 char promoChar = NULLCHAR;
11544 ChessMove moveType;
11545 char move[MSG_SIZ];
11548 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11549 gameMode != AnalyzeMode && gameMode != Training) {
11554 yyboardindex = forwardMostMove;
11555 if (readAhead != EndOfFile) {
11556 moveType = readAhead;
11558 if (gameFileFP == NULL)
11560 moveType = (ChessMove) Myylex();
11564 switch (moveType) {
11566 if (appData.debugMode)
11567 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11570 /* append the comment but don't display it */
11571 AppendComment(currentMove, p, FALSE);
11574 case WhiteCapturesEnPassant:
11575 case BlackCapturesEnPassant:
11576 case WhitePromotion:
11577 case BlackPromotion:
11578 case WhiteNonPromotion:
11579 case BlackNonPromotion:
11582 case WhiteKingSideCastle:
11583 case WhiteQueenSideCastle:
11584 case BlackKingSideCastle:
11585 case BlackQueenSideCastle:
11586 case WhiteKingSideCastleWild:
11587 case WhiteQueenSideCastleWild:
11588 case BlackKingSideCastleWild:
11589 case BlackQueenSideCastleWild:
11591 case WhiteHSideCastleFR:
11592 case WhiteASideCastleFR:
11593 case BlackHSideCastleFR:
11594 case BlackASideCastleFR:
11596 if (appData.debugMode)
11597 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11598 fromX = currentMoveString[0] - AAA;
11599 fromY = currentMoveString[1] - ONE;
11600 toX = currentMoveString[2] - AAA;
11601 toY = currentMoveString[3] - ONE;
11602 promoChar = currentMoveString[4];
11603 if(promoChar == ';') promoChar = NULLCHAR;
11608 if (appData.debugMode)
11609 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11610 fromX = moveType == WhiteDrop ?
11611 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11612 (int) CharToPiece(ToLower(currentMoveString[0]));
11614 toX = currentMoveString[2] - AAA;
11615 toY = currentMoveString[3] - ONE;
11621 case GameUnfinished:
11622 if (appData.debugMode)
11623 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11624 p = strchr(yy_text, '{');
11625 if (p == NULL) p = strchr(yy_text, '(');
11628 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11630 q = strchr(p, *p == '{' ? '}' : ')');
11631 if (q != NULL) *q = NULLCHAR;
11634 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11635 GameEnds(moveType, p, GE_FILE);
11637 if (cmailMsgLoaded) {
11639 flipView = WhiteOnMove(currentMove);
11640 if (moveType == GameUnfinished) flipView = !flipView;
11641 if (appData.debugMode)
11642 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11647 if (appData.debugMode)
11648 fprintf(debugFP, "Parser hit end of file\n");
11649 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11655 if (WhiteOnMove(currentMove)) {
11656 GameEnds(BlackWins, "Black mates", GE_FILE);
11658 GameEnds(WhiteWins, "White mates", GE_FILE);
11662 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11668 case MoveNumberOne:
11669 if (lastLoadGameStart == GNUChessGame) {
11670 /* GNUChessGames have numbers, but they aren't move numbers */
11671 if (appData.debugMode)
11672 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11673 yy_text, (int) moveType);
11674 return LoadGameOneMove(EndOfFile); /* tail recursion */
11676 /* else fall thru */
11681 /* Reached start of next game in file */
11682 if (appData.debugMode)
11683 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11684 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11690 if (WhiteOnMove(currentMove)) {
11691 GameEnds(BlackWins, "Black mates", GE_FILE);
11693 GameEnds(WhiteWins, "White mates", GE_FILE);
11697 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11703 case PositionDiagram: /* should not happen; ignore */
11704 case ElapsedTime: /* ignore */
11705 case NAG: /* ignore */
11706 if (appData.debugMode)
11707 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11708 yy_text, (int) moveType);
11709 return LoadGameOneMove(EndOfFile); /* tail recursion */
11712 if (appData.testLegality) {
11713 if (appData.debugMode)
11714 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11715 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11716 (forwardMostMove / 2) + 1,
11717 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11718 DisplayError(move, 0);
11721 if (appData.debugMode)
11722 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11723 yy_text, currentMoveString);
11724 fromX = currentMoveString[0] - AAA;
11725 fromY = currentMoveString[1] - ONE;
11726 toX = currentMoveString[2] - AAA;
11727 toY = currentMoveString[3] - ONE;
11728 promoChar = currentMoveString[4];
11732 case AmbiguousMove:
11733 if (appData.debugMode)
11734 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11735 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11736 (forwardMostMove / 2) + 1,
11737 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11738 DisplayError(move, 0);
11743 case ImpossibleMove:
11744 if (appData.debugMode)
11745 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11746 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11747 (forwardMostMove / 2) + 1,
11748 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11749 DisplayError(move, 0);
11755 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11756 DrawPosition(FALSE, boards[currentMove]);
11757 DisplayBothClocks();
11758 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11759 DisplayComment(currentMove - 1, commentList[currentMove]);
11761 (void) StopLoadGameTimer();
11763 cmailOldMove = forwardMostMove;
11766 /* currentMoveString is set as a side-effect of yylex */
11768 thinkOutput[0] = NULLCHAR;
11769 MakeMove(fromX, fromY, toX, toY, promoChar);
11770 killX = killY = -1; // [HGM] lion: used up
11771 currentMove = forwardMostMove;
11776 /* Load the nth game from the given file */
11778 LoadGameFromFile (char *filename, int n, char *title, int useList)
11783 if (strcmp(filename, "-") == 0) {
11787 f = fopen(filename, "rb");
11789 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11790 DisplayError(buf, errno);
11794 if (fseek(f, 0, 0) == -1) {
11795 /* f is not seekable; probably a pipe */
11798 if (useList && n == 0) {
11799 int error = GameListBuild(f);
11801 DisplayError(_("Cannot build game list"), error);
11802 } else if (!ListEmpty(&gameList) &&
11803 ((ListGame *) gameList.tailPred)->number > 1) {
11804 GameListPopUp(f, title);
11811 return LoadGame(f, n, title, FALSE);
11816 MakeRegisteredMove ()
11818 int fromX, fromY, toX, toY;
11820 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11821 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11824 if (appData.debugMode)
11825 fprintf(debugFP, "Restoring %s for game %d\n",
11826 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11828 thinkOutput[0] = NULLCHAR;
11829 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11830 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11831 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11832 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11833 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11834 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11835 MakeMove(fromX, fromY, toX, toY, promoChar);
11836 ShowMove(fromX, fromY, toX, toY);
11838 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11845 if (WhiteOnMove(currentMove)) {
11846 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11848 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11853 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11860 if (WhiteOnMove(currentMove)) {
11861 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11863 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11868 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11879 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11881 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11885 if (gameNumber > nCmailGames) {
11886 DisplayError(_("No more games in this message"), 0);
11889 if (f == lastLoadGameFP) {
11890 int offset = gameNumber - lastLoadGameNumber;
11892 cmailMsg[0] = NULLCHAR;
11893 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11894 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11895 nCmailMovesRegistered--;
11897 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11898 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11899 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11902 if (! RegisterMove()) return FALSE;
11906 retVal = LoadGame(f, gameNumber, title, useList);
11908 /* Make move registered during previous look at this game, if any */
11909 MakeRegisteredMove();
11911 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11912 commentList[currentMove]
11913 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11914 DisplayComment(currentMove - 1, commentList[currentMove]);
11920 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11922 ReloadGame (int offset)
11924 int gameNumber = lastLoadGameNumber + offset;
11925 if (lastLoadGameFP == NULL) {
11926 DisplayError(_("No game has been loaded yet"), 0);
11929 if (gameNumber <= 0) {
11930 DisplayError(_("Can't back up any further"), 0);
11933 if (cmailMsgLoaded) {
11934 return CmailLoadGame(lastLoadGameFP, gameNumber,
11935 lastLoadGameTitle, lastLoadGameUseList);
11937 return LoadGame(lastLoadGameFP, gameNumber,
11938 lastLoadGameTitle, lastLoadGameUseList);
11942 int keys[EmptySquare+1];
11945 PositionMatches (Board b1, Board b2)
11948 switch(appData.searchMode) {
11949 case 1: return CompareWithRights(b1, b2);
11951 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11952 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11956 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11957 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11958 sum += keys[b1[r][f]] - keys[b2[r][f]];
11962 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11963 sum += keys[b1[r][f]] - keys[b2[r][f]];
11975 int pieceList[256], quickBoard[256];
11976 ChessSquare pieceType[256] = { EmptySquare };
11977 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11978 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11979 int soughtTotal, turn;
11980 Boolean epOK, flipSearch;
11983 unsigned char piece, to;
11986 #define DSIZE (250000)
11988 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11989 Move *moveDatabase = initialSpace;
11990 unsigned int movePtr, dataSize = DSIZE;
11993 MakePieceList (Board board, int *counts)
11995 int r, f, n=Q_PROMO, total=0;
11996 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11997 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11998 int sq = f + (r<<4);
11999 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12000 quickBoard[sq] = ++n;
12002 pieceType[n] = board[r][f];
12003 counts[board[r][f]]++;
12004 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12005 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12009 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12014 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12016 int sq = fromX + (fromY<<4);
12017 int piece = quickBoard[sq];
12018 quickBoard[sq] = 0;
12019 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12020 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12021 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12022 moveDatabase[movePtr++].piece = Q_WCASTL;
12023 quickBoard[sq] = piece;
12024 piece = quickBoard[from]; quickBoard[from] = 0;
12025 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12027 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12028 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12029 moveDatabase[movePtr++].piece = Q_BCASTL;
12030 quickBoard[sq] = piece;
12031 piece = quickBoard[from]; quickBoard[from] = 0;
12032 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12034 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12035 quickBoard[(fromY<<4)+toX] = 0;
12036 moveDatabase[movePtr].piece = Q_EP;
12037 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12038 moveDatabase[movePtr].to = sq;
12040 if(promoPiece != pieceType[piece]) {
12041 moveDatabase[movePtr++].piece = Q_PROMO;
12042 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12044 moveDatabase[movePtr].piece = piece;
12045 quickBoard[sq] = piece;
12050 PackGame (Board board)
12052 Move *newSpace = NULL;
12053 moveDatabase[movePtr].piece = 0; // terminate previous game
12054 if(movePtr > dataSize) {
12055 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12056 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12057 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12060 Move *p = moveDatabase, *q = newSpace;
12061 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12062 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12063 moveDatabase = newSpace;
12064 } else { // calloc failed, we must be out of memory. Too bad...
12065 dataSize = 0; // prevent calloc events for all subsequent games
12066 return 0; // and signal this one isn't cached
12070 MakePieceList(board, counts);
12075 QuickCompare (Board board, int *minCounts, int *maxCounts)
12076 { // compare according to search mode
12078 switch(appData.searchMode)
12080 case 1: // exact position match
12081 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12082 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12083 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12086 case 2: // can have extra material on empty squares
12087 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12088 if(board[r][f] == EmptySquare) continue;
12089 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12092 case 3: // material with exact Pawn structure
12093 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12094 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12095 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12096 } // fall through to material comparison
12097 case 4: // exact material
12098 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12100 case 6: // material range with given imbalance
12101 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12102 // fall through to range comparison
12103 case 5: // material range
12104 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12110 QuickScan (Board board, Move *move)
12111 { // reconstruct game,and compare all positions in it
12112 int cnt=0, stretch=0, total = MakePieceList(board, counts);
12114 int piece = move->piece;
12115 int to = move->to, from = pieceList[piece];
12116 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12117 if(!piece) return -1;
12118 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12119 piece = (++move)->piece;
12120 from = pieceList[piece];
12121 counts[pieceType[piece]]--;
12122 pieceType[piece] = (ChessSquare) move->to;
12123 counts[move->to]++;
12124 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12125 counts[pieceType[quickBoard[to]]]--;
12126 quickBoard[to] = 0; total--;
12129 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12130 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12131 from = pieceList[piece]; // so this must be King
12132 quickBoard[from] = 0;
12133 pieceList[piece] = to;
12134 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12135 quickBoard[from] = 0; // rook
12136 quickBoard[to] = piece;
12137 to = move->to; piece = move->piece;
12141 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12142 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12143 quickBoard[from] = 0;
12145 quickBoard[to] = piece;
12146 pieceList[piece] = to;
12148 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12149 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12150 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12151 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12153 static int lastCounts[EmptySquare+1];
12155 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12156 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12157 } else stretch = 0;
12158 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12167 flipSearch = FALSE;
12168 CopyBoard(soughtBoard, boards[currentMove]);
12169 soughtTotal = MakePieceList(soughtBoard, maxSought);
12170 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12171 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12172 CopyBoard(reverseBoard, boards[currentMove]);
12173 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12174 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12175 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12176 reverseBoard[r][f] = piece;
12178 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12179 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12180 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12181 || (boards[currentMove][CASTLING][2] == NoRights ||
12182 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12183 && (boards[currentMove][CASTLING][5] == NoRights ||
12184 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12187 CopyBoard(flipBoard, soughtBoard);
12188 CopyBoard(rotateBoard, reverseBoard);
12189 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12190 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12191 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12194 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12195 if(appData.searchMode >= 5) {
12196 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12197 MakePieceList(soughtBoard, minSought);
12198 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12200 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12201 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12204 GameInfo dummyInfo;
12205 static int creatingBook;
12208 GameContainsPosition (FILE *f, ListGame *lg)
12210 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12211 int fromX, fromY, toX, toY;
12213 static int initDone=FALSE;
12215 // weed out games based on numerical tag comparison
12216 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12217 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12218 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12219 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12221 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12224 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12225 else CopyBoard(boards[scratch], initialPosition); // default start position
12228 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12229 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12232 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12233 fseek(f, lg->offset, 0);
12236 yyboardindex = scratch;
12237 quickFlag = plyNr+1;
12242 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12248 if(plyNr) return -1; // after we have seen moves, this is for new game
12251 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12252 case ImpossibleMove:
12253 case WhiteWins: // game ends here with these four
12256 case GameUnfinished:
12260 if(appData.testLegality) return -1;
12261 case WhiteCapturesEnPassant:
12262 case BlackCapturesEnPassant:
12263 case WhitePromotion:
12264 case BlackPromotion:
12265 case WhiteNonPromotion:
12266 case BlackNonPromotion:
12269 case WhiteKingSideCastle:
12270 case WhiteQueenSideCastle:
12271 case BlackKingSideCastle:
12272 case BlackQueenSideCastle:
12273 case WhiteKingSideCastleWild:
12274 case WhiteQueenSideCastleWild:
12275 case BlackKingSideCastleWild:
12276 case BlackQueenSideCastleWild:
12277 case WhiteHSideCastleFR:
12278 case WhiteASideCastleFR:
12279 case BlackHSideCastleFR:
12280 case BlackASideCastleFR:
12281 fromX = currentMoveString[0] - AAA;
12282 fromY = currentMoveString[1] - ONE;
12283 toX = currentMoveString[2] - AAA;
12284 toY = currentMoveString[3] - ONE;
12285 promoChar = currentMoveString[4];
12289 fromX = next == WhiteDrop ?
12290 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12291 (int) CharToPiece(ToLower(currentMoveString[0]));
12293 toX = currentMoveString[2] - AAA;
12294 toY = currentMoveString[3] - ONE;
12298 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12300 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12301 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12302 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12303 if(appData.findMirror) {
12304 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12305 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12310 /* Load the nth game from open file f */
12312 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12316 int gn = gameNumber;
12317 ListGame *lg = NULL;
12318 int numPGNTags = 0;
12320 GameMode oldGameMode;
12321 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12323 if (appData.debugMode)
12324 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12326 if (gameMode == Training )
12327 SetTrainingModeOff();
12329 oldGameMode = gameMode;
12330 if (gameMode != BeginningOfGame) {
12331 Reset(FALSE, TRUE);
12333 killX = killY = -1; // [HGM] lion: in case we did not Reset
12336 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12337 fclose(lastLoadGameFP);
12341 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12344 fseek(f, lg->offset, 0);
12345 GameListHighlight(gameNumber);
12346 pos = lg->position;
12350 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12351 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12353 DisplayError(_("Game number out of range"), 0);
12358 if (fseek(f, 0, 0) == -1) {
12359 if (f == lastLoadGameFP ?
12360 gameNumber == lastLoadGameNumber + 1 :
12364 DisplayError(_("Can't seek on game file"), 0);
12369 lastLoadGameFP = f;
12370 lastLoadGameNumber = gameNumber;
12371 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12372 lastLoadGameUseList = useList;
12376 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12377 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12378 lg->gameInfo.black);
12380 } else if (*title != NULLCHAR) {
12381 if (gameNumber > 1) {
12382 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12385 DisplayTitle(title);
12389 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12390 gameMode = PlayFromGameFile;
12394 currentMove = forwardMostMove = backwardMostMove = 0;
12395 CopyBoard(boards[0], initialPosition);
12399 * Skip the first gn-1 games in the file.
12400 * Also skip over anything that precedes an identifiable
12401 * start of game marker, to avoid being confused by
12402 * garbage at the start of the file. Currently
12403 * recognized start of game markers are the move number "1",
12404 * the pattern "gnuchess .* game", the pattern
12405 * "^[#;%] [^ ]* game file", and a PGN tag block.
12406 * A game that starts with one of the latter two patterns
12407 * will also have a move number 1, possibly
12408 * following a position diagram.
12409 * 5-4-02: Let's try being more lenient and allowing a game to
12410 * start with an unnumbered move. Does that break anything?
12412 cm = lastLoadGameStart = EndOfFile;
12414 yyboardindex = forwardMostMove;
12415 cm = (ChessMove) Myylex();
12418 if (cmailMsgLoaded) {
12419 nCmailGames = CMAIL_MAX_GAMES - gn;
12422 DisplayError(_("Game not found in file"), 0);
12429 lastLoadGameStart = cm;
12432 case MoveNumberOne:
12433 switch (lastLoadGameStart) {
12438 case MoveNumberOne:
12440 gn--; /* count this game */
12441 lastLoadGameStart = cm;
12450 switch (lastLoadGameStart) {
12453 case MoveNumberOne:
12455 gn--; /* count this game */
12456 lastLoadGameStart = cm;
12459 lastLoadGameStart = cm; /* game counted already */
12467 yyboardindex = forwardMostMove;
12468 cm = (ChessMove) Myylex();
12469 } while (cm == PGNTag || cm == Comment);
12476 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12477 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12478 != CMAIL_OLD_RESULT) {
12480 cmailResult[ CMAIL_MAX_GAMES
12481 - gn - 1] = CMAIL_OLD_RESULT;
12488 /* Only a NormalMove can be at the start of a game
12489 * without a position diagram. */
12490 if (lastLoadGameStart == EndOfFile ) {
12492 lastLoadGameStart = MoveNumberOne;
12501 if (appData.debugMode)
12502 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12504 if (cm == XBoardGame) {
12505 /* Skip any header junk before position diagram and/or move 1 */
12507 yyboardindex = forwardMostMove;
12508 cm = (ChessMove) Myylex();
12510 if (cm == EndOfFile ||
12511 cm == GNUChessGame || cm == XBoardGame) {
12512 /* Empty game; pretend end-of-file and handle later */
12517 if (cm == MoveNumberOne || cm == PositionDiagram ||
12518 cm == PGNTag || cm == Comment)
12521 } else if (cm == GNUChessGame) {
12522 if (gameInfo.event != NULL) {
12523 free(gameInfo.event);
12525 gameInfo.event = StrSave(yy_text);
12528 startedFromSetupPosition = FALSE;
12529 while (cm == PGNTag) {
12530 if (appData.debugMode)
12531 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12532 err = ParsePGNTag(yy_text, &gameInfo);
12533 if (!err) numPGNTags++;
12535 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12536 if(gameInfo.variant != oldVariant) {
12537 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12538 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12539 InitPosition(TRUE);
12540 oldVariant = gameInfo.variant;
12541 if (appData.debugMode)
12542 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12546 if (gameInfo.fen != NULL) {
12547 Board initial_position;
12548 startedFromSetupPosition = TRUE;
12549 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12551 DisplayError(_("Bad FEN position in file"), 0);
12554 CopyBoard(boards[0], initial_position);
12555 if (blackPlaysFirst) {
12556 currentMove = forwardMostMove = backwardMostMove = 1;
12557 CopyBoard(boards[1], initial_position);
12558 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12559 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12560 timeRemaining[0][1] = whiteTimeRemaining;
12561 timeRemaining[1][1] = blackTimeRemaining;
12562 if (commentList[0] != NULL) {
12563 commentList[1] = commentList[0];
12564 commentList[0] = NULL;
12567 currentMove = forwardMostMove = backwardMostMove = 0;
12569 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12571 initialRulePlies = FENrulePlies;
12572 for( i=0; i< nrCastlingRights; i++ )
12573 initialRights[i] = initial_position[CASTLING][i];
12575 yyboardindex = forwardMostMove;
12576 free(gameInfo.fen);
12577 gameInfo.fen = NULL;
12580 yyboardindex = forwardMostMove;
12581 cm = (ChessMove) Myylex();
12583 /* Handle comments interspersed among the tags */
12584 while (cm == Comment) {
12586 if (appData.debugMode)
12587 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12589 AppendComment(currentMove, p, FALSE);
12590 yyboardindex = forwardMostMove;
12591 cm = (ChessMove) Myylex();
12595 /* don't rely on existence of Event tag since if game was
12596 * pasted from clipboard the Event tag may not exist
12598 if (numPGNTags > 0){
12600 if (gameInfo.variant == VariantNormal) {
12601 VariantClass v = StringToVariant(gameInfo.event);
12602 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12603 if(v < VariantShogi) gameInfo.variant = v;
12606 if( appData.autoDisplayTags ) {
12607 tags = PGNTags(&gameInfo);
12608 TagsPopUp(tags, CmailMsg());
12613 /* Make something up, but don't display it now */
12618 if (cm == PositionDiagram) {
12621 Board initial_position;
12623 if (appData.debugMode)
12624 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12626 if (!startedFromSetupPosition) {
12628 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12629 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12640 initial_position[i][j++] = CharToPiece(*p);
12643 while (*p == ' ' || *p == '\t' ||
12644 *p == '\n' || *p == '\r') p++;
12646 if (strncmp(p, "black", strlen("black"))==0)
12647 blackPlaysFirst = TRUE;
12649 blackPlaysFirst = FALSE;
12650 startedFromSetupPosition = TRUE;
12652 CopyBoard(boards[0], initial_position);
12653 if (blackPlaysFirst) {
12654 currentMove = forwardMostMove = backwardMostMove = 1;
12655 CopyBoard(boards[1], initial_position);
12656 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12657 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12658 timeRemaining[0][1] = whiteTimeRemaining;
12659 timeRemaining[1][1] = blackTimeRemaining;
12660 if (commentList[0] != NULL) {
12661 commentList[1] = commentList[0];
12662 commentList[0] = NULL;
12665 currentMove = forwardMostMove = backwardMostMove = 0;
12668 yyboardindex = forwardMostMove;
12669 cm = (ChessMove) Myylex();
12672 if(!creatingBook) {
12673 if (first.pr == NoProc) {
12674 StartChessProgram(&first);
12676 InitChessProgram(&first, FALSE);
12677 SendToProgram("force\n", &first);
12678 if (startedFromSetupPosition) {
12679 SendBoard(&first, forwardMostMove);
12680 if (appData.debugMode) {
12681 fprintf(debugFP, "Load Game\n");
12683 DisplayBothClocks();
12687 /* [HGM] server: flag to write setup moves in broadcast file as one */
12688 loadFlag = appData.suppressLoadMoves;
12690 while (cm == Comment) {
12692 if (appData.debugMode)
12693 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12695 AppendComment(currentMove, p, FALSE);
12696 yyboardindex = forwardMostMove;
12697 cm = (ChessMove) Myylex();
12700 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12701 cm == WhiteWins || cm == BlackWins ||
12702 cm == GameIsDrawn || cm == GameUnfinished) {
12703 DisplayMessage("", _("No moves in game"));
12704 if (cmailMsgLoaded) {
12705 if (appData.debugMode)
12706 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12710 DrawPosition(FALSE, boards[currentMove]);
12711 DisplayBothClocks();
12712 gameMode = EditGame;
12719 // [HGM] PV info: routine tests if comment empty
12720 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12721 DisplayComment(currentMove - 1, commentList[currentMove]);
12723 if (!matchMode && appData.timeDelay != 0)
12724 DrawPosition(FALSE, boards[currentMove]);
12726 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12727 programStats.ok_to_send = 1;
12730 /* if the first token after the PGN tags is a move
12731 * and not move number 1, retrieve it from the parser
12733 if (cm != MoveNumberOne)
12734 LoadGameOneMove(cm);
12736 /* load the remaining moves from the file */
12737 while (LoadGameOneMove(EndOfFile)) {
12738 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12739 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12742 /* rewind to the start of the game */
12743 currentMove = backwardMostMove;
12745 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12747 if (oldGameMode == AnalyzeFile) {
12748 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12749 AnalyzeFileEvent();
12751 if (oldGameMode == AnalyzeMode) {
12752 AnalyzeFileEvent();
12755 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12756 long int w, b; // [HGM] adjourn: restore saved clock times
12757 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12758 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12759 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12760 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12764 if(creatingBook) return TRUE;
12765 if (!matchMode && pos > 0) {
12766 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12768 if (matchMode || appData.timeDelay == 0) {
12770 } else if (appData.timeDelay > 0) {
12771 AutoPlayGameLoop();
12774 if (appData.debugMode)
12775 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12777 loadFlag = 0; /* [HGM] true game starts */
12781 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12783 ReloadPosition (int offset)
12785 int positionNumber = lastLoadPositionNumber + offset;
12786 if (lastLoadPositionFP == NULL) {
12787 DisplayError(_("No position has been loaded yet"), 0);
12790 if (positionNumber <= 0) {
12791 DisplayError(_("Can't back up any further"), 0);
12794 return LoadPosition(lastLoadPositionFP, positionNumber,
12795 lastLoadPositionTitle);
12798 /* Load the nth position from the given file */
12800 LoadPositionFromFile (char *filename, int n, char *title)
12805 if (strcmp(filename, "-") == 0) {
12806 return LoadPosition(stdin, n, "stdin");
12808 f = fopen(filename, "rb");
12810 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12811 DisplayError(buf, errno);
12814 return LoadPosition(f, n, title);
12819 /* Load the nth position from the given open file, and close it */
12821 LoadPosition (FILE *f, int positionNumber, char *title)
12823 char *p, line[MSG_SIZ];
12824 Board initial_position;
12825 int i, j, fenMode, pn;
12827 if (gameMode == Training )
12828 SetTrainingModeOff();
12830 if (gameMode != BeginningOfGame) {
12831 Reset(FALSE, TRUE);
12833 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12834 fclose(lastLoadPositionFP);
12836 if (positionNumber == 0) positionNumber = 1;
12837 lastLoadPositionFP = f;
12838 lastLoadPositionNumber = positionNumber;
12839 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12840 if (first.pr == NoProc && !appData.noChessProgram) {
12841 StartChessProgram(&first);
12842 InitChessProgram(&first, FALSE);
12844 pn = positionNumber;
12845 if (positionNumber < 0) {
12846 /* Negative position number means to seek to that byte offset */
12847 if (fseek(f, -positionNumber, 0) == -1) {
12848 DisplayError(_("Can't seek on position file"), 0);
12853 if (fseek(f, 0, 0) == -1) {
12854 if (f == lastLoadPositionFP ?
12855 positionNumber == lastLoadPositionNumber + 1 :
12856 positionNumber == 1) {
12859 DisplayError(_("Can't seek on position file"), 0);
12864 /* See if this file is FEN or old-style xboard */
12865 if (fgets(line, MSG_SIZ, f) == NULL) {
12866 DisplayError(_("Position not found in file"), 0);
12869 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12870 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12873 if (fenMode || line[0] == '#') pn--;
12875 /* skip positions before number pn */
12876 if (fgets(line, MSG_SIZ, f) == NULL) {
12878 DisplayError(_("Position not found in file"), 0);
12881 if (fenMode || line[0] == '#') pn--;
12886 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12887 DisplayError(_("Bad FEN position in file"), 0);
12891 (void) fgets(line, MSG_SIZ, f);
12892 (void) fgets(line, MSG_SIZ, f);
12894 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12895 (void) fgets(line, MSG_SIZ, f);
12896 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12899 initial_position[i][j++] = CharToPiece(*p);
12903 blackPlaysFirst = FALSE;
12905 (void) fgets(line, MSG_SIZ, f);
12906 if (strncmp(line, "black", strlen("black"))==0)
12907 blackPlaysFirst = TRUE;
12910 startedFromSetupPosition = TRUE;
12912 CopyBoard(boards[0], initial_position);
12913 if (blackPlaysFirst) {
12914 currentMove = forwardMostMove = backwardMostMove = 1;
12915 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12916 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12917 CopyBoard(boards[1], initial_position);
12918 DisplayMessage("", _("Black to play"));
12920 currentMove = forwardMostMove = backwardMostMove = 0;
12921 DisplayMessage("", _("White to play"));
12923 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12924 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12925 SendToProgram("force\n", &first);
12926 SendBoard(&first, forwardMostMove);
12928 if (appData.debugMode) {
12930 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12931 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12932 fprintf(debugFP, "Load Position\n");
12935 if (positionNumber > 1) {
12936 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12937 DisplayTitle(line);
12939 DisplayTitle(title);
12941 gameMode = EditGame;
12944 timeRemaining[0][1] = whiteTimeRemaining;
12945 timeRemaining[1][1] = blackTimeRemaining;
12946 DrawPosition(FALSE, boards[currentMove]);
12953 CopyPlayerNameIntoFileName (char **dest, char *src)
12955 while (*src != NULLCHAR && *src != ',') {
12960 *(*dest)++ = *src++;
12966 DefaultFileName (char *ext)
12968 static char def[MSG_SIZ];
12971 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12973 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12975 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12977 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12984 /* Save the current game to the given file */
12986 SaveGameToFile (char *filename, int append)
12990 int result, i, t,tot=0;
12992 if (strcmp(filename, "-") == 0) {
12993 return SaveGame(stdout, 0, NULL);
12995 for(i=0; i<10; i++) { // upto 10 tries
12996 f = fopen(filename, append ? "a" : "w");
12997 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12998 if(f || errno != 13) break;
12999 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13003 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13004 DisplayError(buf, errno);
13007 safeStrCpy(buf, lastMsg, MSG_SIZ);
13008 DisplayMessage(_("Waiting for access to save file"), "");
13009 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13010 DisplayMessage(_("Saving game"), "");
13011 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13012 result = SaveGame(f, 0, NULL);
13013 DisplayMessage(buf, "");
13020 SavePart (char *str)
13022 static char buf[MSG_SIZ];
13025 p = strchr(str, ' ');
13026 if (p == NULL) return str;
13027 strncpy(buf, str, p - str);
13028 buf[p - str] = NULLCHAR;
13032 #define PGN_MAX_LINE 75
13034 #define PGN_SIDE_WHITE 0
13035 #define PGN_SIDE_BLACK 1
13038 FindFirstMoveOutOfBook (int side)
13042 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13043 int index = backwardMostMove;
13044 int has_book_hit = 0;
13046 if( (index % 2) != side ) {
13050 while( index < forwardMostMove ) {
13051 /* Check to see if engine is in book */
13052 int depth = pvInfoList[index].depth;
13053 int score = pvInfoList[index].score;
13059 else if( score == 0 && depth == 63 ) {
13060 in_book = 1; /* Zappa */
13062 else if( score == 2 && depth == 99 ) {
13063 in_book = 1; /* Abrok */
13066 has_book_hit += in_book;
13082 GetOutOfBookInfo (char * buf)
13086 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13088 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13089 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13093 if( oob[0] >= 0 || oob[1] >= 0 ) {
13094 for( i=0; i<2; i++ ) {
13098 if( i > 0 && oob[0] >= 0 ) {
13099 strcat( buf, " " );
13102 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13103 sprintf( buf+strlen(buf), "%s%.2f",
13104 pvInfoList[idx].score >= 0 ? "+" : "",
13105 pvInfoList[idx].score / 100.0 );
13111 /* Save game in PGN style and close the file */
13113 SaveGamePGN (FILE *f)
13115 int i, offset, linelen, newblock;
13118 int movelen, numlen, blank;
13119 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13121 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13123 PrintPGNTags(f, &gameInfo);
13125 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13127 if (backwardMostMove > 0 || startedFromSetupPosition) {
13128 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13129 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13130 fprintf(f, "\n{--------------\n");
13131 PrintPosition(f, backwardMostMove);
13132 fprintf(f, "--------------}\n");
13136 /* [AS] Out of book annotation */
13137 if( appData.saveOutOfBookInfo ) {
13140 GetOutOfBookInfo( buf );
13142 if( buf[0] != '\0' ) {
13143 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13150 i = backwardMostMove;
13154 while (i < forwardMostMove) {
13155 /* Print comments preceding this move */
13156 if (commentList[i] != NULL) {
13157 if (linelen > 0) fprintf(f, "\n");
13158 fprintf(f, "%s", commentList[i]);
13163 /* Format move number */
13165 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13168 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13170 numtext[0] = NULLCHAR;
13172 numlen = strlen(numtext);
13175 /* Print move number */
13176 blank = linelen > 0 && numlen > 0;
13177 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13186 fprintf(f, "%s", numtext);
13190 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13191 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13194 blank = linelen > 0 && movelen > 0;
13195 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13204 fprintf(f, "%s", move_buffer);
13205 linelen += movelen;
13207 /* [AS] Add PV info if present */
13208 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13209 /* [HGM] add time */
13210 char buf[MSG_SIZ]; int seconds;
13212 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13218 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13221 seconds = (seconds + 4)/10; // round to full seconds
13223 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13225 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13228 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13229 pvInfoList[i].score >= 0 ? "+" : "",
13230 pvInfoList[i].score / 100.0,
13231 pvInfoList[i].depth,
13234 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13236 /* Print score/depth */
13237 blank = linelen > 0 && movelen > 0;
13238 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13247 fprintf(f, "%s", move_buffer);
13248 linelen += movelen;
13254 /* Start a new line */
13255 if (linelen > 0) fprintf(f, "\n");
13257 /* Print comments after last move */
13258 if (commentList[i] != NULL) {
13259 fprintf(f, "%s\n", commentList[i]);
13263 if (gameInfo.resultDetails != NULL &&
13264 gameInfo.resultDetails[0] != NULLCHAR) {
13265 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13266 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13267 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13268 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13269 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13271 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13275 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13279 /* Save game in old style and close the file */
13281 SaveGameOldStyle (FILE *f)
13286 tm = time((time_t *) NULL);
13288 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13291 if (backwardMostMove > 0 || startedFromSetupPosition) {
13292 fprintf(f, "\n[--------------\n");
13293 PrintPosition(f, backwardMostMove);
13294 fprintf(f, "--------------]\n");
13299 i = backwardMostMove;
13300 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13302 while (i < forwardMostMove) {
13303 if (commentList[i] != NULL) {
13304 fprintf(f, "[%s]\n", commentList[i]);
13307 if ((i % 2) == 1) {
13308 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13311 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13313 if (commentList[i] != NULL) {
13317 if (i >= forwardMostMove) {
13321 fprintf(f, "%s\n", parseList[i]);
13326 if (commentList[i] != NULL) {
13327 fprintf(f, "[%s]\n", commentList[i]);
13330 /* This isn't really the old style, but it's close enough */
13331 if (gameInfo.resultDetails != NULL &&
13332 gameInfo.resultDetails[0] != NULLCHAR) {
13333 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13334 gameInfo.resultDetails);
13336 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13343 /* Save the current game to open file f and close the file */
13345 SaveGame (FILE *f, int dummy, char *dummy2)
13347 if (gameMode == EditPosition) EditPositionDone(TRUE);
13348 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13349 if (appData.oldSaveStyle)
13350 return SaveGameOldStyle(f);
13352 return SaveGamePGN(f);
13355 /* Save the current position to the given file */
13357 SavePositionToFile (char *filename)
13362 if (strcmp(filename, "-") == 0) {
13363 return SavePosition(stdout, 0, NULL);
13365 f = fopen(filename, "a");
13367 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13368 DisplayError(buf, errno);
13371 safeStrCpy(buf, lastMsg, MSG_SIZ);
13372 DisplayMessage(_("Waiting for access to save file"), "");
13373 flock(fileno(f), LOCK_EX); // [HGM] lock
13374 DisplayMessage(_("Saving position"), "");
13375 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13376 SavePosition(f, 0, NULL);
13377 DisplayMessage(buf, "");
13383 /* Save the current position to the given open file and close the file */
13385 SavePosition (FILE *f, int dummy, char *dummy2)
13390 if (gameMode == EditPosition) EditPositionDone(TRUE);
13391 if (appData.oldSaveStyle) {
13392 tm = time((time_t *) NULL);
13394 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13396 fprintf(f, "[--------------\n");
13397 PrintPosition(f, currentMove);
13398 fprintf(f, "--------------]\n");
13400 fen = PositionToFEN(currentMove, NULL, 1);
13401 fprintf(f, "%s\n", fen);
13409 ReloadCmailMsgEvent (int unregister)
13412 static char *inFilename = NULL;
13413 static char *outFilename;
13415 struct stat inbuf, outbuf;
13418 /* Any registered moves are unregistered if unregister is set, */
13419 /* i.e. invoked by the signal handler */
13421 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13422 cmailMoveRegistered[i] = FALSE;
13423 if (cmailCommentList[i] != NULL) {
13424 free(cmailCommentList[i]);
13425 cmailCommentList[i] = NULL;
13428 nCmailMovesRegistered = 0;
13431 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13432 cmailResult[i] = CMAIL_NOT_RESULT;
13436 if (inFilename == NULL) {
13437 /* Because the filenames are static they only get malloced once */
13438 /* and they never get freed */
13439 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13440 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13442 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13443 sprintf(outFilename, "%s.out", appData.cmailGameName);
13446 status = stat(outFilename, &outbuf);
13448 cmailMailedMove = FALSE;
13450 status = stat(inFilename, &inbuf);
13451 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13454 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13455 counts the games, notes how each one terminated, etc.
13457 It would be nice to remove this kludge and instead gather all
13458 the information while building the game list. (And to keep it
13459 in the game list nodes instead of having a bunch of fixed-size
13460 parallel arrays.) Note this will require getting each game's
13461 termination from the PGN tags, as the game list builder does
13462 not process the game moves. --mann
13464 cmailMsgLoaded = TRUE;
13465 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13467 /* Load first game in the file or popup game menu */
13468 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13470 #endif /* !WIN32 */
13478 char string[MSG_SIZ];
13480 if ( cmailMailedMove
13481 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13482 return TRUE; /* Allow free viewing */
13485 /* Unregister move to ensure that we don't leave RegisterMove */
13486 /* with the move registered when the conditions for registering no */
13488 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13489 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13490 nCmailMovesRegistered --;
13492 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13494 free(cmailCommentList[lastLoadGameNumber - 1]);
13495 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13499 if (cmailOldMove == -1) {
13500 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13504 if (currentMove > cmailOldMove + 1) {
13505 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13509 if (currentMove < cmailOldMove) {
13510 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13514 if (forwardMostMove > currentMove) {
13515 /* Silently truncate extra moves */
13519 if ( (currentMove == cmailOldMove + 1)
13520 || ( (currentMove == cmailOldMove)
13521 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13522 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13523 if (gameInfo.result != GameUnfinished) {
13524 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13527 if (commentList[currentMove] != NULL) {
13528 cmailCommentList[lastLoadGameNumber - 1]
13529 = StrSave(commentList[currentMove]);
13531 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13533 if (appData.debugMode)
13534 fprintf(debugFP, "Saving %s for game %d\n",
13535 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13537 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13539 f = fopen(string, "w");
13540 if (appData.oldSaveStyle) {
13541 SaveGameOldStyle(f); /* also closes the file */
13543 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13544 f = fopen(string, "w");
13545 SavePosition(f, 0, NULL); /* also closes the file */
13547 fprintf(f, "{--------------\n");
13548 PrintPosition(f, currentMove);
13549 fprintf(f, "--------------}\n\n");
13551 SaveGame(f, 0, NULL); /* also closes the file*/
13554 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13555 nCmailMovesRegistered ++;
13556 } else if (nCmailGames == 1) {
13557 DisplayError(_("You have not made a move yet"), 0);
13568 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13569 FILE *commandOutput;
13570 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13571 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13577 if (! cmailMsgLoaded) {
13578 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13582 if (nCmailGames == nCmailResults) {
13583 DisplayError(_("No unfinished games"), 0);
13587 #if CMAIL_PROHIBIT_REMAIL
13588 if (cmailMailedMove) {
13589 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);
13590 DisplayError(msg, 0);
13595 if (! (cmailMailedMove || RegisterMove())) return;
13597 if ( cmailMailedMove
13598 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13599 snprintf(string, MSG_SIZ, partCommandString,
13600 appData.debugMode ? " -v" : "", appData.cmailGameName);
13601 commandOutput = popen(string, "r");
13603 if (commandOutput == NULL) {
13604 DisplayError(_("Failed to invoke cmail"), 0);
13606 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13607 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13609 if (nBuffers > 1) {
13610 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13611 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13612 nBytes = MSG_SIZ - 1;
13614 (void) memcpy(msg, buffer, nBytes);
13616 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13618 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13619 cmailMailedMove = TRUE; /* Prevent >1 moves */
13622 for (i = 0; i < nCmailGames; i ++) {
13623 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13628 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13630 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13632 appData.cmailGameName,
13634 LoadGameFromFile(buffer, 1, buffer, FALSE);
13635 cmailMsgLoaded = FALSE;
13639 DisplayInformation(msg);
13640 pclose(commandOutput);
13643 if ((*cmailMsg) != '\0') {
13644 DisplayInformation(cmailMsg);
13649 #endif /* !WIN32 */
13658 int prependComma = 0;
13660 char string[MSG_SIZ]; /* Space for game-list */
13663 if (!cmailMsgLoaded) return "";
13665 if (cmailMailedMove) {
13666 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13668 /* Create a list of games left */
13669 snprintf(string, MSG_SIZ, "[");
13670 for (i = 0; i < nCmailGames; i ++) {
13671 if (! ( cmailMoveRegistered[i]
13672 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13673 if (prependComma) {
13674 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13676 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13680 strcat(string, number);
13683 strcat(string, "]");
13685 if (nCmailMovesRegistered + nCmailResults == 0) {
13686 switch (nCmailGames) {
13688 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13692 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13696 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13701 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13703 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13708 if (nCmailResults == nCmailGames) {
13709 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13711 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13716 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13728 if (gameMode == Training)
13729 SetTrainingModeOff();
13732 cmailMsgLoaded = FALSE;
13733 if (appData.icsActive) {
13734 SendToICS(ics_prefix);
13735 SendToICS("refresh\n");
13740 ExitEvent (int status)
13744 /* Give up on clean exit */
13748 /* Keep trying for clean exit */
13752 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13754 if (telnetISR != NULL) {
13755 RemoveInputSource(telnetISR);
13757 if (icsPR != NoProc) {
13758 DestroyChildProcess(icsPR, TRUE);
13761 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13762 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13764 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13765 /* make sure this other one finishes before killing it! */
13766 if(endingGame) { int count = 0;
13767 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13768 while(endingGame && count++ < 10) DoSleep(1);
13769 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13772 /* Kill off chess programs */
13773 if (first.pr != NoProc) {
13776 DoSleep( appData.delayBeforeQuit );
13777 SendToProgram("quit\n", &first);
13778 DoSleep( appData.delayAfterQuit );
13779 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13781 if (second.pr != NoProc) {
13782 DoSleep( appData.delayBeforeQuit );
13783 SendToProgram("quit\n", &second);
13784 DoSleep( appData.delayAfterQuit );
13785 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13787 if (first.isr != NULL) {
13788 RemoveInputSource(first.isr);
13790 if (second.isr != NULL) {
13791 RemoveInputSource(second.isr);
13794 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13795 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13797 ShutDownFrontEnd();
13802 PauseEngine (ChessProgramState *cps)
13804 SendToProgram("pause\n", cps);
13809 UnPauseEngine (ChessProgramState *cps)
13811 SendToProgram("resume\n", cps);
13818 if (appData.debugMode)
13819 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13823 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13825 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13826 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13827 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13829 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13830 HandleMachineMove(stashedInputMove, stalledEngine);
13831 stalledEngine = NULL;
13834 if (gameMode == MachinePlaysWhite ||
13835 gameMode == TwoMachinesPlay ||
13836 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13837 if(first.pause) UnPauseEngine(&first);
13838 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13839 if(second.pause) UnPauseEngine(&second);
13840 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13843 DisplayBothClocks();
13845 if (gameMode == PlayFromGameFile) {
13846 if (appData.timeDelay >= 0)
13847 AutoPlayGameLoop();
13848 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13849 Reset(FALSE, TRUE);
13850 SendToICS(ics_prefix);
13851 SendToICS("refresh\n");
13852 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13853 ForwardInner(forwardMostMove);
13855 pauseExamInvalid = FALSE;
13857 switch (gameMode) {
13861 pauseExamForwardMostMove = forwardMostMove;
13862 pauseExamInvalid = FALSE;
13865 case IcsPlayingWhite:
13866 case IcsPlayingBlack:
13870 case PlayFromGameFile:
13871 (void) StopLoadGameTimer();
13875 case BeginningOfGame:
13876 if (appData.icsActive) return;
13877 /* else fall through */
13878 case MachinePlaysWhite:
13879 case MachinePlaysBlack:
13880 case TwoMachinesPlay:
13881 if (forwardMostMove == 0)
13882 return; /* don't pause if no one has moved */
13883 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13884 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13885 if(onMove->pause) { // thinking engine can be paused
13886 PauseEngine(onMove); // do it
13887 if(onMove->other->pause) // pondering opponent can always be paused immediately
13888 PauseEngine(onMove->other);
13890 SendToProgram("easy\n", onMove->other);
13892 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13893 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13895 PauseEngine(&first);
13897 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13898 } else { // human on move, pause pondering by either method
13900 PauseEngine(&first);
13901 else if(appData.ponderNextMove)
13902 SendToProgram("easy\n", &first);
13905 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13915 EditCommentEvent ()
13917 char title[MSG_SIZ];
13919 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13920 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13922 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13923 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13924 parseList[currentMove - 1]);
13927 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13934 char *tags = PGNTags(&gameInfo);
13936 EditTagsPopUp(tags, NULL);
13943 if(second.analyzing) {
13944 SendToProgram("exit\n", &second);
13945 second.analyzing = FALSE;
13947 if (second.pr == NoProc) StartChessProgram(&second);
13948 InitChessProgram(&second, FALSE);
13949 FeedMovesToProgram(&second, currentMove);
13951 SendToProgram("analyze\n", &second);
13952 second.analyzing = TRUE;
13956 /* Toggle ShowThinking */
13958 ToggleShowThinking()
13960 appData.showThinking = !appData.showThinking;
13961 ShowThinkingEvent();
13965 AnalyzeModeEvent ()
13969 if (!first.analysisSupport) {
13970 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13971 DisplayError(buf, 0);
13974 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13975 if (appData.icsActive) {
13976 if (gameMode != IcsObserving) {
13977 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13978 DisplayError(buf, 0);
13980 if (appData.icsEngineAnalyze) {
13981 if (appData.debugMode)
13982 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13988 /* if enable, user wants to disable icsEngineAnalyze */
13989 if (appData.icsEngineAnalyze) {
13994 appData.icsEngineAnalyze = TRUE;
13995 if (appData.debugMode)
13996 fprintf(debugFP, "ICS engine analyze starting... \n");
13999 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14000 if (appData.noChessProgram || gameMode == AnalyzeMode)
14003 if (gameMode != AnalyzeFile) {
14004 if (!appData.icsEngineAnalyze) {
14006 if (gameMode != EditGame) return 0;
14008 if (!appData.showThinking) ToggleShowThinking();
14009 ResurrectChessProgram();
14010 SendToProgram("analyze\n", &first);
14011 first.analyzing = TRUE;
14012 /*first.maybeThinking = TRUE;*/
14013 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14014 EngineOutputPopUp();
14016 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14021 StartAnalysisClock();
14022 GetTimeMark(&lastNodeCountTime);
14028 AnalyzeFileEvent ()
14030 if (appData.noChessProgram || gameMode == AnalyzeFile)
14033 if (!first.analysisSupport) {
14035 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14036 DisplayError(buf, 0);
14040 if (gameMode != AnalyzeMode) {
14041 keepInfo = 1; // mere annotating should not alter PGN tags
14044 if (gameMode != EditGame) return;
14045 if (!appData.showThinking) ToggleShowThinking();
14046 ResurrectChessProgram();
14047 SendToProgram("analyze\n", &first);
14048 first.analyzing = TRUE;
14049 /*first.maybeThinking = TRUE;*/
14050 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14051 EngineOutputPopUp();
14053 gameMode = AnalyzeFile;
14057 StartAnalysisClock();
14058 GetTimeMark(&lastNodeCountTime);
14060 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14061 AnalysisPeriodicEvent(1);
14065 MachineWhiteEvent ()
14068 char *bookHit = NULL;
14070 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14074 if (gameMode == PlayFromGameFile ||
14075 gameMode == TwoMachinesPlay ||
14076 gameMode == Training ||
14077 gameMode == AnalyzeMode ||
14078 gameMode == EndOfGame)
14081 if (gameMode == EditPosition)
14082 EditPositionDone(TRUE);
14084 if (!WhiteOnMove(currentMove)) {
14085 DisplayError(_("It is not White's turn"), 0);
14089 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14092 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14093 gameMode == AnalyzeFile)
14096 ResurrectChessProgram(); /* in case it isn't running */
14097 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14098 gameMode = MachinePlaysWhite;
14101 gameMode = MachinePlaysWhite;
14105 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14107 if (first.sendName) {
14108 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14109 SendToProgram(buf, &first);
14111 if (first.sendTime) {
14112 if (first.useColors) {
14113 SendToProgram("black\n", &first); /*gnu kludge*/
14115 SendTimeRemaining(&first, TRUE);
14117 if (first.useColors) {
14118 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14120 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14121 SetMachineThinkingEnables();
14122 first.maybeThinking = TRUE;
14126 if (appData.autoFlipView && !flipView) {
14127 flipView = !flipView;
14128 DrawPosition(FALSE, NULL);
14129 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14132 if(bookHit) { // [HGM] book: simulate book reply
14133 static char bookMove[MSG_SIZ]; // a bit generous?
14135 programStats.nodes = programStats.depth = programStats.time =
14136 programStats.score = programStats.got_only_move = 0;
14137 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14139 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14140 strcat(bookMove, bookHit);
14141 HandleMachineMove(bookMove, &first);
14146 MachineBlackEvent ()
14149 char *bookHit = NULL;
14151 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14155 if (gameMode == PlayFromGameFile ||
14156 gameMode == TwoMachinesPlay ||
14157 gameMode == Training ||
14158 gameMode == AnalyzeMode ||
14159 gameMode == EndOfGame)
14162 if (gameMode == EditPosition)
14163 EditPositionDone(TRUE);
14165 if (WhiteOnMove(currentMove)) {
14166 DisplayError(_("It is not Black's turn"), 0);
14170 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14173 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14174 gameMode == AnalyzeFile)
14177 ResurrectChessProgram(); /* in case it isn't running */
14178 gameMode = MachinePlaysBlack;
14182 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14184 if (first.sendName) {
14185 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14186 SendToProgram(buf, &first);
14188 if (first.sendTime) {
14189 if (first.useColors) {
14190 SendToProgram("white\n", &first); /*gnu kludge*/
14192 SendTimeRemaining(&first, FALSE);
14194 if (first.useColors) {
14195 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14197 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14198 SetMachineThinkingEnables();
14199 first.maybeThinking = TRUE;
14202 if (appData.autoFlipView && flipView) {
14203 flipView = !flipView;
14204 DrawPosition(FALSE, NULL);
14205 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14207 if(bookHit) { // [HGM] book: simulate book reply
14208 static char bookMove[MSG_SIZ]; // a bit generous?
14210 programStats.nodes = programStats.depth = programStats.time =
14211 programStats.score = programStats.got_only_move = 0;
14212 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14214 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14215 strcat(bookMove, bookHit);
14216 HandleMachineMove(bookMove, &first);
14222 DisplayTwoMachinesTitle ()
14225 if (appData.matchGames > 0) {
14226 if(appData.tourneyFile[0]) {
14227 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14228 gameInfo.white, _("vs."), gameInfo.black,
14229 nextGame+1, appData.matchGames+1,
14230 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14232 if (first.twoMachinesColor[0] == 'w') {
14233 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14234 gameInfo.white, _("vs."), gameInfo.black,
14235 first.matchWins, second.matchWins,
14236 matchGame - 1 - (first.matchWins + second.matchWins));
14238 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14239 gameInfo.white, _("vs."), gameInfo.black,
14240 second.matchWins, first.matchWins,
14241 matchGame - 1 - (first.matchWins + second.matchWins));
14244 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14250 SettingsMenuIfReady ()
14252 if (second.lastPing != second.lastPong) {
14253 DisplayMessage("", _("Waiting for second chess program"));
14254 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14258 DisplayMessage("", "");
14259 SettingsPopUp(&second);
14263 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14266 if (cps->pr == NoProc) {
14267 StartChessProgram(cps);
14268 if (cps->protocolVersion == 1) {
14270 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14272 /* kludge: allow timeout for initial "feature" command */
14273 if(retry != TwoMachinesEventIfReady) FreezeUI();
14274 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14275 DisplayMessage("", buf);
14276 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14284 TwoMachinesEvent P((void))
14288 ChessProgramState *onmove;
14289 char *bookHit = NULL;
14290 static int stalling = 0;
14294 if (appData.noChessProgram) return;
14296 switch (gameMode) {
14297 case TwoMachinesPlay:
14299 case MachinePlaysWhite:
14300 case MachinePlaysBlack:
14301 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14302 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14306 case BeginningOfGame:
14307 case PlayFromGameFile:
14310 if (gameMode != EditGame) return;
14313 EditPositionDone(TRUE);
14324 // forwardMostMove = currentMove;
14325 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14326 startingEngine = TRUE;
14328 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14330 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14331 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14332 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14335 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14337 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14338 startingEngine = FALSE;
14339 DisplayError("second engine does not play this", 0);
14344 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14345 SendToProgram("force\n", &second);
14347 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14350 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14351 if(appData.matchPause>10000 || appData.matchPause<10)
14352 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14353 wait = SubtractTimeMarks(&now, &pauseStart);
14354 if(wait < appData.matchPause) {
14355 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14358 // we are now committed to starting the game
14360 DisplayMessage("", "");
14361 if (startedFromSetupPosition) {
14362 SendBoard(&second, backwardMostMove);
14363 if (appData.debugMode) {
14364 fprintf(debugFP, "Two Machines\n");
14367 for (i = backwardMostMove; i < forwardMostMove; i++) {
14368 SendMoveToProgram(i, &second);
14371 gameMode = TwoMachinesPlay;
14372 pausing = startingEngine = FALSE;
14373 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14375 DisplayTwoMachinesTitle();
14377 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14382 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14383 SendToProgram(first.computerString, &first);
14384 if (first.sendName) {
14385 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14386 SendToProgram(buf, &first);
14388 SendToProgram(second.computerString, &second);
14389 if (second.sendName) {
14390 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14391 SendToProgram(buf, &second);
14395 if (!first.sendTime || !second.sendTime) {
14396 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14397 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14399 if (onmove->sendTime) {
14400 if (onmove->useColors) {
14401 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14403 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14405 if (onmove->useColors) {
14406 SendToProgram(onmove->twoMachinesColor, onmove);
14408 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14409 // SendToProgram("go\n", onmove);
14410 onmove->maybeThinking = TRUE;
14411 SetMachineThinkingEnables();
14415 if(bookHit) { // [HGM] book: simulate book reply
14416 static char bookMove[MSG_SIZ]; // a bit generous?
14418 programStats.nodes = programStats.depth = programStats.time =
14419 programStats.score = programStats.got_only_move = 0;
14420 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14422 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14423 strcat(bookMove, bookHit);
14424 savedMessage = bookMove; // args for deferred call
14425 savedState = onmove;
14426 ScheduleDelayedEvent(DeferredBookMove, 1);
14433 if (gameMode == Training) {
14434 SetTrainingModeOff();
14435 gameMode = PlayFromGameFile;
14436 DisplayMessage("", _("Training mode off"));
14438 gameMode = Training;
14439 animateTraining = appData.animate;
14441 /* make sure we are not already at the end of the game */
14442 if (currentMove < forwardMostMove) {
14443 SetTrainingModeOn();
14444 DisplayMessage("", _("Training mode on"));
14446 gameMode = PlayFromGameFile;
14447 DisplayError(_("Already at end of game"), 0);
14456 if (!appData.icsActive) return;
14457 switch (gameMode) {
14458 case IcsPlayingWhite:
14459 case IcsPlayingBlack:
14462 case BeginningOfGame:
14470 EditPositionDone(TRUE);
14483 gameMode = IcsIdle;
14493 switch (gameMode) {
14495 SetTrainingModeOff();
14497 case MachinePlaysWhite:
14498 case MachinePlaysBlack:
14499 case BeginningOfGame:
14500 SendToProgram("force\n", &first);
14501 SetUserThinkingEnables();
14503 case PlayFromGameFile:
14504 (void) StopLoadGameTimer();
14505 if (gameFileFP != NULL) {
14510 EditPositionDone(TRUE);
14515 SendToProgram("force\n", &first);
14517 case TwoMachinesPlay:
14518 GameEnds(EndOfFile, NULL, GE_PLAYER);
14519 ResurrectChessProgram();
14520 SetUserThinkingEnables();
14523 ResurrectChessProgram();
14525 case IcsPlayingBlack:
14526 case IcsPlayingWhite:
14527 DisplayError(_("Warning: You are still playing a game"), 0);
14530 DisplayError(_("Warning: You are still observing a game"), 0);
14533 DisplayError(_("Warning: You are still examining a game"), 0);
14544 first.offeredDraw = second.offeredDraw = 0;
14546 if (gameMode == PlayFromGameFile) {
14547 whiteTimeRemaining = timeRemaining[0][currentMove];
14548 blackTimeRemaining = timeRemaining[1][currentMove];
14552 if (gameMode == MachinePlaysWhite ||
14553 gameMode == MachinePlaysBlack ||
14554 gameMode == TwoMachinesPlay ||
14555 gameMode == EndOfGame) {
14556 i = forwardMostMove;
14557 while (i > currentMove) {
14558 SendToProgram("undo\n", &first);
14561 if(!adjustedClock) {
14562 whiteTimeRemaining = timeRemaining[0][currentMove];
14563 blackTimeRemaining = timeRemaining[1][currentMove];
14564 DisplayBothClocks();
14566 if (whiteFlag || blackFlag) {
14567 whiteFlag = blackFlag = 0;
14572 gameMode = EditGame;
14579 EditPositionEvent ()
14581 if (gameMode == EditPosition) {
14587 if (gameMode != EditGame) return;
14589 gameMode = EditPosition;
14592 if (currentMove > 0)
14593 CopyBoard(boards[0], boards[currentMove]);
14595 blackPlaysFirst = !WhiteOnMove(currentMove);
14597 currentMove = forwardMostMove = backwardMostMove = 0;
14598 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14600 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14606 /* [DM] icsEngineAnalyze - possible call from other functions */
14607 if (appData.icsEngineAnalyze) {
14608 appData.icsEngineAnalyze = FALSE;
14610 DisplayMessage("",_("Close ICS engine analyze..."));
14612 if (first.analysisSupport && first.analyzing) {
14613 SendToBoth("exit\n");
14614 first.analyzing = second.analyzing = FALSE;
14616 thinkOutput[0] = NULLCHAR;
14620 EditPositionDone (Boolean fakeRights)
14622 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14624 startedFromSetupPosition = TRUE;
14625 InitChessProgram(&first, FALSE);
14626 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14627 boards[0][EP_STATUS] = EP_NONE;
14628 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14629 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14630 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14631 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14632 } else boards[0][CASTLING][2] = NoRights;
14633 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14634 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14635 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14636 } else boards[0][CASTLING][5] = NoRights;
14637 if(gameInfo.variant == VariantSChess) {
14639 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14640 boards[0][VIRGIN][i] = 0;
14641 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14642 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14646 SendToProgram("force\n", &first);
14647 if (blackPlaysFirst) {
14648 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14649 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14650 currentMove = forwardMostMove = backwardMostMove = 1;
14651 CopyBoard(boards[1], boards[0]);
14653 currentMove = forwardMostMove = backwardMostMove = 0;
14655 SendBoard(&first, forwardMostMove);
14656 if (appData.debugMode) {
14657 fprintf(debugFP, "EditPosDone\n");
14660 DisplayMessage("", "");
14661 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14662 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14663 gameMode = EditGame;
14665 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14666 ClearHighlights(); /* [AS] */
14669 /* Pause for `ms' milliseconds */
14670 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14672 TimeDelay (long ms)
14679 } while (SubtractTimeMarks(&m2, &m1) < ms);
14682 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14684 SendMultiLineToICS (char *buf)
14686 char temp[MSG_SIZ+1], *p;
14693 strncpy(temp, buf, len);
14698 if (*p == '\n' || *p == '\r')
14703 strcat(temp, "\n");
14705 SendToPlayer(temp, strlen(temp));
14709 SetWhiteToPlayEvent ()
14711 if (gameMode == EditPosition) {
14712 blackPlaysFirst = FALSE;
14713 DisplayBothClocks(); /* works because currentMove is 0 */
14714 } else if (gameMode == IcsExamining) {
14715 SendToICS(ics_prefix);
14716 SendToICS("tomove white\n");
14721 SetBlackToPlayEvent ()
14723 if (gameMode == EditPosition) {
14724 blackPlaysFirst = TRUE;
14725 currentMove = 1; /* kludge */
14726 DisplayBothClocks();
14728 } else if (gameMode == IcsExamining) {
14729 SendToICS(ics_prefix);
14730 SendToICS("tomove black\n");
14735 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14738 ChessSquare piece = boards[0][y][x];
14739 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14740 static int lastVariant;
14742 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14744 switch (selection) {
14746 CopyBoard(currentBoard, boards[0]);
14747 CopyBoard(menuBoard, initialPosition);
14748 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14749 SendToICS(ics_prefix);
14750 SendToICS("bsetup clear\n");
14751 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14752 SendToICS(ics_prefix);
14753 SendToICS("clearboard\n");
14756 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14757 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14758 for (y = 0; y < BOARD_HEIGHT; y++) {
14759 if (gameMode == IcsExamining) {
14760 if (boards[currentMove][y][x] != EmptySquare) {
14761 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14766 if(boards[0][y][x] != p) nonEmpty++;
14767 boards[0][y][x] = p;
14770 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14772 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14773 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
14774 ChessSquare p = menuBoard[0][x];
14775 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14776 p = menuBoard[BOARD_HEIGHT-1][x];
14777 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14779 DisplayMessage("Clicking clock again restores position", "");
14780 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14781 if(!nonEmpty) { // asked to clear an empty board
14782 CopyBoard(boards[0], menuBoard);
14784 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14785 CopyBoard(boards[0], initialPosition);
14787 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14788 && !CompareBoards(nullBoard, erasedBoard)) {
14789 CopyBoard(boards[0], erasedBoard);
14791 CopyBoard(erasedBoard, currentBoard);
14795 if (gameMode == EditPosition) {
14796 DrawPosition(FALSE, boards[0]);
14801 SetWhiteToPlayEvent();
14805 SetBlackToPlayEvent();
14809 if (gameMode == IcsExamining) {
14810 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14811 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14814 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14815 if(x == BOARD_LEFT-2) {
14816 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14817 boards[0][y][1] = 0;
14819 if(x == BOARD_RGHT+1) {
14820 if(y >= gameInfo.holdingsSize) break;
14821 boards[0][y][BOARD_WIDTH-2] = 0;
14824 boards[0][y][x] = EmptySquare;
14825 DrawPosition(FALSE, boards[0]);
14830 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14831 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14832 selection = (ChessSquare) (PROMOTED piece);
14833 } else if(piece == EmptySquare) selection = WhiteSilver;
14834 else selection = (ChessSquare)((int)piece - 1);
14838 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14839 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14840 selection = (ChessSquare) (DEMOTED piece);
14841 } else if(piece == EmptySquare) selection = BlackSilver;
14842 else selection = (ChessSquare)((int)piece + 1);
14847 if(gameInfo.variant == VariantShatranj ||
14848 gameInfo.variant == VariantXiangqi ||
14849 gameInfo.variant == VariantCourier ||
14850 gameInfo.variant == VariantASEAN ||
14851 gameInfo.variant == VariantMakruk )
14852 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14857 if(gameInfo.variant == VariantXiangqi)
14858 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14859 if(gameInfo.variant == VariantKnightmate)
14860 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14863 if (gameMode == IcsExamining) {
14864 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14865 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14866 PieceToChar(selection), AAA + x, ONE + y);
14869 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14871 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14872 n = PieceToNumber(selection - BlackPawn);
14873 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14874 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14875 boards[0][BOARD_HEIGHT-1-n][1]++;
14877 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14878 n = PieceToNumber(selection);
14879 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14880 boards[0][n][BOARD_WIDTH-1] = selection;
14881 boards[0][n][BOARD_WIDTH-2]++;
14884 boards[0][y][x] = selection;
14885 DrawPosition(TRUE, boards[0]);
14887 fromX = fromY = -1;
14895 DropMenuEvent (ChessSquare selection, int x, int y)
14897 ChessMove moveType;
14899 switch (gameMode) {
14900 case IcsPlayingWhite:
14901 case MachinePlaysBlack:
14902 if (!WhiteOnMove(currentMove)) {
14903 DisplayMoveError(_("It is Black's turn"));
14906 moveType = WhiteDrop;
14908 case IcsPlayingBlack:
14909 case MachinePlaysWhite:
14910 if (WhiteOnMove(currentMove)) {
14911 DisplayMoveError(_("It is White's turn"));
14914 moveType = BlackDrop;
14917 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14923 if (moveType == BlackDrop && selection < BlackPawn) {
14924 selection = (ChessSquare) ((int) selection
14925 + (int) BlackPawn - (int) WhitePawn);
14927 if (boards[currentMove][y][x] != EmptySquare) {
14928 DisplayMoveError(_("That square is occupied"));
14932 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14938 /* Accept a pending offer of any kind from opponent */
14940 if (appData.icsActive) {
14941 SendToICS(ics_prefix);
14942 SendToICS("accept\n");
14943 } else if (cmailMsgLoaded) {
14944 if (currentMove == cmailOldMove &&
14945 commentList[cmailOldMove] != NULL &&
14946 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14947 "Black offers a draw" : "White offers a draw")) {
14949 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14950 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14952 DisplayError(_("There is no pending offer on this move"), 0);
14953 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14956 /* Not used for offers from chess program */
14963 /* Decline a pending offer of any kind from opponent */
14965 if (appData.icsActive) {
14966 SendToICS(ics_prefix);
14967 SendToICS("decline\n");
14968 } else if (cmailMsgLoaded) {
14969 if (currentMove == cmailOldMove &&
14970 commentList[cmailOldMove] != NULL &&
14971 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14972 "Black offers a draw" : "White offers a draw")) {
14974 AppendComment(cmailOldMove, "Draw declined", TRUE);
14975 DisplayComment(cmailOldMove - 1, "Draw declined");
14978 DisplayError(_("There is no pending offer on this move"), 0);
14981 /* Not used for offers from chess program */
14988 /* Issue ICS rematch command */
14989 if (appData.icsActive) {
14990 SendToICS(ics_prefix);
14991 SendToICS("rematch\n");
14998 /* Call your opponent's flag (claim a win on time) */
14999 if (appData.icsActive) {
15000 SendToICS(ics_prefix);
15001 SendToICS("flag\n");
15003 switch (gameMode) {
15006 case MachinePlaysWhite:
15009 GameEnds(GameIsDrawn, "Both players ran out of time",
15012 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15014 DisplayError(_("Your opponent is not out of time"), 0);
15017 case MachinePlaysBlack:
15020 GameEnds(GameIsDrawn, "Both players ran out of time",
15023 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15025 DisplayError(_("Your opponent is not out of time"), 0);
15033 ClockClick (int which)
15034 { // [HGM] code moved to back-end from winboard.c
15035 if(which) { // black clock
15036 if (gameMode == EditPosition || gameMode == IcsExamining) {
15037 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15038 SetBlackToPlayEvent();
15039 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15040 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15041 } else if (shiftKey) {
15042 AdjustClock(which, -1);
15043 } else if (gameMode == IcsPlayingWhite ||
15044 gameMode == MachinePlaysBlack) {
15047 } else { // white clock
15048 if (gameMode == EditPosition || gameMode == IcsExamining) {
15049 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15050 SetWhiteToPlayEvent();
15051 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15052 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15053 } else if (shiftKey) {
15054 AdjustClock(which, -1);
15055 } else if (gameMode == IcsPlayingBlack ||
15056 gameMode == MachinePlaysWhite) {
15065 /* Offer draw or accept pending draw offer from opponent */
15067 if (appData.icsActive) {
15068 /* Note: tournament rules require draw offers to be
15069 made after you make your move but before you punch
15070 your clock. Currently ICS doesn't let you do that;
15071 instead, you immediately punch your clock after making
15072 a move, but you can offer a draw at any time. */
15074 SendToICS(ics_prefix);
15075 SendToICS("draw\n");
15076 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15077 } else if (cmailMsgLoaded) {
15078 if (currentMove == cmailOldMove &&
15079 commentList[cmailOldMove] != NULL &&
15080 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15081 "Black offers a draw" : "White offers a draw")) {
15082 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15083 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15084 } else if (currentMove == cmailOldMove + 1) {
15085 char *offer = WhiteOnMove(cmailOldMove) ?
15086 "White offers a draw" : "Black offers a draw";
15087 AppendComment(currentMove, offer, TRUE);
15088 DisplayComment(currentMove - 1, offer);
15089 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15091 DisplayError(_("You must make your move before offering a draw"), 0);
15092 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15094 } else if (first.offeredDraw) {
15095 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15097 if (first.sendDrawOffers) {
15098 SendToProgram("draw\n", &first);
15099 userOfferedDraw = TRUE;
15107 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15109 if (appData.icsActive) {
15110 SendToICS(ics_prefix);
15111 SendToICS("adjourn\n");
15113 /* Currently GNU Chess doesn't offer or accept Adjourns */
15121 /* Offer Abort or accept pending Abort offer from opponent */
15123 if (appData.icsActive) {
15124 SendToICS(ics_prefix);
15125 SendToICS("abort\n");
15127 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15134 /* Resign. You can do this even if it's not your turn. */
15136 if (appData.icsActive) {
15137 SendToICS(ics_prefix);
15138 SendToICS("resign\n");
15140 switch (gameMode) {
15141 case MachinePlaysWhite:
15142 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15144 case MachinePlaysBlack:
15145 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15148 if (cmailMsgLoaded) {
15150 if (WhiteOnMove(cmailOldMove)) {
15151 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15153 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15155 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15166 StopObservingEvent ()
15168 /* Stop observing current games */
15169 SendToICS(ics_prefix);
15170 SendToICS("unobserve\n");
15174 StopExaminingEvent ()
15176 /* Stop observing current game */
15177 SendToICS(ics_prefix);
15178 SendToICS("unexamine\n");
15182 ForwardInner (int target)
15184 int limit; int oldSeekGraphUp = seekGraphUp;
15186 if (appData.debugMode)
15187 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15188 target, currentMove, forwardMostMove);
15190 if (gameMode == EditPosition)
15193 seekGraphUp = FALSE;
15194 MarkTargetSquares(1);
15196 if (gameMode == PlayFromGameFile && !pausing)
15199 if (gameMode == IcsExamining && pausing)
15200 limit = pauseExamForwardMostMove;
15202 limit = forwardMostMove;
15204 if (target > limit) target = limit;
15206 if (target > 0 && moveList[target - 1][0]) {
15207 int fromX, fromY, toX, toY;
15208 toX = moveList[target - 1][2] - AAA;
15209 toY = moveList[target - 1][3] - ONE;
15210 if (moveList[target - 1][1] == '@') {
15211 if (appData.highlightLastMove) {
15212 SetHighlights(-1, -1, toX, toY);
15215 fromX = moveList[target - 1][0] - AAA;
15216 fromY = moveList[target - 1][1] - ONE;
15217 if (target == currentMove + 1) {
15218 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15220 if (appData.highlightLastMove) {
15221 SetHighlights(fromX, fromY, toX, toY);
15225 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15226 gameMode == Training || gameMode == PlayFromGameFile ||
15227 gameMode == AnalyzeFile) {
15228 while (currentMove < target) {
15229 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15230 SendMoveToProgram(currentMove++, &first);
15233 currentMove = target;
15236 if (gameMode == EditGame || gameMode == EndOfGame) {
15237 whiteTimeRemaining = timeRemaining[0][currentMove];
15238 blackTimeRemaining = timeRemaining[1][currentMove];
15240 DisplayBothClocks();
15241 DisplayMove(currentMove - 1);
15242 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15243 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15244 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15245 DisplayComment(currentMove - 1, commentList[currentMove]);
15247 ClearMap(); // [HGM] exclude: invalidate map
15254 if (gameMode == IcsExamining && !pausing) {
15255 SendToICS(ics_prefix);
15256 SendToICS("forward\n");
15258 ForwardInner(currentMove + 1);
15265 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15266 /* to optimze, we temporarily turn off analysis mode while we feed
15267 * the remaining moves to the engine. Otherwise we get analysis output
15270 if (first.analysisSupport) {
15271 SendToProgram("exit\nforce\n", &first);
15272 first.analyzing = FALSE;
15276 if (gameMode == IcsExamining && !pausing) {
15277 SendToICS(ics_prefix);
15278 SendToICS("forward 999999\n");
15280 ForwardInner(forwardMostMove);
15283 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15284 /* we have fed all the moves, so reactivate analysis mode */
15285 SendToProgram("analyze\n", &first);
15286 first.analyzing = TRUE;
15287 /*first.maybeThinking = TRUE;*/
15288 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15293 BackwardInner (int target)
15295 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15297 if (appData.debugMode)
15298 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15299 target, currentMove, forwardMostMove);
15301 if (gameMode == EditPosition) return;
15302 seekGraphUp = FALSE;
15303 MarkTargetSquares(1);
15304 if (currentMove <= backwardMostMove) {
15306 DrawPosition(full_redraw, boards[currentMove]);
15309 if (gameMode == PlayFromGameFile && !pausing)
15312 if (moveList[target][0]) {
15313 int fromX, fromY, toX, toY;
15314 toX = moveList[target][2] - AAA;
15315 toY = moveList[target][3] - ONE;
15316 if (moveList[target][1] == '@') {
15317 if (appData.highlightLastMove) {
15318 SetHighlights(-1, -1, toX, toY);
15321 fromX = moveList[target][0] - AAA;
15322 fromY = moveList[target][1] - ONE;
15323 if (target == currentMove - 1) {
15324 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15326 if (appData.highlightLastMove) {
15327 SetHighlights(fromX, fromY, toX, toY);
15331 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15332 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15333 while (currentMove > target) {
15334 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15335 // null move cannot be undone. Reload program with move history before it.
15337 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15338 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15340 SendBoard(&first, i);
15341 if(second.analyzing) SendBoard(&second, i);
15342 for(currentMove=i; currentMove<target; currentMove++) {
15343 SendMoveToProgram(currentMove, &first);
15344 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15348 SendToBoth("undo\n");
15352 currentMove = target;
15355 if (gameMode == EditGame || gameMode == EndOfGame) {
15356 whiteTimeRemaining = timeRemaining[0][currentMove];
15357 blackTimeRemaining = timeRemaining[1][currentMove];
15359 DisplayBothClocks();
15360 DisplayMove(currentMove - 1);
15361 DrawPosition(full_redraw, boards[currentMove]);
15362 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15363 // [HGM] PV info: routine tests if comment empty
15364 DisplayComment(currentMove - 1, commentList[currentMove]);
15365 ClearMap(); // [HGM] exclude: invalidate map
15371 if (gameMode == IcsExamining && !pausing) {
15372 SendToICS(ics_prefix);
15373 SendToICS("backward\n");
15375 BackwardInner(currentMove - 1);
15382 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15383 /* to optimize, we temporarily turn off analysis mode while we undo
15384 * all the moves. Otherwise we get analysis output after each undo.
15386 if (first.analysisSupport) {
15387 SendToProgram("exit\nforce\n", &first);
15388 first.analyzing = FALSE;
15392 if (gameMode == IcsExamining && !pausing) {
15393 SendToICS(ics_prefix);
15394 SendToICS("backward 999999\n");
15396 BackwardInner(backwardMostMove);
15399 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15400 /* we have fed all the moves, so reactivate analysis mode */
15401 SendToProgram("analyze\n", &first);
15402 first.analyzing = TRUE;
15403 /*first.maybeThinking = TRUE;*/
15404 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15411 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15412 if (to >= forwardMostMove) to = forwardMostMove;
15413 if (to <= backwardMostMove) to = backwardMostMove;
15414 if (to < currentMove) {
15422 RevertEvent (Boolean annotate)
15424 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15427 if (gameMode != IcsExamining) {
15428 DisplayError(_("You are not examining a game"), 0);
15432 DisplayError(_("You can't revert while pausing"), 0);
15435 SendToICS(ics_prefix);
15436 SendToICS("revert\n");
15440 RetractMoveEvent ()
15442 switch (gameMode) {
15443 case MachinePlaysWhite:
15444 case MachinePlaysBlack:
15445 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15446 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15449 if (forwardMostMove < 2) return;
15450 currentMove = forwardMostMove = forwardMostMove - 2;
15451 whiteTimeRemaining = timeRemaining[0][currentMove];
15452 blackTimeRemaining = timeRemaining[1][currentMove];
15453 DisplayBothClocks();
15454 DisplayMove(currentMove - 1);
15455 ClearHighlights();/*!! could figure this out*/
15456 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15457 SendToProgram("remove\n", &first);
15458 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15461 case BeginningOfGame:
15465 case IcsPlayingWhite:
15466 case IcsPlayingBlack:
15467 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15468 SendToICS(ics_prefix);
15469 SendToICS("takeback 2\n");
15471 SendToICS(ics_prefix);
15472 SendToICS("takeback 1\n");
15481 ChessProgramState *cps;
15483 switch (gameMode) {
15484 case MachinePlaysWhite:
15485 if (!WhiteOnMove(forwardMostMove)) {
15486 DisplayError(_("It is your turn"), 0);
15491 case MachinePlaysBlack:
15492 if (WhiteOnMove(forwardMostMove)) {
15493 DisplayError(_("It is your turn"), 0);
15498 case TwoMachinesPlay:
15499 if (WhiteOnMove(forwardMostMove) ==
15500 (first.twoMachinesColor[0] == 'w')) {
15506 case BeginningOfGame:
15510 SendToProgram("?\n", cps);
15514 TruncateGameEvent ()
15517 if (gameMode != EditGame) return;
15524 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15525 if (forwardMostMove > currentMove) {
15526 if (gameInfo.resultDetails != NULL) {
15527 free(gameInfo.resultDetails);
15528 gameInfo.resultDetails = NULL;
15529 gameInfo.result = GameUnfinished;
15531 forwardMostMove = currentMove;
15532 HistorySet(parseList, backwardMostMove, forwardMostMove,
15540 if (appData.noChessProgram) return;
15541 switch (gameMode) {
15542 case MachinePlaysWhite:
15543 if (WhiteOnMove(forwardMostMove)) {
15544 DisplayError(_("Wait until your turn."), 0);
15548 case BeginningOfGame:
15549 case MachinePlaysBlack:
15550 if (!WhiteOnMove(forwardMostMove)) {
15551 DisplayError(_("Wait until your turn."), 0);
15556 DisplayError(_("No hint available"), 0);
15559 SendToProgram("hint\n", &first);
15560 hintRequested = TRUE;
15566 ListGame * lg = (ListGame *) gameList.head;
15569 static int secondTime = FALSE;
15571 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15572 DisplayError(_("Game list not loaded or empty"), 0);
15576 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15579 DisplayNote(_("Book file exists! Try again for overwrite."));
15583 creatingBook = TRUE;
15584 secondTime = FALSE;
15586 /* Get list size */
15587 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15588 LoadGame(f, nItem, "", TRUE);
15589 AddGameToBook(TRUE);
15590 lg = (ListGame *) lg->node.succ;
15593 creatingBook = FALSE;
15600 if (appData.noChessProgram) return;
15601 switch (gameMode) {
15602 case MachinePlaysWhite:
15603 if (WhiteOnMove(forwardMostMove)) {
15604 DisplayError(_("Wait until your turn."), 0);
15608 case BeginningOfGame:
15609 case MachinePlaysBlack:
15610 if (!WhiteOnMove(forwardMostMove)) {
15611 DisplayError(_("Wait until your turn."), 0);
15616 EditPositionDone(TRUE);
15618 case TwoMachinesPlay:
15623 SendToProgram("bk\n", &first);
15624 bookOutput[0] = NULLCHAR;
15625 bookRequested = TRUE;
15631 char *tags = PGNTags(&gameInfo);
15632 TagsPopUp(tags, CmailMsg());
15636 /* end button procedures */
15639 PrintPosition (FILE *fp, int move)
15643 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15644 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15645 char c = PieceToChar(boards[move][i][j]);
15646 fputc(c == 'x' ? '.' : c, fp);
15647 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15650 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15651 fprintf(fp, "white to play\n");
15653 fprintf(fp, "black to play\n");
15657 PrintOpponents (FILE *fp)
15659 if (gameInfo.white != NULL) {
15660 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15666 /* Find last component of program's own name, using some heuristics */
15668 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15671 int local = (strcmp(host, "localhost") == 0);
15672 while (!local && (p = strchr(prog, ';')) != NULL) {
15674 while (*p == ' ') p++;
15677 if (*prog == '"' || *prog == '\'') {
15678 q = strchr(prog + 1, *prog);
15680 q = strchr(prog, ' ');
15682 if (q == NULL) q = prog + strlen(prog);
15684 while (p >= prog && *p != '/' && *p != '\\') p--;
15686 if(p == prog && *p == '"') p++;
15688 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15689 memcpy(buf, p, q - p);
15690 buf[q - p] = NULLCHAR;
15698 TimeControlTagValue ()
15701 if (!appData.clockMode) {
15702 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15703 } else if (movesPerSession > 0) {
15704 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15705 } else if (timeIncrement == 0) {
15706 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15708 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15710 return StrSave(buf);
15716 /* This routine is used only for certain modes */
15717 VariantClass v = gameInfo.variant;
15718 ChessMove r = GameUnfinished;
15721 if(keepInfo) return;
15723 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15724 r = gameInfo.result;
15725 p = gameInfo.resultDetails;
15726 gameInfo.resultDetails = NULL;
15728 ClearGameInfo(&gameInfo);
15729 gameInfo.variant = v;
15731 switch (gameMode) {
15732 case MachinePlaysWhite:
15733 gameInfo.event = StrSave( appData.pgnEventHeader );
15734 gameInfo.site = StrSave(HostName());
15735 gameInfo.date = PGNDate();
15736 gameInfo.round = StrSave("-");
15737 gameInfo.white = StrSave(first.tidy);
15738 gameInfo.black = StrSave(UserName());
15739 gameInfo.timeControl = TimeControlTagValue();
15742 case MachinePlaysBlack:
15743 gameInfo.event = StrSave( appData.pgnEventHeader );
15744 gameInfo.site = StrSave(HostName());
15745 gameInfo.date = PGNDate();
15746 gameInfo.round = StrSave("-");
15747 gameInfo.white = StrSave(UserName());
15748 gameInfo.black = StrSave(first.tidy);
15749 gameInfo.timeControl = TimeControlTagValue();
15752 case TwoMachinesPlay:
15753 gameInfo.event = StrSave( appData.pgnEventHeader );
15754 gameInfo.site = StrSave(HostName());
15755 gameInfo.date = PGNDate();
15758 snprintf(buf, MSG_SIZ, "%d", roundNr);
15759 gameInfo.round = StrSave(buf);
15761 gameInfo.round = StrSave("-");
15763 if (first.twoMachinesColor[0] == 'w') {
15764 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15765 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15767 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15768 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15770 gameInfo.timeControl = TimeControlTagValue();
15774 gameInfo.event = StrSave("Edited game");
15775 gameInfo.site = StrSave(HostName());
15776 gameInfo.date = PGNDate();
15777 gameInfo.round = StrSave("-");
15778 gameInfo.white = StrSave("-");
15779 gameInfo.black = StrSave("-");
15780 gameInfo.result = r;
15781 gameInfo.resultDetails = p;
15785 gameInfo.event = StrSave("Edited position");
15786 gameInfo.site = StrSave(HostName());
15787 gameInfo.date = PGNDate();
15788 gameInfo.round = StrSave("-");
15789 gameInfo.white = StrSave("-");
15790 gameInfo.black = StrSave("-");
15793 case IcsPlayingWhite:
15794 case IcsPlayingBlack:
15799 case PlayFromGameFile:
15800 gameInfo.event = StrSave("Game from non-PGN file");
15801 gameInfo.site = StrSave(HostName());
15802 gameInfo.date = PGNDate();
15803 gameInfo.round = StrSave("-");
15804 gameInfo.white = StrSave("?");
15805 gameInfo.black = StrSave("?");
15814 ReplaceComment (int index, char *text)
15820 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15821 pvInfoList[index-1].depth == len &&
15822 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15823 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15824 while (*text == '\n') text++;
15825 len = strlen(text);
15826 while (len > 0 && text[len - 1] == '\n') len--;
15828 if (commentList[index] != NULL)
15829 free(commentList[index]);
15832 commentList[index] = NULL;
15835 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15836 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15837 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15838 commentList[index] = (char *) malloc(len + 2);
15839 strncpy(commentList[index], text, len);
15840 commentList[index][len] = '\n';
15841 commentList[index][len + 1] = NULLCHAR;
15843 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15845 commentList[index] = (char *) malloc(len + 7);
15846 safeStrCpy(commentList[index], "{\n", 3);
15847 safeStrCpy(commentList[index]+2, text, len+1);
15848 commentList[index][len+2] = NULLCHAR;
15849 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15850 strcat(commentList[index], "\n}\n");
15855 CrushCRs (char *text)
15863 if (ch == '\r') continue;
15865 } while (ch != '\0');
15869 AppendComment (int index, char *text, Boolean addBraces)
15870 /* addBraces tells if we should add {} */
15875 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15876 if(addBraces == 3) addBraces = 0; else // force appending literally
15877 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15880 while (*text == '\n') text++;
15881 len = strlen(text);
15882 while (len > 0 && text[len - 1] == '\n') len--;
15883 text[len] = NULLCHAR;
15885 if (len == 0) return;
15887 if (commentList[index] != NULL) {
15888 Boolean addClosingBrace = addBraces;
15889 old = commentList[index];
15890 oldlen = strlen(old);
15891 while(commentList[index][oldlen-1] == '\n')
15892 commentList[index][--oldlen] = NULLCHAR;
15893 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15894 safeStrCpy(commentList[index], old, oldlen + len + 6);
15896 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15897 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15898 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15899 while (*text == '\n') { text++; len--; }
15900 commentList[index][--oldlen] = NULLCHAR;
15902 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15903 else strcat(commentList[index], "\n");
15904 strcat(commentList[index], text);
15905 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15906 else strcat(commentList[index], "\n");
15908 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15910 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15911 else commentList[index][0] = NULLCHAR;
15912 strcat(commentList[index], text);
15913 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15914 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15919 FindStr (char * text, char * sub_text)
15921 char * result = strstr( text, sub_text );
15923 if( result != NULL ) {
15924 result += strlen( sub_text );
15930 /* [AS] Try to extract PV info from PGN comment */
15931 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15933 GetInfoFromComment (int index, char * text)
15935 char * sep = text, *p;
15937 if( text != NULL && index > 0 ) {
15940 int time = -1, sec = 0, deci;
15941 char * s_eval = FindStr( text, "[%eval " );
15942 char * s_emt = FindStr( text, "[%emt " );
15944 if( s_eval != NULL || s_emt != NULL ) {
15946 if(0) { // [HGM] this code is not finished, and could actually be detrimental
15951 if( s_eval != NULL ) {
15952 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15956 if( delim != ']' ) {
15961 if( s_emt != NULL ) {
15966 /* We expect something like: [+|-]nnn.nn/dd */
15969 if(*text != '{') return text; // [HGM] braces: must be normal comment
15971 sep = strchr( text, '/' );
15972 if( sep == NULL || sep < (text+4) ) {
15977 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15978 if(p[1] == '(') { // comment starts with PV
15979 p = strchr(p, ')'); // locate end of PV
15980 if(p == NULL || sep < p+5) return text;
15981 // at this point we have something like "{(.*) +0.23/6 ..."
15982 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15983 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15984 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15986 time = -1; sec = -1; deci = -1;
15987 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15988 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15989 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15990 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15994 if( score_lo < 0 || score_lo >= 100 ) {
15998 if(sec >= 0) time = 600*time + 10*sec; else
15999 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16001 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16003 /* [HGM] PV time: now locate end of PV info */
16004 while( *++sep >= '0' && *sep <= '9'); // strip depth
16006 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16008 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16010 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16011 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16022 pvInfoList[index-1].depth = depth;
16023 pvInfoList[index-1].score = score;
16024 pvInfoList[index-1].time = 10*time; // centi-sec
16025 if(*sep == '}') *sep = 0; else *--sep = '{';
16026 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16032 SendToProgram (char *message, ChessProgramState *cps)
16034 int count, outCount, error;
16037 if (cps->pr == NoProc) return;
16040 if (appData.debugMode) {
16043 fprintf(debugFP, "%ld >%-6s: %s",
16044 SubtractTimeMarks(&now, &programStartTime),
16045 cps->which, message);
16047 fprintf(serverFP, "%ld >%-6s: %s",
16048 SubtractTimeMarks(&now, &programStartTime),
16049 cps->which, message), fflush(serverFP);
16052 count = strlen(message);
16053 outCount = OutputToProcess(cps->pr, message, count, &error);
16054 if (outCount < count && !exiting
16055 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16056 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16057 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16058 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16059 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16060 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16061 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16062 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16064 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16065 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16066 gameInfo.result = res;
16068 gameInfo.resultDetails = StrSave(buf);
16070 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16071 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16076 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16080 ChessProgramState *cps = (ChessProgramState *)closure;
16082 if (isr != cps->isr) return; /* Killed intentionally */
16085 RemoveInputSource(cps->isr);
16086 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16087 _(cps->which), cps->program);
16088 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16089 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16090 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16091 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16092 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16093 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16095 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16096 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16097 gameInfo.result = res;
16099 gameInfo.resultDetails = StrSave(buf);
16101 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16102 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16104 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16105 _(cps->which), cps->program);
16106 RemoveInputSource(cps->isr);
16108 /* [AS] Program is misbehaving badly... kill it */
16109 if( count == -2 ) {
16110 DestroyChildProcess( cps->pr, 9 );
16114 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16119 if ((end_str = strchr(message, '\r')) != NULL)
16120 *end_str = NULLCHAR;
16121 if ((end_str = strchr(message, '\n')) != NULL)
16122 *end_str = NULLCHAR;
16124 if (appData.debugMode) {
16125 TimeMark now; int print = 1;
16126 char *quote = ""; char c; int i;
16128 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16129 char start = message[0];
16130 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16131 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16132 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16133 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16134 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16135 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16136 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16137 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16138 sscanf(message, "hint: %c", &c)!=1 &&
16139 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16140 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16141 print = (appData.engineComments >= 2);
16143 message[0] = start; // restore original message
16147 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16148 SubtractTimeMarks(&now, &programStartTime), cps->which,
16152 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16153 SubtractTimeMarks(&now, &programStartTime), cps->which,
16155 message), fflush(serverFP);
16159 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16160 if (appData.icsEngineAnalyze) {
16161 if (strstr(message, "whisper") != NULL ||
16162 strstr(message, "kibitz") != NULL ||
16163 strstr(message, "tellics") != NULL) return;
16166 HandleMachineMove(message, cps);
16171 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16176 if( timeControl_2 > 0 ) {
16177 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16178 tc = timeControl_2;
16181 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16182 inc /= cps->timeOdds;
16183 st /= cps->timeOdds;
16185 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16188 /* Set exact time per move, normally using st command */
16189 if (cps->stKludge) {
16190 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16192 if (seconds == 0) {
16193 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16195 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16198 snprintf(buf, MSG_SIZ, "st %d\n", st);
16201 /* Set conventional or incremental time control, using level command */
16202 if (seconds == 0) {
16203 /* Note old gnuchess bug -- minutes:seconds used to not work.
16204 Fixed in later versions, but still avoid :seconds
16205 when seconds is 0. */
16206 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16208 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16209 seconds, inc/1000.);
16212 SendToProgram(buf, cps);
16214 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16215 /* Orthogonally, limit search to given depth */
16217 if (cps->sdKludge) {
16218 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16220 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16222 SendToProgram(buf, cps);
16225 if(cps->nps >= 0) { /* [HGM] nps */
16226 if(cps->supportsNPS == FALSE)
16227 cps->nps = -1; // don't use if engine explicitly says not supported!
16229 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16230 SendToProgram(buf, cps);
16235 ChessProgramState *
16237 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16239 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16240 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16246 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16248 char message[MSG_SIZ];
16251 /* Note: this routine must be called when the clocks are stopped
16252 or when they have *just* been set or switched; otherwise
16253 it will be off by the time since the current tick started.
16255 if (machineWhite) {
16256 time = whiteTimeRemaining / 10;
16257 otime = blackTimeRemaining / 10;
16259 time = blackTimeRemaining / 10;
16260 otime = whiteTimeRemaining / 10;
16262 /* [HGM] translate opponent's time by time-odds factor */
16263 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16265 if (time <= 0) time = 1;
16266 if (otime <= 0) otime = 1;
16268 snprintf(message, MSG_SIZ, "time %ld\n", time);
16269 SendToProgram(message, cps);
16271 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16272 SendToProgram(message, cps);
16276 EngineDefinedVariant (ChessProgramState *cps, int n)
16277 { // return name of n-th unknown variant that engine supports
16278 static char buf[MSG_SIZ];
16279 char *p, *s = cps->variants;
16280 if(!s) return NULL;
16281 do { // parse string from variants feature
16283 p = strchr(s, ',');
16284 if(p) *p = NULLCHAR;
16285 v = StringToVariant(s);
16286 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16287 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16288 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16291 if(n < 0) return buf;
16297 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16300 int len = strlen(name);
16303 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16305 sscanf(*p, "%d", &val);
16307 while (**p && **p != ' ')
16309 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16310 SendToProgram(buf, cps);
16317 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16320 int len = strlen(name);
16321 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16323 sscanf(*p, "%d", loc);
16324 while (**p && **p != ' ') (*p)++;
16325 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16326 SendToProgram(buf, cps);
16333 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16336 int len = strlen(name);
16337 if (strncmp((*p), name, len) == 0
16338 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16340 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16341 sscanf(*p, "%[^\"]", *loc);
16342 while (**p && **p != '\"') (*p)++;
16343 if (**p == '\"') (*p)++;
16344 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16345 SendToProgram(buf, cps);
16352 ParseOption (Option *opt, ChessProgramState *cps)
16353 // [HGM] options: process the string that defines an engine option, and determine
16354 // name, type, default value, and allowed value range
16356 char *p, *q, buf[MSG_SIZ];
16357 int n, min = (-1)<<31, max = 1<<31, def;
16359 if(p = strstr(opt->name, " -spin ")) {
16360 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16361 if(max < min) max = min; // enforce consistency
16362 if(def < min) def = min;
16363 if(def > max) def = max;
16368 } else if((p = strstr(opt->name, " -slider "))) {
16369 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16370 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16371 if(max < min) max = min; // enforce consistency
16372 if(def < min) def = min;
16373 if(def > max) def = max;
16377 opt->type = Spin; // Slider;
16378 } else if((p = strstr(opt->name, " -string "))) {
16379 opt->textValue = p+9;
16380 opt->type = TextBox;
16381 } else if((p = strstr(opt->name, " -file "))) {
16382 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16383 opt->textValue = p+7;
16384 opt->type = FileName; // FileName;
16385 } else if((p = strstr(opt->name, " -path "))) {
16386 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16387 opt->textValue = p+7;
16388 opt->type = PathName; // PathName;
16389 } else if(p = strstr(opt->name, " -check ")) {
16390 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16391 opt->value = (def != 0);
16392 opt->type = CheckBox;
16393 } else if(p = strstr(opt->name, " -combo ")) {
16394 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16395 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16396 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16397 opt->value = n = 0;
16398 while(q = StrStr(q, " /// ")) {
16399 n++; *q = 0; // count choices, and null-terminate each of them
16401 if(*q == '*') { // remember default, which is marked with * prefix
16405 cps->comboList[cps->comboCnt++] = q;
16407 cps->comboList[cps->comboCnt++] = NULL;
16409 opt->type = ComboBox;
16410 } else if(p = strstr(opt->name, " -button")) {
16411 opt->type = Button;
16412 } else if(p = strstr(opt->name, " -save")) {
16413 opt->type = SaveButton;
16414 } else return FALSE;
16415 *p = 0; // terminate option name
16416 // now look if the command-line options define a setting for this engine option.
16417 if(cps->optionSettings && cps->optionSettings[0])
16418 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16419 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16420 snprintf(buf, MSG_SIZ, "option %s", p);
16421 if(p = strstr(buf, ",")) *p = 0;
16422 if(q = strchr(buf, '=')) switch(opt->type) {
16424 for(n=0; n<opt->max; n++)
16425 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16428 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16432 opt->value = atoi(q+1);
16437 SendToProgram(buf, cps);
16443 FeatureDone (ChessProgramState *cps, int val)
16445 DelayedEventCallback cb = GetDelayedEvent();
16446 if ((cb == InitBackEnd3 && cps == &first) ||
16447 (cb == SettingsMenuIfReady && cps == &second) ||
16448 (cb == LoadEngine) ||
16449 (cb == TwoMachinesEventIfReady)) {
16450 CancelDelayedEvent();
16451 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16453 cps->initDone = val;
16454 if(val) cps->reload = FALSE;
16457 /* Parse feature command from engine */
16459 ParseFeatures (char *args, ChessProgramState *cps)
16467 while (*p == ' ') p++;
16468 if (*p == NULLCHAR) return;
16470 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16471 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16472 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16473 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16474 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16475 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16476 if (BoolFeature(&p, "reuse", &val, cps)) {
16477 /* Engine can disable reuse, but can't enable it if user said no */
16478 if (!val) cps->reuse = FALSE;
16481 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16482 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16483 if (gameMode == TwoMachinesPlay) {
16484 DisplayTwoMachinesTitle();
16490 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16491 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16492 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16493 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16494 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16495 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16496 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16497 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16498 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16499 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16500 if (IntFeature(&p, "done", &val, cps)) {
16501 FeatureDone(cps, val);
16504 /* Added by Tord: */
16505 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16506 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16507 /* End of additions by Tord */
16509 /* [HGM] added features: */
16510 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16511 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16512 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16513 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16514 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16515 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16516 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16517 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16518 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16519 FREE(cps->option[cps->nrOptions].name);
16520 cps->option[cps->nrOptions].name = q; q = NULL;
16521 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16522 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16523 SendToProgram(buf, cps);
16526 if(cps->nrOptions >= MAX_OPTIONS) {
16528 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16529 DisplayError(buf, 0);
16533 /* End of additions by HGM */
16535 /* unknown feature: complain and skip */
16537 while (*q && *q != '=') q++;
16538 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16539 SendToProgram(buf, cps);
16545 while (*p && *p != '\"') p++;
16546 if (*p == '\"') p++;
16548 while (*p && *p != ' ') p++;
16556 PeriodicUpdatesEvent (int newState)
16558 if (newState == appData.periodicUpdates)
16561 appData.periodicUpdates=newState;
16563 /* Display type changes, so update it now */
16564 // DisplayAnalysis();
16566 /* Get the ball rolling again... */
16568 AnalysisPeriodicEvent(1);
16569 StartAnalysisClock();
16574 PonderNextMoveEvent (int newState)
16576 if (newState == appData.ponderNextMove) return;
16577 if (gameMode == EditPosition) EditPositionDone(TRUE);
16579 SendToProgram("hard\n", &first);
16580 if (gameMode == TwoMachinesPlay) {
16581 SendToProgram("hard\n", &second);
16584 SendToProgram("easy\n", &first);
16585 thinkOutput[0] = NULLCHAR;
16586 if (gameMode == TwoMachinesPlay) {
16587 SendToProgram("easy\n", &second);
16590 appData.ponderNextMove = newState;
16594 NewSettingEvent (int option, int *feature, char *command, int value)
16598 if (gameMode == EditPosition) EditPositionDone(TRUE);
16599 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16600 if(feature == NULL || *feature) SendToProgram(buf, &first);
16601 if (gameMode == TwoMachinesPlay) {
16602 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16607 ShowThinkingEvent ()
16608 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16610 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16611 int newState = appData.showThinking
16612 // [HGM] thinking: other features now need thinking output as well
16613 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16615 if (oldState == newState) return;
16616 oldState = newState;
16617 if (gameMode == EditPosition) EditPositionDone(TRUE);
16619 SendToProgram("post\n", &first);
16620 if (gameMode == TwoMachinesPlay) {
16621 SendToProgram("post\n", &second);
16624 SendToProgram("nopost\n", &first);
16625 thinkOutput[0] = NULLCHAR;
16626 if (gameMode == TwoMachinesPlay) {
16627 SendToProgram("nopost\n", &second);
16630 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16634 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16636 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16637 if (pr == NoProc) return;
16638 AskQuestion(title, question, replyPrefix, pr);
16642 TypeInEvent (char firstChar)
16644 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16645 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16646 gameMode == AnalyzeMode || gameMode == EditGame ||
16647 gameMode == EditPosition || gameMode == IcsExamining ||
16648 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16649 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16650 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16651 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16652 gameMode == Training) PopUpMoveDialog(firstChar);
16656 TypeInDoneEvent (char *move)
16659 int n, fromX, fromY, toX, toY;
16661 ChessMove moveType;
16664 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16665 EditPositionPasteFEN(move);
16668 // [HGM] movenum: allow move number to be typed in any mode
16669 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16673 // undocumented kludge: allow command-line option to be typed in!
16674 // (potentially fatal, and does not implement the effect of the option.)
16675 // should only be used for options that are values on which future decisions will be made,
16676 // and definitely not on options that would be used during initialization.
16677 if(strstr(move, "!!! -") == move) {
16678 ParseArgsFromString(move+4);
16682 if (gameMode != EditGame && currentMove != forwardMostMove &&
16683 gameMode != Training) {
16684 DisplayMoveError(_("Displayed move is not current"));
16686 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16687 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16688 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16689 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16690 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16691 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16693 DisplayMoveError(_("Could not parse move"));
16699 DisplayMove (int moveNumber)
16701 char message[MSG_SIZ];
16703 char cpThinkOutput[MSG_SIZ];
16705 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16707 if (moveNumber == forwardMostMove - 1 ||
16708 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16710 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16712 if (strchr(cpThinkOutput, '\n')) {
16713 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16716 *cpThinkOutput = NULLCHAR;
16719 /* [AS] Hide thinking from human user */
16720 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16721 *cpThinkOutput = NULLCHAR;
16722 if( thinkOutput[0] != NULLCHAR ) {
16725 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16726 cpThinkOutput[i] = '.';
16728 cpThinkOutput[i] = NULLCHAR;
16729 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16733 if (moveNumber == forwardMostMove - 1 &&
16734 gameInfo.resultDetails != NULL) {
16735 if (gameInfo.resultDetails[0] == NULLCHAR) {
16736 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16738 snprintf(res, MSG_SIZ, " {%s} %s",
16739 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16745 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16746 DisplayMessage(res, cpThinkOutput);
16748 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16749 WhiteOnMove(moveNumber) ? " " : ".. ",
16750 parseList[moveNumber], res);
16751 DisplayMessage(message, cpThinkOutput);
16756 DisplayComment (int moveNumber, char *text)
16758 char title[MSG_SIZ];
16760 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16761 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16763 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16764 WhiteOnMove(moveNumber) ? " " : ".. ",
16765 parseList[moveNumber]);
16767 if (text != NULL && (appData.autoDisplayComment || commentUp))
16768 CommentPopUp(title, text);
16771 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16772 * might be busy thinking or pondering. It can be omitted if your
16773 * gnuchess is configured to stop thinking immediately on any user
16774 * input. However, that gnuchess feature depends on the FIONREAD
16775 * ioctl, which does not work properly on some flavors of Unix.
16778 Attention (ChessProgramState *cps)
16781 if (!cps->useSigint) return;
16782 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16783 switch (gameMode) {
16784 case MachinePlaysWhite:
16785 case MachinePlaysBlack:
16786 case TwoMachinesPlay:
16787 case IcsPlayingWhite:
16788 case IcsPlayingBlack:
16791 /* Skip if we know it isn't thinking */
16792 if (!cps->maybeThinking) return;
16793 if (appData.debugMode)
16794 fprintf(debugFP, "Interrupting %s\n", cps->which);
16795 InterruptChildProcess(cps->pr);
16796 cps->maybeThinking = FALSE;
16801 #endif /*ATTENTION*/
16807 if (whiteTimeRemaining <= 0) {
16810 if (appData.icsActive) {
16811 if (appData.autoCallFlag &&
16812 gameMode == IcsPlayingBlack && !blackFlag) {
16813 SendToICS(ics_prefix);
16814 SendToICS("flag\n");
16818 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16820 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16821 if (appData.autoCallFlag) {
16822 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16829 if (blackTimeRemaining <= 0) {
16832 if (appData.icsActive) {
16833 if (appData.autoCallFlag &&
16834 gameMode == IcsPlayingWhite && !whiteFlag) {
16835 SendToICS(ics_prefix);
16836 SendToICS("flag\n");
16840 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16842 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16843 if (appData.autoCallFlag) {
16844 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16855 CheckTimeControl ()
16857 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16858 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16861 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16863 if ( !WhiteOnMove(forwardMostMove) ) {
16864 /* White made time control */
16865 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16866 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16867 /* [HGM] time odds: correct new time quota for time odds! */
16868 / WhitePlayer()->timeOdds;
16869 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16871 lastBlack -= blackTimeRemaining;
16872 /* Black made time control */
16873 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16874 / WhitePlayer()->other->timeOdds;
16875 lastWhite = whiteTimeRemaining;
16880 DisplayBothClocks ()
16882 int wom = gameMode == EditPosition ?
16883 !blackPlaysFirst : WhiteOnMove(currentMove);
16884 DisplayWhiteClock(whiteTimeRemaining, wom);
16885 DisplayBlackClock(blackTimeRemaining, !wom);
16889 /* Timekeeping seems to be a portability nightmare. I think everyone
16890 has ftime(), but I'm really not sure, so I'm including some ifdefs
16891 to use other calls if you don't. Clocks will be less accurate if
16892 you have neither ftime nor gettimeofday.
16895 /* VS 2008 requires the #include outside of the function */
16896 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16897 #include <sys/timeb.h>
16900 /* Get the current time as a TimeMark */
16902 GetTimeMark (TimeMark *tm)
16904 #if HAVE_GETTIMEOFDAY
16906 struct timeval timeVal;
16907 struct timezone timeZone;
16909 gettimeofday(&timeVal, &timeZone);
16910 tm->sec = (long) timeVal.tv_sec;
16911 tm->ms = (int) (timeVal.tv_usec / 1000L);
16913 #else /*!HAVE_GETTIMEOFDAY*/
16916 // include <sys/timeb.h> / moved to just above start of function
16917 struct timeb timeB;
16920 tm->sec = (long) timeB.time;
16921 tm->ms = (int) timeB.millitm;
16923 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16924 tm->sec = (long) time(NULL);
16930 /* Return the difference in milliseconds between two
16931 time marks. We assume the difference will fit in a long!
16934 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16936 return 1000L*(tm2->sec - tm1->sec) +
16937 (long) (tm2->ms - tm1->ms);
16942 * Code to manage the game clocks.
16944 * In tournament play, black starts the clock and then white makes a move.
16945 * We give the human user a slight advantage if he is playing white---the
16946 * clocks don't run until he makes his first move, so it takes zero time.
16947 * Also, we don't account for network lag, so we could get out of sync
16948 * with GNU Chess's clock -- but then, referees are always right.
16951 static TimeMark tickStartTM;
16952 static long intendedTickLength;
16955 NextTickLength (long timeRemaining)
16957 long nominalTickLength, nextTickLength;
16959 if (timeRemaining > 0L && timeRemaining <= 10000L)
16960 nominalTickLength = 100L;
16962 nominalTickLength = 1000L;
16963 nextTickLength = timeRemaining % nominalTickLength;
16964 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16966 return nextTickLength;
16969 /* Adjust clock one minute up or down */
16971 AdjustClock (Boolean which, int dir)
16973 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16974 if(which) blackTimeRemaining += 60000*dir;
16975 else whiteTimeRemaining += 60000*dir;
16976 DisplayBothClocks();
16977 adjustedClock = TRUE;
16980 /* Stop clocks and reset to a fresh time control */
16984 (void) StopClockTimer();
16985 if (appData.icsActive) {
16986 whiteTimeRemaining = blackTimeRemaining = 0;
16987 } else if (searchTime) {
16988 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16989 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16990 } else { /* [HGM] correct new time quote for time odds */
16991 whiteTC = blackTC = fullTimeControlString;
16992 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16993 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16995 if (whiteFlag || blackFlag) {
16997 whiteFlag = blackFlag = FALSE;
16999 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17000 DisplayBothClocks();
17001 adjustedClock = FALSE;
17004 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17006 /* Decrement running clock by amount of time that has passed */
17010 long timeRemaining;
17011 long lastTickLength, fudge;
17014 if (!appData.clockMode) return;
17015 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17019 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17021 /* Fudge if we woke up a little too soon */
17022 fudge = intendedTickLength - lastTickLength;
17023 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17025 if (WhiteOnMove(forwardMostMove)) {
17026 if(whiteNPS >= 0) lastTickLength = 0;
17027 timeRemaining = whiteTimeRemaining -= lastTickLength;
17028 if(timeRemaining < 0 && !appData.icsActive) {
17029 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17030 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17031 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17032 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17035 DisplayWhiteClock(whiteTimeRemaining - fudge,
17036 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17038 if(blackNPS >= 0) lastTickLength = 0;
17039 timeRemaining = blackTimeRemaining -= lastTickLength;
17040 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17041 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17043 blackStartMove = forwardMostMove;
17044 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17047 DisplayBlackClock(blackTimeRemaining - fudge,
17048 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17050 if (CheckFlags()) return;
17052 if(twoBoards) { // count down secondary board's clocks as well
17053 activePartnerTime -= lastTickLength;
17055 if(activePartner == 'W')
17056 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17058 DisplayBlackClock(activePartnerTime, TRUE);
17063 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17064 StartClockTimer(intendedTickLength);
17066 /* if the time remaining has fallen below the alarm threshold, sound the
17067 * alarm. if the alarm has sounded and (due to a takeback or time control
17068 * with increment) the time remaining has increased to a level above the
17069 * threshold, reset the alarm so it can sound again.
17072 if (appData.icsActive && appData.icsAlarm) {
17074 /* make sure we are dealing with the user's clock */
17075 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17076 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17079 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17080 alarmSounded = FALSE;
17081 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17083 alarmSounded = TRUE;
17089 /* A player has just moved, so stop the previously running
17090 clock and (if in clock mode) start the other one.
17091 We redisplay both clocks in case we're in ICS mode, because
17092 ICS gives us an update to both clocks after every move.
17093 Note that this routine is called *after* forwardMostMove
17094 is updated, so the last fractional tick must be subtracted
17095 from the color that is *not* on move now.
17098 SwitchClocks (int newMoveNr)
17100 long lastTickLength;
17102 int flagged = FALSE;
17106 if (StopClockTimer() && appData.clockMode) {
17107 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17108 if (!WhiteOnMove(forwardMostMove)) {
17109 if(blackNPS >= 0) lastTickLength = 0;
17110 blackTimeRemaining -= lastTickLength;
17111 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17112 // if(pvInfoList[forwardMostMove].time == -1)
17113 pvInfoList[forwardMostMove].time = // use GUI time
17114 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17116 if(whiteNPS >= 0) lastTickLength = 0;
17117 whiteTimeRemaining -= lastTickLength;
17118 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17119 // if(pvInfoList[forwardMostMove].time == -1)
17120 pvInfoList[forwardMostMove].time =
17121 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17123 flagged = CheckFlags();
17125 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17126 CheckTimeControl();
17128 if (flagged || !appData.clockMode) return;
17130 switch (gameMode) {
17131 case MachinePlaysBlack:
17132 case MachinePlaysWhite:
17133 case BeginningOfGame:
17134 if (pausing) return;
17138 case PlayFromGameFile:
17146 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17147 if(WhiteOnMove(forwardMostMove))
17148 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17149 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17153 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17154 whiteTimeRemaining : blackTimeRemaining);
17155 StartClockTimer(intendedTickLength);
17159 /* Stop both clocks */
17163 long lastTickLength;
17166 if (!StopClockTimer()) return;
17167 if (!appData.clockMode) return;
17171 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17172 if (WhiteOnMove(forwardMostMove)) {
17173 if(whiteNPS >= 0) lastTickLength = 0;
17174 whiteTimeRemaining -= lastTickLength;
17175 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17177 if(blackNPS >= 0) lastTickLength = 0;
17178 blackTimeRemaining -= lastTickLength;
17179 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17184 /* Start clock of player on move. Time may have been reset, so
17185 if clock is already running, stop and restart it. */
17189 (void) StopClockTimer(); /* in case it was running already */
17190 DisplayBothClocks();
17191 if (CheckFlags()) return;
17193 if (!appData.clockMode) return;
17194 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17196 GetTimeMark(&tickStartTM);
17197 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17198 whiteTimeRemaining : blackTimeRemaining);
17200 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17201 whiteNPS = blackNPS = -1;
17202 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17203 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17204 whiteNPS = first.nps;
17205 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17206 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17207 blackNPS = first.nps;
17208 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17209 whiteNPS = second.nps;
17210 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17211 blackNPS = second.nps;
17212 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17214 StartClockTimer(intendedTickLength);
17218 TimeString (long ms)
17220 long second, minute, hour, day;
17222 static char buf[32];
17224 if (ms > 0 && ms <= 9900) {
17225 /* convert milliseconds to tenths, rounding up */
17226 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17228 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17232 /* convert milliseconds to seconds, rounding up */
17233 /* use floating point to avoid strangeness of integer division
17234 with negative dividends on many machines */
17235 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17242 day = second / (60 * 60 * 24);
17243 second = second % (60 * 60 * 24);
17244 hour = second / (60 * 60);
17245 second = second % (60 * 60);
17246 minute = second / 60;
17247 second = second % 60;
17250 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17251 sign, day, hour, minute, second);
17253 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17255 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17262 * This is necessary because some C libraries aren't ANSI C compliant yet.
17265 StrStr (char *string, char *match)
17269 length = strlen(match);
17271 for (i = strlen(string) - length; i >= 0; i--, string++)
17272 if (!strncmp(match, string, length))
17279 StrCaseStr (char *string, char *match)
17283 length = strlen(match);
17285 for (i = strlen(string) - length; i >= 0; i--, string++) {
17286 for (j = 0; j < length; j++) {
17287 if (ToLower(match[j]) != ToLower(string[j]))
17290 if (j == length) return string;
17298 StrCaseCmp (char *s1, char *s2)
17303 c1 = ToLower(*s1++);
17304 c2 = ToLower(*s2++);
17305 if (c1 > c2) return 1;
17306 if (c1 < c2) return -1;
17307 if (c1 == NULLCHAR) return 0;
17315 return isupper(c) ? tolower(c) : c;
17322 return islower(c) ? toupper(c) : c;
17324 #endif /* !_amigados */
17331 if ((ret = (char *) malloc(strlen(s) + 1)))
17333 safeStrCpy(ret, s, strlen(s)+1);
17339 StrSavePtr (char *s, char **savePtr)
17344 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17345 safeStrCpy(*savePtr, s, strlen(s)+1);
17357 clock = time((time_t *)NULL);
17358 tm = localtime(&clock);
17359 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17360 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17361 return StrSave(buf);
17366 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17368 int i, j, fromX, fromY, toX, toY;
17375 whiteToPlay = (gameMode == EditPosition) ?
17376 !blackPlaysFirst : (move % 2 == 0);
17379 /* Piece placement data */
17380 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17381 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17383 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17384 if (boards[move][i][j] == EmptySquare) {
17386 } else { ChessSquare piece = boards[move][i][j];
17387 if (emptycount > 0) {
17388 if(emptycount<10) /* [HGM] can be >= 10 */
17389 *p++ = '0' + emptycount;
17390 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17393 if(PieceToChar(piece) == '+') {
17394 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17396 piece = (ChessSquare)(DEMOTED piece);
17398 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17400 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17401 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17406 if (emptycount > 0) {
17407 if(emptycount<10) /* [HGM] can be >= 10 */
17408 *p++ = '0' + emptycount;
17409 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17416 /* [HGM] print Crazyhouse or Shogi holdings */
17417 if( gameInfo.holdingsWidth ) {
17418 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17420 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17421 piece = boards[move][i][BOARD_WIDTH-1];
17422 if( piece != EmptySquare )
17423 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17424 *p++ = PieceToChar(piece);
17426 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17427 piece = boards[move][BOARD_HEIGHT-i-1][0];
17428 if( piece != EmptySquare )
17429 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17430 *p++ = PieceToChar(piece);
17433 if( q == p ) *p++ = '-';
17439 *p++ = whiteToPlay ? 'w' : 'b';
17442 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17443 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17445 if(nrCastlingRights) {
17447 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17448 /* [HGM] write directly from rights */
17449 if(boards[move][CASTLING][2] != NoRights &&
17450 boards[move][CASTLING][0] != NoRights )
17451 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17452 if(boards[move][CASTLING][2] != NoRights &&
17453 boards[move][CASTLING][1] != NoRights )
17454 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17455 if(boards[move][CASTLING][5] != NoRights &&
17456 boards[move][CASTLING][3] != NoRights )
17457 *p++ = boards[move][CASTLING][3] + AAA;
17458 if(boards[move][CASTLING][5] != NoRights &&
17459 boards[move][CASTLING][4] != NoRights )
17460 *p++ = boards[move][CASTLING][4] + AAA;
17463 /* [HGM] write true castling rights */
17464 if( nrCastlingRights == 6 ) {
17466 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17467 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17468 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17469 boards[move][CASTLING][2] != NoRights );
17470 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17471 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17472 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17473 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17474 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17478 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17479 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17480 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17481 boards[move][CASTLING][5] != NoRights );
17482 if(gameInfo.variant == VariantSChess) {
17483 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17484 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17485 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17486 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17491 if (q == p) *p++ = '-'; /* No castling rights */
17495 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17496 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17497 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17498 /* En passant target square */
17499 if (move > backwardMostMove) {
17500 fromX = moveList[move - 1][0] - AAA;
17501 fromY = moveList[move - 1][1] - ONE;
17502 toX = moveList[move - 1][2] - AAA;
17503 toY = moveList[move - 1][3] - ONE;
17504 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17505 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17506 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17508 /* 2-square pawn move just happened */
17510 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17514 } else if(move == backwardMostMove) {
17515 // [HGM] perhaps we should always do it like this, and forget the above?
17516 if((signed char)boards[move][EP_STATUS] >= 0) {
17517 *p++ = boards[move][EP_STATUS] + AAA;
17518 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17530 { int i = 0, j=move;
17532 /* [HGM] find reversible plies */
17533 if (appData.debugMode) { int k;
17534 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17535 for(k=backwardMostMove; k<=forwardMostMove; k++)
17536 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17540 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17541 if( j == backwardMostMove ) i += initialRulePlies;
17542 sprintf(p, "%d ", i);
17543 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17545 /* Fullmove number */
17546 sprintf(p, "%d", (move / 2) + 1);
17547 } else *--p = NULLCHAR;
17549 return StrSave(buf);
17553 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17557 int emptycount, virgin[BOARD_FILES];
17562 /* Piece placement data */
17563 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17566 if (*p == '/' || *p == ' ' || *p == '[' ) {
17568 emptycount = gameInfo.boardWidth - j;
17569 while (emptycount--)
17570 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17571 if (*p == '/') p++;
17572 else if(autoSize) { // we stumbled unexpectedly into end of board
17573 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17574 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17576 appData.NrRanks = gameInfo.boardHeight - i; i=0;
17579 #if(BOARD_FILES >= 10)
17580 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17581 p++; emptycount=10;
17582 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17583 while (emptycount--)
17584 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17586 } else if (*p == '*') {
17587 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17588 } else if (isdigit(*p)) {
17589 emptycount = *p++ - '0';
17590 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17591 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17592 while (emptycount--)
17593 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17594 } else if (*p == '+' || isalpha(*p)) {
17595 if (j >= gameInfo.boardWidth) return FALSE;
17597 piece = CharToPiece(*++p);
17598 if(piece == EmptySquare) return FALSE; /* unknown piece */
17599 piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17600 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17601 } else piece = CharToPiece(*p++);
17603 if(piece==EmptySquare) return FALSE; /* unknown piece */
17604 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17605 piece = (ChessSquare) (PROMOTED piece);
17606 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17609 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17615 while (*p == '/' || *p == ' ') p++;
17617 if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17619 /* [HGM] by default clear Crazyhouse holdings, if present */
17620 if(gameInfo.holdingsWidth) {
17621 for(i=0; i<BOARD_HEIGHT; i++) {
17622 board[i][0] = EmptySquare; /* black holdings */
17623 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17624 board[i][1] = (ChessSquare) 0; /* black counts */
17625 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17629 /* [HGM] look for Crazyhouse holdings here */
17630 while(*p==' ') p++;
17631 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17633 if(*p == '-' ) p++; /* empty holdings */ else {
17634 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17635 /* if we would allow FEN reading to set board size, we would */
17636 /* have to add holdings and shift the board read so far here */
17637 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17639 if((int) piece >= (int) BlackPawn ) {
17640 i = (int)piece - (int)BlackPawn;
17641 i = PieceToNumber((ChessSquare)i);
17642 if( i >= gameInfo.holdingsSize ) return FALSE;
17643 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17644 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17646 i = (int)piece - (int)WhitePawn;
17647 i = PieceToNumber((ChessSquare)i);
17648 if( i >= gameInfo.holdingsSize ) return FALSE;
17649 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17650 board[i][BOARD_WIDTH-2]++; /* black holdings */
17657 while(*p == ' ') p++;
17661 if(appData.colorNickNames) {
17662 if( c == appData.colorNickNames[0] ) c = 'w'; else
17663 if( c == appData.colorNickNames[1] ) c = 'b';
17667 *blackPlaysFirst = FALSE;
17670 *blackPlaysFirst = TRUE;
17676 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17677 /* return the extra info in global variiables */
17679 /* set defaults in case FEN is incomplete */
17680 board[EP_STATUS] = EP_UNKNOWN;
17681 for(i=0; i<nrCastlingRights; i++ ) {
17682 board[CASTLING][i] =
17683 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17684 } /* assume possible unless obviously impossible */
17685 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17686 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17687 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17688 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17689 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17690 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17691 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17692 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17695 while(*p==' ') p++;
17696 if(nrCastlingRights) {
17697 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17698 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17699 /* castling indicator present, so default becomes no castlings */
17700 for(i=0; i<nrCastlingRights; i++ ) {
17701 board[CASTLING][i] = NoRights;
17704 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17705 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17706 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17707 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17708 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17710 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17711 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17712 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17714 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17715 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17716 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17717 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17718 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17719 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17722 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17723 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17724 board[CASTLING][2] = whiteKingFile;
17725 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17726 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17729 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17730 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17731 board[CASTLING][2] = whiteKingFile;
17732 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17733 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17736 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17737 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17738 board[CASTLING][5] = blackKingFile;
17739 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17740 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17743 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17744 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17745 board[CASTLING][5] = blackKingFile;
17746 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17747 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17750 default: /* FRC castlings */
17751 if(c >= 'a') { /* black rights */
17752 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17753 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17754 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17755 if(i == BOARD_RGHT) break;
17756 board[CASTLING][5] = i;
17758 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17759 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17761 board[CASTLING][3] = c;
17763 board[CASTLING][4] = c;
17764 } else { /* white rights */
17765 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17766 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17767 if(board[0][i] == WhiteKing) break;
17768 if(i == BOARD_RGHT) break;
17769 board[CASTLING][2] = i;
17770 c -= AAA - 'a' + 'A';
17771 if(board[0][c] >= WhiteKing) break;
17773 board[CASTLING][0] = c;
17775 board[CASTLING][1] = c;
17779 for(i=0; i<nrCastlingRights; i++)
17780 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17781 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17782 if (appData.debugMode) {
17783 fprintf(debugFP, "FEN castling rights:");
17784 for(i=0; i<nrCastlingRights; i++)
17785 fprintf(debugFP, " %d", board[CASTLING][i]);
17786 fprintf(debugFP, "\n");
17789 while(*p==' ') p++;
17792 /* read e.p. field in games that know e.p. capture */
17793 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17794 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17795 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17797 p++; board[EP_STATUS] = EP_NONE;
17799 char c = *p++ - AAA;
17801 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17802 if(*p >= '0' && *p <='9') p++;
17803 board[EP_STATUS] = c;
17808 if(sscanf(p, "%d", &i) == 1) {
17809 FENrulePlies = i; /* 50-move ply counter */
17810 /* (The move number is still ignored) */
17817 EditPositionPasteFEN (char *fen)
17820 Board initial_position;
17822 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17823 DisplayError(_("Bad FEN position in clipboard"), 0);
17826 int savedBlackPlaysFirst = blackPlaysFirst;
17827 EditPositionEvent();
17828 blackPlaysFirst = savedBlackPlaysFirst;
17829 CopyBoard(boards[0], initial_position);
17830 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17831 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17832 DisplayBothClocks();
17833 DrawPosition(FALSE, boards[currentMove]);
17838 static char cseq[12] = "\\ ";
17841 set_cont_sequence (char *new_seq)
17846 // handle bad attempts to set the sequence
17848 return 0; // acceptable error - no debug
17850 len = strlen(new_seq);
17851 ret = (len > 0) && (len < sizeof(cseq));
17853 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17854 else if (appData.debugMode)
17855 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17860 reformat a source message so words don't cross the width boundary. internal
17861 newlines are not removed. returns the wrapped size (no null character unless
17862 included in source message). If dest is NULL, only calculate the size required
17863 for the dest buffer. lp argument indicats line position upon entry, and it's
17864 passed back upon exit.
17867 wrap (char *dest, char *src, int count, int width, int *lp)
17869 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17871 cseq_len = strlen(cseq);
17872 old_line = line = *lp;
17873 ansi = len = clen = 0;
17875 for (i=0; i < count; i++)
17877 if (src[i] == '\033')
17880 // if we hit the width, back up
17881 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17883 // store i & len in case the word is too long
17884 old_i = i, old_len = len;
17886 // find the end of the last word
17887 while (i && src[i] != ' ' && src[i] != '\n')
17893 // word too long? restore i & len before splitting it
17894 if ((old_i-i+clen) >= width)
17901 if (i && src[i-1] == ' ')
17904 if (src[i] != ' ' && src[i] != '\n')
17911 // now append the newline and continuation sequence
17916 strncpy(dest+len, cseq, cseq_len);
17924 dest[len] = src[i];
17928 if (src[i] == '\n')
17933 if (dest && appData.debugMode)
17935 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17936 count, width, line, len, *lp);
17937 show_bytes(debugFP, src, count);
17938 fprintf(debugFP, "\ndest: ");
17939 show_bytes(debugFP, dest, len);
17940 fprintf(debugFP, "\n");
17942 *lp = dest ? line : old_line;
17947 // [HGM] vari: routines for shelving variations
17948 Boolean modeRestore = FALSE;
17951 PushInner (int firstMove, int lastMove)
17953 int i, j, nrMoves = lastMove - firstMove;
17955 // push current tail of game on stack
17956 savedResult[storedGames] = gameInfo.result;
17957 savedDetails[storedGames] = gameInfo.resultDetails;
17958 gameInfo.resultDetails = NULL;
17959 savedFirst[storedGames] = firstMove;
17960 savedLast [storedGames] = lastMove;
17961 savedFramePtr[storedGames] = framePtr;
17962 framePtr -= nrMoves; // reserve space for the boards
17963 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17964 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17965 for(j=0; j<MOVE_LEN; j++)
17966 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17967 for(j=0; j<2*MOVE_LEN; j++)
17968 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17969 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17970 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17971 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17972 pvInfoList[firstMove+i-1].depth = 0;
17973 commentList[framePtr+i] = commentList[firstMove+i];
17974 commentList[firstMove+i] = NULL;
17978 forwardMostMove = firstMove; // truncate game so we can start variation
17982 PushTail (int firstMove, int lastMove)
17984 if(appData.icsActive) { // only in local mode
17985 forwardMostMove = currentMove; // mimic old ICS behavior
17988 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17990 PushInner(firstMove, lastMove);
17991 if(storedGames == 1) GreyRevert(FALSE);
17992 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17996 PopInner (Boolean annotate)
17999 char buf[8000], moveBuf[20];
18001 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18002 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18003 nrMoves = savedLast[storedGames] - currentMove;
18006 if(!WhiteOnMove(currentMove))
18007 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18008 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18009 for(i=currentMove; i<forwardMostMove; i++) {
18011 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18012 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18013 strcat(buf, moveBuf);
18014 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18015 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18019 for(i=1; i<=nrMoves; i++) { // copy last variation back
18020 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18021 for(j=0; j<MOVE_LEN; j++)
18022 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18023 for(j=0; j<2*MOVE_LEN; j++)
18024 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18025 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18026 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18027 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18028 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18029 commentList[currentMove+i] = commentList[framePtr+i];
18030 commentList[framePtr+i] = NULL;
18032 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18033 framePtr = savedFramePtr[storedGames];
18034 gameInfo.result = savedResult[storedGames];
18035 if(gameInfo.resultDetails != NULL) {
18036 free(gameInfo.resultDetails);
18038 gameInfo.resultDetails = savedDetails[storedGames];
18039 forwardMostMove = currentMove + nrMoves;
18043 PopTail (Boolean annotate)
18045 if(appData.icsActive) return FALSE; // only in local mode
18046 if(!storedGames) return FALSE; // sanity
18047 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18049 PopInner(annotate);
18050 if(currentMove < forwardMostMove) ForwardEvent(); else
18051 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18053 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18059 { // remove all shelved variations
18061 for(i=0; i<storedGames; i++) {
18062 if(savedDetails[i])
18063 free(savedDetails[i]);
18064 savedDetails[i] = NULL;
18066 for(i=framePtr; i<MAX_MOVES; i++) {
18067 if(commentList[i]) free(commentList[i]);
18068 commentList[i] = NULL;
18070 framePtr = MAX_MOVES-1;
18075 LoadVariation (int index, char *text)
18076 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18077 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18078 int level = 0, move;
18080 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18081 // first find outermost bracketing variation
18082 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18083 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18084 if(*p == '{') wait = '}'; else
18085 if(*p == '[') wait = ']'; else
18086 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18087 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18089 if(*p == wait) wait = NULLCHAR; // closing ]} found
18092 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18093 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18094 end[1] = NULLCHAR; // clip off comment beyond variation
18095 ToNrEvent(currentMove-1);
18096 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18097 // kludge: use ParsePV() to append variation to game
18098 move = currentMove;
18099 ParsePV(start, TRUE, TRUE);
18100 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18101 ClearPremoveHighlights();
18103 ToNrEvent(currentMove+1);
18109 char *p, *q, buf[MSG_SIZ];
18110 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18111 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18112 ParseArgsFromString(buf);
18113 ActivateTheme(TRUE); // also redo colors
18117 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18120 q = appData.themeNames;
18121 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18122 if(appData.useBitmaps) {
18123 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18124 appData.liteBackTextureFile, appData.darkBackTextureFile,
18125 appData.liteBackTextureMode,
18126 appData.darkBackTextureMode );
18128 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18129 Col2Text(2), // lightSquareColor
18130 Col2Text(3) ); // darkSquareColor
18132 if(appData.useBorder) {
18133 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18136 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18138 if(appData.useFont) {
18139 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18140 appData.renderPiecesWithFont,
18141 appData.fontToPieceTable,
18142 Col2Text(9), // appData.fontBackColorWhite
18143 Col2Text(10) ); // appData.fontForeColorBlack
18145 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18146 appData.pieceDirectory);
18147 if(!appData.pieceDirectory[0])
18148 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18149 Col2Text(0), // whitePieceColor
18150 Col2Text(1) ); // blackPieceColor
18152 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18153 Col2Text(4), // highlightSquareColor
18154 Col2Text(5) ); // premoveHighlightColor
18155 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18156 if(insert != q) insert[-1] = NULLCHAR;
18157 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18160 ActivateTheme(FALSE);