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:
5405 case WhiteCapturesEnPassant:
5406 case BlackCapturesEnPassant:
5407 case WhiteKingSideCastle:
5408 case WhiteQueenSideCastle:
5409 case BlackKingSideCastle:
5410 case BlackQueenSideCastle:
5411 case WhiteKingSideCastleWild:
5412 case WhiteQueenSideCastleWild:
5413 case BlackKingSideCastleWild:
5414 case BlackQueenSideCastleWild:
5415 /* Code added by Tord: */
5416 case WhiteHSideCastleFR:
5417 case WhiteASideCastleFR:
5418 case BlackHSideCastleFR:
5419 case BlackASideCastleFR:
5420 /* End of code added by Tord */
5421 case IllegalMove: /* bug or odd chess variant */
5422 *fromX = currentMoveString[0] - AAA;
5423 *fromY = currentMoveString[1] - ONE;
5424 *toX = currentMoveString[2] - AAA;
5425 *toY = currentMoveString[3] - ONE;
5426 *promoChar = currentMoveString[4];
5427 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5428 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5429 if (appData.debugMode) {
5430 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5432 *fromX = *fromY = *toX = *toY = 0;
5435 if (appData.testLegality) {
5436 return (*moveType != IllegalMove);
5438 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5439 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5444 *fromX = *moveType == WhiteDrop ?
5445 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5446 (int) CharToPiece(ToLower(currentMoveString[0]));
5448 *toX = currentMoveString[2] - AAA;
5449 *toY = currentMoveString[3] - ONE;
5450 *promoChar = NULLCHAR;
5454 case ImpossibleMove:
5464 if (appData.debugMode) {
5465 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5468 *fromX = *fromY = *toX = *toY = 0;
5469 *promoChar = NULLCHAR;
5474 Boolean pushed = FALSE;
5475 char *lastParseAttempt;
5478 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5479 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5480 int fromX, fromY, toX, toY; char promoChar;
5485 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5486 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5487 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5490 endPV = forwardMostMove;
5492 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5493 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5494 lastParseAttempt = pv;
5495 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5496 if(!valid && nr == 0 &&
5497 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5498 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5499 // Hande case where played move is different from leading PV move
5500 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5501 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5502 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5503 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5504 endPV += 2; // if position different, keep this
5505 moveList[endPV-1][0] = fromX + AAA;
5506 moveList[endPV-1][1] = fromY + ONE;
5507 moveList[endPV-1][2] = toX + AAA;
5508 moveList[endPV-1][3] = toY + ONE;
5509 parseList[endPV-1][0] = NULLCHAR;
5510 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5513 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5514 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5515 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5516 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5517 valid++; // allow comments in PV
5521 if(endPV+1 > framePtr) break; // no space, truncate
5524 CopyBoard(boards[endPV], boards[endPV-1]);
5525 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5526 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5527 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5528 CoordsToAlgebraic(boards[endPV - 1],
5529 PosFlags(endPV - 1),
5530 fromY, fromX, toY, toX, promoChar,
5531 parseList[endPV - 1]);
5533 if(atEnd == 2) return; // used hidden, for PV conversion
5534 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5535 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5536 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5537 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5538 DrawPosition(TRUE, boards[currentMove]);
5542 MultiPV (ChessProgramState *cps)
5543 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5545 for(i=0; i<cps->nrOptions; i++)
5546 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5551 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5554 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5556 int startPV, multi, lineStart, origIndex = index;
5557 char *p, buf2[MSG_SIZ];
5558 ChessProgramState *cps = (pane ? &second : &first);
5560 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5561 lastX = x; lastY = y;
5562 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5563 lineStart = startPV = index;
5564 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5565 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5567 do{ while(buf[index] && buf[index] != '\n') index++;
5568 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5570 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5571 int n = cps->option[multi].value;
5572 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5573 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5574 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5575 cps->option[multi].value = n;
5578 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5579 ExcludeClick(origIndex - lineStart);
5582 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5583 *start = startPV; *end = index-1;
5584 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5591 static char buf[10*MSG_SIZ];
5592 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5594 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5595 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5596 for(i = forwardMostMove; i<endPV; i++){
5597 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5598 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5601 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5602 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5603 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5609 LoadPV (int x, int y)
5610 { // called on right mouse click to load PV
5611 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5612 lastX = x; lastY = y;
5613 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5621 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5622 if(endPV < 0) return;
5623 if(appData.autoCopyPV) CopyFENToClipboard();
5625 if(extendGame && currentMove > forwardMostMove) {
5626 Boolean saveAnimate = appData.animate;
5628 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5629 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5630 } else storedGames--; // abandon shelved tail of original game
5633 forwardMostMove = currentMove;
5634 currentMove = oldFMM;
5635 appData.animate = FALSE;
5636 ToNrEvent(forwardMostMove);
5637 appData.animate = saveAnimate;
5639 currentMove = forwardMostMove;
5640 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5641 ClearPremoveHighlights();
5642 DrawPosition(TRUE, boards[currentMove]);
5646 MovePV (int x, int y, int h)
5647 { // step through PV based on mouse coordinates (called on mouse move)
5648 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5650 // we must somehow check if right button is still down (might be released off board!)
5651 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5652 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5653 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5655 lastX = x; lastY = y;
5657 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5658 if(endPV < 0) return;
5659 if(y < margin) step = 1; else
5660 if(y > h - margin) step = -1;
5661 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5662 currentMove += step;
5663 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5664 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5665 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5666 DrawPosition(FALSE, boards[currentMove]);
5670 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5671 // All positions will have equal probability, but the current method will not provide a unique
5672 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5678 int piecesLeft[(int)BlackPawn];
5679 int seed, nrOfShuffles;
5682 GetPositionNumber ()
5683 { // sets global variable seed
5686 seed = appData.defaultFrcPosition;
5687 if(seed < 0) { // randomize based on time for negative FRC position numbers
5688 for(i=0; i<50; i++) seed += random();
5689 seed = random() ^ random() >> 8 ^ random() << 8;
5690 if(seed<0) seed = -seed;
5695 put (Board board, int pieceType, int rank, int n, int shade)
5696 // put the piece on the (n-1)-th empty squares of the given shade
5700 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5701 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5702 board[rank][i] = (ChessSquare) pieceType;
5703 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5705 piecesLeft[pieceType]--;
5714 AddOnePiece (Board board, int pieceType, int rank, int shade)
5715 // calculate where the next piece goes, (any empty square), and put it there
5719 i = seed % squaresLeft[shade];
5720 nrOfShuffles *= squaresLeft[shade];
5721 seed /= squaresLeft[shade];
5722 put(board, pieceType, rank, i, shade);
5726 AddTwoPieces (Board board, int pieceType, int rank)
5727 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5729 int i, n=squaresLeft[ANY], j=n-1, k;
5731 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5732 i = seed % k; // pick one
5735 while(i >= j) i -= j--;
5736 j = n - 1 - j; i += j;
5737 put(board, pieceType, rank, j, ANY);
5738 put(board, pieceType, rank, i, ANY);
5742 SetUpShuffle (Board board, int number)
5746 GetPositionNumber(); nrOfShuffles = 1;
5748 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5749 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5750 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5752 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5754 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5755 p = (int) board[0][i];
5756 if(p < (int) BlackPawn) piecesLeft[p] ++;
5757 board[0][i] = EmptySquare;
5760 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5761 // shuffles restricted to allow normal castling put KRR first
5762 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5763 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5764 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5765 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5766 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5767 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5768 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5769 put(board, WhiteRook, 0, 0, ANY);
5770 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5773 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5774 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5775 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5776 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5777 while(piecesLeft[p] >= 2) {
5778 AddOnePiece(board, p, 0, LITE);
5779 AddOnePiece(board, p, 0, DARK);
5781 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5784 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5785 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5786 // but we leave King and Rooks for last, to possibly obey FRC restriction
5787 if(p == (int)WhiteRook) continue;
5788 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5789 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5792 // now everything is placed, except perhaps King (Unicorn) and Rooks
5794 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5795 // Last King gets castling rights
5796 while(piecesLeft[(int)WhiteUnicorn]) {
5797 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5798 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5801 while(piecesLeft[(int)WhiteKing]) {
5802 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5803 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5808 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5809 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5812 // Only Rooks can be left; simply place them all
5813 while(piecesLeft[(int)WhiteRook]) {
5814 i = put(board, WhiteRook, 0, 0, ANY);
5815 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5818 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5820 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5823 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5824 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5827 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5831 SetCharTable (char *table, const char * map)
5832 /* [HGM] moved here from winboard.c because of its general usefulness */
5833 /* Basically a safe strcpy that uses the last character as King */
5835 int result = FALSE; int NrPieces;
5837 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5838 && NrPieces >= 12 && !(NrPieces&1)) {
5839 int i; /* [HGM] Accept even length from 12 to 34 */
5841 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5842 for( i=0; i<NrPieces/2-1; i++ ) {
5844 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5846 table[(int) WhiteKing] = map[NrPieces/2-1];
5847 table[(int) BlackKing] = map[NrPieces-1];
5856 Prelude (Board board)
5857 { // [HGM] superchess: random selection of exo-pieces
5858 int i, j, k; ChessSquare p;
5859 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5861 GetPositionNumber(); // use FRC position number
5863 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5864 SetCharTable(pieceToChar, appData.pieceToCharTable);
5865 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5866 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5869 j = seed%4; seed /= 4;
5870 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5871 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5872 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5873 j = seed%3 + (seed%3 >= j); seed /= 3;
5874 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5875 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5876 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5877 j = seed%3; seed /= 3;
5878 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5879 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5880 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5881 j = seed%2 + (seed%2 >= j); seed /= 2;
5882 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5883 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5884 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5885 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5886 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5887 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5888 put(board, exoPieces[0], 0, 0, ANY);
5889 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5893 InitPosition (int redraw)
5895 ChessSquare (* pieces)[BOARD_FILES];
5896 int i, j, pawnRow=1, pieceRows=1, overrule,
5897 oldx = gameInfo.boardWidth,
5898 oldy = gameInfo.boardHeight,
5899 oldh = gameInfo.holdingsWidth;
5902 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5904 /* [AS] Initialize pv info list [HGM] and game status */
5906 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5907 pvInfoList[i].depth = 0;
5908 boards[i][EP_STATUS] = EP_NONE;
5909 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5912 initialRulePlies = 0; /* 50-move counter start */
5914 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5915 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5919 /* [HGM] logic here is completely changed. In stead of full positions */
5920 /* the initialized data only consist of the two backranks. The switch */
5921 /* selects which one we will use, which is than copied to the Board */
5922 /* initialPosition, which for the rest is initialized by Pawns and */
5923 /* empty squares. This initial position is then copied to boards[0], */
5924 /* possibly after shuffling, so that it remains available. */
5926 gameInfo.holdingsWidth = 0; /* default board sizes */
5927 gameInfo.boardWidth = 8;
5928 gameInfo.boardHeight = 8;
5929 gameInfo.holdingsSize = 0;
5930 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5931 for(i=0; i<BOARD_FILES-2; i++)
5932 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5933 initialPosition[EP_STATUS] = EP_NONE;
5934 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5935 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5936 SetCharTable(pieceNickName, appData.pieceNickNames);
5937 else SetCharTable(pieceNickName, "............");
5940 switch (gameInfo.variant) {
5941 case VariantFischeRandom:
5942 shuffleOpenings = TRUE;
5945 case VariantShatranj:
5946 pieces = ShatranjArray;
5947 nrCastlingRights = 0;
5948 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5951 pieces = makrukArray;
5952 nrCastlingRights = 0;
5953 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5956 pieces = aseanArray;
5957 nrCastlingRights = 0;
5958 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5960 case VariantTwoKings:
5961 pieces = twoKingsArray;
5964 pieces = GrandArray;
5965 nrCastlingRights = 0;
5966 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5967 gameInfo.boardWidth = 10;
5968 gameInfo.boardHeight = 10;
5969 gameInfo.holdingsSize = 7;
5971 case VariantCapaRandom:
5972 shuffleOpenings = TRUE;
5973 case VariantCapablanca:
5974 pieces = CapablancaArray;
5975 gameInfo.boardWidth = 10;
5976 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5979 pieces = GothicArray;
5980 gameInfo.boardWidth = 10;
5981 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5984 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5985 gameInfo.holdingsSize = 7;
5986 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5989 pieces = JanusArray;
5990 gameInfo.boardWidth = 10;
5991 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5992 nrCastlingRights = 6;
5993 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5994 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5995 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5996 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5997 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5998 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6001 pieces = FalconArray;
6002 gameInfo.boardWidth = 10;
6003 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6005 case VariantXiangqi:
6006 pieces = XiangqiArray;
6007 gameInfo.boardWidth = 9;
6008 gameInfo.boardHeight = 10;
6009 nrCastlingRights = 0;
6010 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6013 pieces = ShogiArray;
6014 gameInfo.boardWidth = 9;
6015 gameInfo.boardHeight = 9;
6016 gameInfo.holdingsSize = 7;
6017 nrCastlingRights = 0;
6018 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6021 pieces = ChuArray; pieceRows = 3;
6022 gameInfo.boardWidth = 12;
6023 gameInfo.boardHeight = 12;
6024 nrCastlingRights = 0;
6025 SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6026 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6028 case VariantCourier:
6029 pieces = CourierArray;
6030 gameInfo.boardWidth = 12;
6031 nrCastlingRights = 0;
6032 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6034 case VariantKnightmate:
6035 pieces = KnightmateArray;
6036 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6038 case VariantSpartan:
6039 pieces = SpartanArray;
6040 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6044 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6047 pieces = fairyArray;
6048 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6051 pieces = GreatArray;
6052 gameInfo.boardWidth = 10;
6053 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6054 gameInfo.holdingsSize = 8;
6058 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6059 gameInfo.holdingsSize = 8;
6060 startedFromSetupPosition = TRUE;
6062 case VariantCrazyhouse:
6063 case VariantBughouse:
6065 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6066 gameInfo.holdingsSize = 5;
6068 case VariantWildCastle:
6070 /* !!?shuffle with kings guaranteed to be on d or e file */
6071 shuffleOpenings = 1;
6073 case VariantNoCastle:
6075 nrCastlingRights = 0;
6076 /* !!?unconstrained back-rank shuffle */
6077 shuffleOpenings = 1;
6082 if(appData.NrFiles >= 0) {
6083 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6084 gameInfo.boardWidth = appData.NrFiles;
6086 if(appData.NrRanks >= 0) {
6087 gameInfo.boardHeight = appData.NrRanks;
6089 if(appData.holdingsSize >= 0) {
6090 i = appData.holdingsSize;
6091 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6092 gameInfo.holdingsSize = i;
6094 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6095 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6096 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6098 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6099 if(pawnRow < 1) pawnRow = 1;
6100 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6101 if(gameInfo.variant == VariantChu) pawnRow = 3;
6103 /* User pieceToChar list overrules defaults */
6104 if(appData.pieceToCharTable != NULL)
6105 SetCharTable(pieceToChar, appData.pieceToCharTable);
6107 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6109 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6110 s = (ChessSquare) 0; /* account holding counts in guard band */
6111 for( i=0; i<BOARD_HEIGHT; i++ )
6112 initialPosition[i][j] = s;
6114 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6115 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6116 initialPosition[pawnRow][j] = WhitePawn;
6117 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6118 if(gameInfo.variant == VariantXiangqi) {
6120 initialPosition[pawnRow][j] =
6121 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6122 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6123 initialPosition[2][j] = WhiteCannon;
6124 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6128 if(gameInfo.variant == VariantChu) {
6129 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6130 initialPosition[pawnRow+1][j] = WhiteCobra,
6131 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6132 for(i=1; i<pieceRows; i++) {
6133 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6134 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6137 if(gameInfo.variant == VariantGrand) {
6138 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6139 initialPosition[0][j] = WhiteRook;
6140 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6143 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
6145 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6148 initialPosition[1][j] = WhiteBishop;
6149 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6151 initialPosition[1][j] = WhiteRook;
6152 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6155 if( nrCastlingRights == -1) {
6156 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6157 /* This sets default castling rights from none to normal corners */
6158 /* Variants with other castling rights must set them themselves above */
6159 nrCastlingRights = 6;
6161 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6162 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6163 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6164 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6165 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6166 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6169 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6170 if(gameInfo.variant == VariantGreat) { // promotion commoners
6171 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6172 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6173 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6174 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6176 if( gameInfo.variant == VariantSChess ) {
6177 initialPosition[1][0] = BlackMarshall;
6178 initialPosition[2][0] = BlackAngel;
6179 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6180 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6181 initialPosition[1][1] = initialPosition[2][1] =
6182 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6184 if (appData.debugMode) {
6185 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6187 if(shuffleOpenings) {
6188 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6189 startedFromSetupPosition = TRUE;
6191 if(startedFromPositionFile) {
6192 /* [HGM] loadPos: use PositionFile for every new game */
6193 CopyBoard(initialPosition, filePosition);
6194 for(i=0; i<nrCastlingRights; i++)
6195 initialRights[i] = filePosition[CASTLING][i];
6196 startedFromSetupPosition = TRUE;
6199 CopyBoard(boards[0], initialPosition);
6201 if(oldx != gameInfo.boardWidth ||
6202 oldy != gameInfo.boardHeight ||
6203 oldv != gameInfo.variant ||
6204 oldh != gameInfo.holdingsWidth
6206 InitDrawingSizes(-2 ,0);
6208 oldv = gameInfo.variant;
6210 DrawPosition(TRUE, boards[currentMove]);
6214 SendBoard (ChessProgramState *cps, int moveNum)
6216 char message[MSG_SIZ];
6218 if (cps->useSetboard) {
6219 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6220 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6221 SendToProgram(message, cps);
6226 int i, j, left=0, right=BOARD_WIDTH;
6227 /* Kludge to set black to move, avoiding the troublesome and now
6228 * deprecated "black" command.
6230 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6231 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6233 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6235 SendToProgram("edit\n", cps);
6236 SendToProgram("#\n", cps);
6237 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6238 bp = &boards[moveNum][i][left];
6239 for (j = left; j < right; j++, bp++) {
6240 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6241 if ((int) *bp < (int) BlackPawn) {
6242 if(j == BOARD_RGHT+1)
6243 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6244 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6245 if(message[0] == '+' || message[0] == '~') {
6246 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6247 PieceToChar((ChessSquare)(DEMOTED *bp)),
6250 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6251 message[1] = BOARD_RGHT - 1 - j + '1';
6252 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6254 SendToProgram(message, cps);
6259 SendToProgram("c\n", cps);
6260 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6261 bp = &boards[moveNum][i][left];
6262 for (j = left; j < right; j++, bp++) {
6263 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6264 if (((int) *bp != (int) EmptySquare)
6265 && ((int) *bp >= (int) BlackPawn)) {
6266 if(j == BOARD_LEFT-2)
6267 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6268 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6270 if(message[0] == '+' || message[0] == '~') {
6271 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6272 PieceToChar((ChessSquare)(DEMOTED *bp)),
6275 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6276 message[1] = BOARD_RGHT - 1 - j + '1';
6277 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6279 SendToProgram(message, cps);
6284 SendToProgram(".\n", cps);
6286 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6289 char exclusionHeader[MSG_SIZ];
6290 int exCnt, excludePtr;
6291 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6292 static Exclusion excluTab[200];
6293 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6299 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6300 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6306 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6307 excludePtr = 24; exCnt = 0;
6312 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6313 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6314 char buf[2*MOVE_LEN], *p;
6315 Exclusion *e = excluTab;
6317 for(i=0; i<exCnt; i++)
6318 if(e[i].ff == fromX && e[i].fr == fromY &&
6319 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6320 if(i == exCnt) { // was not in exclude list; add it
6321 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6322 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6323 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6326 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6327 excludePtr++; e[i].mark = excludePtr++;
6328 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6331 exclusionHeader[e[i].mark] = state;
6335 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6336 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6340 if((signed char)promoChar == -1) { // kludge to indicate best move
6341 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6342 return 1; // if unparsable, abort
6344 // update exclusion map (resolving toggle by consulting existing state)
6345 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6347 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6348 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6349 excludeMap[k] |= 1<<j;
6350 else excludeMap[k] &= ~(1<<j);
6352 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6354 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6355 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6357 return (state == '+');
6361 ExcludeClick (int index)
6364 Exclusion *e = excluTab;
6365 if(index < 25) { // none, best or tail clicked
6366 if(index < 13) { // none: include all
6367 WriteMap(0); // clear map
6368 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6369 SendToBoth("include all\n"); // and inform engine
6370 } else if(index > 18) { // tail
6371 if(exclusionHeader[19] == '-') { // tail was excluded
6372 SendToBoth("include all\n");
6373 WriteMap(0); // clear map completely
6374 // now re-exclude selected moves
6375 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6376 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6377 } else { // tail was included or in mixed state
6378 SendToBoth("exclude all\n");
6379 WriteMap(0xFF); // fill map completely
6380 // now re-include selected moves
6381 j = 0; // count them
6382 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6383 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6384 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6387 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6390 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6391 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6392 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6399 DefaultPromoChoice (int white)
6402 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6403 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6404 result = WhiteFerz; // no choice
6405 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6406 result= WhiteKing; // in Suicide Q is the last thing we want
6407 else if(gameInfo.variant == VariantSpartan)
6408 result = white ? WhiteQueen : WhiteAngel;
6409 else result = WhiteQueen;
6410 if(!white) result = WHITE_TO_BLACK result;
6414 static int autoQueen; // [HGM] oneclick
6417 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6419 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6420 /* [HGM] add Shogi promotions */
6421 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6426 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6427 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6429 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6430 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6433 piece = boards[currentMove][fromY][fromX];
6434 if(gameInfo.variant == VariantChu) {
6435 int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6436 promotionZoneSize = BOARD_HEIGHT/3;
6437 highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6438 } else if(gameInfo.variant == VariantShogi) {
6439 promotionZoneSize = BOARD_HEIGHT/3;
6440 highestPromotingPiece = (int)WhiteAlfil;
6441 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6442 promotionZoneSize = 3;
6445 // Treat Lance as Pawn when it is not representing Amazon
6446 if(gameInfo.variant != VariantSuper) {
6447 if(piece == WhiteLance) piece = WhitePawn; else
6448 if(piece == BlackLance) piece = BlackPawn;
6451 // next weed out all moves that do not touch the promotion zone at all
6452 if((int)piece >= BlackPawn) {
6453 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6455 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6457 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6458 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6461 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6463 // weed out mandatory Shogi promotions
6464 if(gameInfo.variant == VariantShogi) {
6465 if(piece >= BlackPawn) {
6466 if(toY == 0 && piece == BlackPawn ||
6467 toY == 0 && piece == BlackQueen ||
6468 toY <= 1 && piece == BlackKnight) {
6473 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6474 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6475 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6482 // weed out obviously illegal Pawn moves
6483 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6484 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6485 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6486 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6487 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6488 // note we are not allowed to test for valid (non-)capture, due to premove
6491 // we either have a choice what to promote to, or (in Shogi) whether to promote
6492 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6493 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6494 *promoChoice = PieceToChar(BlackFerz); // no choice
6497 // no sense asking what we must promote to if it is going to explode...
6498 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6499 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6502 // give caller the default choice even if we will not make it
6503 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6504 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6505 if( sweepSelect && gameInfo.variant != VariantGreat
6506 && gameInfo.variant != VariantGrand
6507 && gameInfo.variant != VariantSuper) return FALSE;
6508 if(autoQueen) return FALSE; // predetermined
6510 // suppress promotion popup on illegal moves that are not premoves
6511 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6512 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6513 if(appData.testLegality && !premove) {
6514 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6515 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) ? '+' : NULLCHAR);
6516 if(moveType != WhitePromotion && moveType != BlackPromotion)
6524 InPalace (int row, int column)
6525 { /* [HGM] for Xiangqi */
6526 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6527 column < (BOARD_WIDTH + 4)/2 &&
6528 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6533 PieceForSquare (int x, int y)
6535 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6538 return boards[currentMove][y][x];
6542 OKToStartUserMove (int x, int y)
6544 ChessSquare from_piece;
6547 if (matchMode) return FALSE;
6548 if (gameMode == EditPosition) return TRUE;
6550 if (x >= 0 && y >= 0)
6551 from_piece = boards[currentMove][y][x];
6553 from_piece = EmptySquare;
6555 if (from_piece == EmptySquare) return FALSE;
6557 white_piece = (int)from_piece >= (int)WhitePawn &&
6558 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6562 case TwoMachinesPlay:
6570 case MachinePlaysWhite:
6571 case IcsPlayingBlack:
6572 if (appData.zippyPlay) return FALSE;
6574 DisplayMoveError(_("You are playing Black"));
6579 case MachinePlaysBlack:
6580 case IcsPlayingWhite:
6581 if (appData.zippyPlay) return FALSE;
6583 DisplayMoveError(_("You are playing White"));
6588 case PlayFromGameFile:
6589 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6591 if (!white_piece && WhiteOnMove(currentMove)) {
6592 DisplayMoveError(_("It is White's turn"));
6595 if (white_piece && !WhiteOnMove(currentMove)) {
6596 DisplayMoveError(_("It is Black's turn"));
6599 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6600 /* Editing correspondence game history */
6601 /* Could disallow this or prompt for confirmation */
6606 case BeginningOfGame:
6607 if (appData.icsActive) return FALSE;
6608 if (!appData.noChessProgram) {
6610 DisplayMoveError(_("You are playing White"));
6617 if (!white_piece && WhiteOnMove(currentMove)) {
6618 DisplayMoveError(_("It is White's turn"));
6621 if (white_piece && !WhiteOnMove(currentMove)) {
6622 DisplayMoveError(_("It is Black's turn"));
6631 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6632 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6633 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6634 && gameMode != AnalyzeFile && gameMode != Training) {
6635 DisplayMoveError(_("Displayed position is not current"));
6642 OnlyMove (int *x, int *y, Boolean captures)
6644 DisambiguateClosure cl;
6645 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6647 case MachinePlaysBlack:
6648 case IcsPlayingWhite:
6649 case BeginningOfGame:
6650 if(!WhiteOnMove(currentMove)) return FALSE;
6652 case MachinePlaysWhite:
6653 case IcsPlayingBlack:
6654 if(WhiteOnMove(currentMove)) return FALSE;
6661 cl.pieceIn = EmptySquare;
6666 cl.promoCharIn = NULLCHAR;
6667 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6668 if( cl.kind == NormalMove ||
6669 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6670 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6671 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6678 if(cl.kind != ImpossibleMove) return FALSE;
6679 cl.pieceIn = EmptySquare;
6684 cl.promoCharIn = NULLCHAR;
6685 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6686 if( cl.kind == NormalMove ||
6687 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6688 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6689 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6694 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6700 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6701 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6702 int lastLoadGameUseList = FALSE;
6703 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6704 ChessMove lastLoadGameStart = EndOfFile;
6708 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6712 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6714 /* Check if the user is playing in turn. This is complicated because we
6715 let the user "pick up" a piece before it is his turn. So the piece he
6716 tried to pick up may have been captured by the time he puts it down!
6717 Therefore we use the color the user is supposed to be playing in this
6718 test, not the color of the piece that is currently on the starting
6719 square---except in EditGame mode, where the user is playing both
6720 sides; fortunately there the capture race can't happen. (It can
6721 now happen in IcsExamining mode, but that's just too bad. The user
6722 will get a somewhat confusing message in that case.)
6727 case TwoMachinesPlay:
6731 /* We switched into a game mode where moves are not accepted,
6732 perhaps while the mouse button was down. */
6735 case MachinePlaysWhite:
6736 /* User is moving for Black */
6737 if (WhiteOnMove(currentMove)) {
6738 DisplayMoveError(_("It is White's turn"));
6743 case MachinePlaysBlack:
6744 /* User is moving for White */
6745 if (!WhiteOnMove(currentMove)) {
6746 DisplayMoveError(_("It is Black's turn"));
6751 case PlayFromGameFile:
6752 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6755 case BeginningOfGame:
6758 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6759 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6760 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6761 /* User is moving for Black */
6762 if (WhiteOnMove(currentMove)) {
6763 DisplayMoveError(_("It is White's turn"));
6767 /* User is moving for White */
6768 if (!WhiteOnMove(currentMove)) {
6769 DisplayMoveError(_("It is Black's turn"));
6775 case IcsPlayingBlack:
6776 /* User is moving for Black */
6777 if (WhiteOnMove(currentMove)) {
6778 if (!appData.premove) {
6779 DisplayMoveError(_("It is White's turn"));
6780 } else if (toX >= 0 && toY >= 0) {
6783 premoveFromX = fromX;
6784 premoveFromY = fromY;
6785 premovePromoChar = promoChar;
6787 if (appData.debugMode)
6788 fprintf(debugFP, "Got premove: fromX %d,"
6789 "fromY %d, toX %d, toY %d\n",
6790 fromX, fromY, toX, toY);
6796 case IcsPlayingWhite:
6797 /* User is moving for White */
6798 if (!WhiteOnMove(currentMove)) {
6799 if (!appData.premove) {
6800 DisplayMoveError(_("It is Black's turn"));
6801 } else if (toX >= 0 && toY >= 0) {
6804 premoveFromX = fromX;
6805 premoveFromY = fromY;
6806 premovePromoChar = promoChar;
6808 if (appData.debugMode)
6809 fprintf(debugFP, "Got premove: fromX %d,"
6810 "fromY %d, toX %d, toY %d\n",
6811 fromX, fromY, toX, toY);
6821 /* EditPosition, empty square, or different color piece;
6822 click-click move is possible */
6823 if (toX == -2 || toY == -2) {
6824 boards[0][fromY][fromX] = EmptySquare;
6825 DrawPosition(FALSE, boards[currentMove]);
6827 } else if (toX >= 0 && toY >= 0) {
6828 boards[0][toY][toX] = boards[0][fromY][fromX];
6829 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6830 if(boards[0][fromY][0] != EmptySquare) {
6831 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6832 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6835 if(fromX == BOARD_RGHT+1) {
6836 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6837 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6838 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6841 boards[0][fromY][fromX] = gatingPiece;
6842 DrawPosition(FALSE, boards[currentMove]);
6848 if(toX < 0 || toY < 0) return;
6849 pup = boards[currentMove][toY][toX];
6851 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6852 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6853 if( pup != EmptySquare ) return;
6854 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6855 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6856 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6857 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6858 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6859 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6860 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6864 /* [HGM] always test for legality, to get promotion info */
6865 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6866 fromY, fromX, toY, toX, promoChar);
6868 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6870 /* [HGM] but possibly ignore an IllegalMove result */
6871 if (appData.testLegality) {
6872 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6873 DisplayMoveError(_("Illegal move"));
6878 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6879 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6880 ClearPremoveHighlights(); // was included
6881 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6885 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6888 /* Common tail of UserMoveEvent and DropMenuEvent */
6890 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6894 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6895 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6896 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6897 if(WhiteOnMove(currentMove)) {
6898 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6900 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6904 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6905 move type in caller when we know the move is a legal promotion */
6906 if(moveType == NormalMove && promoChar)
6907 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6909 /* [HGM] <popupFix> The following if has been moved here from
6910 UserMoveEvent(). Because it seemed to belong here (why not allow
6911 piece drops in training games?), and because it can only be
6912 performed after it is known to what we promote. */
6913 if (gameMode == Training) {
6914 /* compare the move played on the board to the next move in the
6915 * game. If they match, display the move and the opponent's response.
6916 * If they don't match, display an error message.
6920 CopyBoard(testBoard, boards[currentMove]);
6921 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6923 if (CompareBoards(testBoard, boards[currentMove+1])) {
6924 ForwardInner(currentMove+1);
6926 /* Autoplay the opponent's response.
6927 * if appData.animate was TRUE when Training mode was entered,
6928 * the response will be animated.
6930 saveAnimate = appData.animate;
6931 appData.animate = animateTraining;
6932 ForwardInner(currentMove+1);
6933 appData.animate = saveAnimate;
6935 /* check for the end of the game */
6936 if (currentMove >= forwardMostMove) {
6937 gameMode = PlayFromGameFile;
6939 SetTrainingModeOff();
6940 DisplayInformation(_("End of game"));
6943 DisplayError(_("Incorrect move"), 0);
6948 /* Ok, now we know that the move is good, so we can kill
6949 the previous line in Analysis Mode */
6950 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6951 && currentMove < forwardMostMove) {
6952 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6953 else forwardMostMove = currentMove;
6958 /* If we need the chess program but it's dead, restart it */
6959 ResurrectChessProgram();
6961 /* A user move restarts a paused game*/
6965 thinkOutput[0] = NULLCHAR;
6967 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6969 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6970 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6974 if (gameMode == BeginningOfGame) {
6975 if (appData.noChessProgram) {
6976 gameMode = EditGame;
6980 gameMode = MachinePlaysBlack;
6983 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6985 if (first.sendName) {
6986 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6987 SendToProgram(buf, &first);
6994 /* Relay move to ICS or chess engine */
6995 if (appData.icsActive) {
6996 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6997 gameMode == IcsExamining) {
6998 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6999 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7001 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7003 // also send plain move, in case ICS does not understand atomic claims
7004 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7008 if (first.sendTime && (gameMode == BeginningOfGame ||
7009 gameMode == MachinePlaysWhite ||
7010 gameMode == MachinePlaysBlack)) {
7011 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7013 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7014 // [HGM] book: if program might be playing, let it use book
7015 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7016 first.maybeThinking = TRUE;
7017 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7018 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7019 SendBoard(&first, currentMove+1);
7020 if(second.analyzing) {
7021 if(!second.useSetboard) SendToProgram("undo\n", &second);
7022 SendBoard(&second, currentMove+1);
7025 SendMoveToProgram(forwardMostMove-1, &first);
7026 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7028 if (currentMove == cmailOldMove + 1) {
7029 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7033 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7037 if(appData.testLegality)
7038 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7044 if (WhiteOnMove(currentMove)) {
7045 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7047 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7051 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7056 case MachinePlaysBlack:
7057 case MachinePlaysWhite:
7058 /* disable certain menu options while machine is thinking */
7059 SetMachineThinkingEnables();
7066 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7067 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7069 if(bookHit) { // [HGM] book: simulate book reply
7070 static char bookMove[MSG_SIZ]; // a bit generous?
7072 programStats.nodes = programStats.depth = programStats.time =
7073 programStats.score = programStats.got_only_move = 0;
7074 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7076 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7077 strcat(bookMove, bookHit);
7078 HandleMachineMove(bookMove, &first);
7084 MarkByFEN(char *fen)
7087 if(!appData.markers || !appData.highlightDragging) return;
7088 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7089 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7093 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7094 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7095 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7096 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7097 if(*fen == 'T') marker[r][f++] = 0; else
7098 if(*fen == 'Y') marker[r][f++] = 1; else
7099 if(*fen == 'G') marker[r][f++] = 3; else
7100 if(*fen == 'B') marker[r][f++] = 4; else
7101 if(*fen == 'C') marker[r][f++] = 5; else
7102 if(*fen == 'M') marker[r][f++] = 6; else
7103 if(*fen == 'W') marker[r][f++] = 7; else
7104 if(*fen == 'D') marker[r][f++] = 8; else
7105 if(*fen == 'R') marker[r][f++] = 2; else {
7106 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7109 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7113 DrawPosition(TRUE, NULL);
7117 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7119 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7120 Markers *m = (Markers *) closure;
7121 if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7122 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7123 || kind == WhiteCapturesEnPassant
7124 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7125 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7129 MarkTargetSquares (int clear)
7132 if(clear) { // no reason to ever suppress clearing
7133 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7134 if(!sum) return; // nothing was cleared,no redraw needed
7137 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7138 !appData.testLegality || gameMode == EditPosition) return;
7139 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7140 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7141 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7143 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7146 DrawPosition(FALSE, NULL);
7150 Explode (Board board, int fromX, int fromY, int toX, int toY)
7152 if(gameInfo.variant == VariantAtomic &&
7153 (board[toY][toX] != EmptySquare || // capture?
7154 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7155 board[fromY][fromX] == BlackPawn )
7157 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7163 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7166 CanPromote (ChessSquare piece, int y)
7168 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7169 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7170 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7171 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7172 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7173 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7174 return (piece == BlackPawn && y == 1 ||
7175 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7176 piece == BlackLance && y == 1 ||
7177 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7181 HoverEvent (int xPix, int yPix, int x, int y)
7183 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7184 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7186 if(dragging == 2) DragPieceMove(xPix, yPix); // [HGM] lion: drag without button for second leg
7187 if(!first.highlight) return;
7188 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7189 if(x == oldX && y == oldY) return; // only do something if we enter new square
7190 oldFromX = fromX; oldFromY = fromY;
7191 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7192 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7193 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7194 else if(oldX != x || oldY != y) {
7195 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7196 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7197 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7198 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7200 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7201 SendToProgram(buf, &first);
7204 // SetHighlights(fromX, fromY, x, y);
7208 void ReportClick(char *action, int x, int y)
7210 char buf[MSG_SIZ]; // Inform engine of what user does
7212 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7213 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7214 if(!first.highlight || gameMode == EditPosition) return;
7215 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7216 SendToProgram(buf, &first);
7220 LeftClick (ClickType clickType, int xPix, int yPix)
7223 Boolean saveAnimate;
7224 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7225 char promoChoice = NULLCHAR;
7227 static TimeMark lastClickTime, prevClickTime;
7229 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7231 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7233 if (clickType == Press) ErrorPopDown();
7234 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7236 x = EventToSquare(xPix, BOARD_WIDTH);
7237 y = EventToSquare(yPix, BOARD_HEIGHT);
7238 if (!flipView && y >= 0) {
7239 y = BOARD_HEIGHT - 1 - y;
7241 if (flipView && x >= 0) {
7242 x = BOARD_WIDTH - 1 - x;
7245 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7246 defaultPromoChoice = promoSweep;
7247 promoSweep = EmptySquare; // terminate sweep
7248 promoDefaultAltered = TRUE;
7249 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7252 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7253 if(clickType == Release) return; // ignore upclick of click-click destination
7254 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7255 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7256 if(gameInfo.holdingsWidth &&
7257 (WhiteOnMove(currentMove)
7258 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7259 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7260 // click in right holdings, for determining promotion piece
7261 ChessSquare p = boards[currentMove][y][x];
7262 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7263 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7264 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7265 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7270 DrawPosition(FALSE, boards[currentMove]);
7274 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7275 if(clickType == Press
7276 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7277 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7278 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7281 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7282 // could be static click on premove from-square: abort premove
7284 ClearPremoveHighlights();
7287 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7288 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7290 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7291 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7292 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7293 defaultPromoChoice = DefaultPromoChoice(side);
7296 autoQueen = appData.alwaysPromoteToQueen;
7300 gatingPiece = EmptySquare;
7301 if (clickType != Press) {
7302 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7303 DragPieceEnd(xPix, yPix); dragging = 0;
7304 DrawPosition(FALSE, NULL);
7308 doubleClick = FALSE;
7309 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7310 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7312 fromX = x; fromY = y; toX = toY = killX = killY = -1;
7313 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7314 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7315 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7317 if (OKToStartUserMove(fromX, fromY)) {
7319 ReportClick("lift", x, y);
7320 MarkTargetSquares(0);
7321 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7322 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7323 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7324 promoSweep = defaultPromoChoice;
7325 selectFlag = 0; lastX = xPix; lastY = yPix;
7326 Sweep(0); // Pawn that is going to promote: preview promotion piece
7327 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7329 if (appData.highlightDragging) {
7330 SetHighlights(fromX, fromY, -1, -1);
7334 } else fromX = fromY = -1;
7340 if (clickType == Press && gameMode != EditPosition) {
7345 // ignore off-board to clicks
7346 if(y < 0 || x < 0) return;
7348 /* Check if clicking again on the same color piece */
7349 fromP = boards[currentMove][fromY][fromX];
7350 toP = boards[currentMove][y][x];
7351 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7352 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7353 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7354 WhitePawn <= toP && toP <= WhiteKing &&
7355 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7356 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7357 (BlackPawn <= fromP && fromP <= BlackKing &&
7358 BlackPawn <= toP && toP <= BlackKing &&
7359 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7360 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7361 /* Clicked again on same color piece -- changed his mind */
7362 second = (x == fromX && y == fromY);
7364 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7365 second = FALSE; // first double-click rather than scond click
7366 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7368 promoDefaultAltered = FALSE;
7369 MarkTargetSquares(1);
7370 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7371 if (appData.highlightDragging) {
7372 SetHighlights(x, y, -1, -1);
7376 if (OKToStartUserMove(x, y)) {
7377 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7378 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7379 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7380 gatingPiece = boards[currentMove][fromY][fromX];
7381 else gatingPiece = doubleClick ? fromP : EmptySquare;
7383 fromY = y; dragging = 1;
7384 ReportClick("lift", x, y);
7385 MarkTargetSquares(0);
7386 DragPieceBegin(xPix, yPix, FALSE);
7387 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7388 promoSweep = defaultPromoChoice;
7389 selectFlag = 0; lastX = xPix; lastY = yPix;
7390 Sweep(0); // Pawn that is going to promote: preview promotion piece
7394 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7397 // ignore clicks on holdings
7398 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7401 if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7402 DragPieceEnd(xPix, yPix); dragging = 0;
7404 // a deferred attempt to click-click move an empty square on top of a piece
7405 boards[currentMove][y][x] = EmptySquare;
7407 DrawPosition(FALSE, boards[currentMove]);
7408 fromX = fromY = -1; clearFlag = 0;
7411 if (appData.animateDragging) {
7412 /* Undo animation damage if any */
7413 DrawPosition(FALSE, NULL);
7415 if (second || sweepSelecting) {
7416 /* Second up/down in same square; just abort move */
7417 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7418 second = sweepSelecting = 0;
7420 gatingPiece = EmptySquare;
7421 MarkTargetSquares(1);
7424 ClearPremoveHighlights();
7426 /* First upclick in same square; start click-click mode */
7427 SetHighlights(x, y, -1, -1);
7434 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7435 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7436 DisplayMessage(_("only marked squares are legal"),"");
7437 DrawPosition(TRUE, NULL);
7438 return; // ignore to-click
7441 /* we now have a different from- and (possibly off-board) to-square */
7442 /* Completed move */
7443 if(!sweepSelecting) {
7448 saveAnimate = appData.animate;
7449 if (clickType == Press) {
7450 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7451 // must be Edit Position mode with empty-square selected
7452 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7453 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7456 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7460 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7461 killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7463 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7464 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7465 if(appData.sweepSelect) {
7466 ChessSquare piece = boards[currentMove][fromY][fromX];
7467 promoSweep = defaultPromoChoice;
7468 if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7469 selectFlag = 0; lastX = xPix; lastY = yPix;
7470 Sweep(0); // Pawn that is going to promote: preview promotion piece
7472 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7473 MarkTargetSquares(1);
7475 return; // promo popup appears on up-click
7477 /* Finish clickclick move */
7478 if (appData.animate || appData.highlightLastMove) {
7479 SetHighlights(fromX, fromY, toX, toY);
7483 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7485 if (appData.animate || appData.highlightLastMove) {
7486 SetHighlights(fromX, fromY, toX, toY);
7492 // [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
7493 /* Finish drag move */
7494 if (appData.highlightLastMove) {
7495 SetHighlights(fromX, fromY, toX, toY);
7500 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7501 dragging *= 2; // flag button-less dragging if we are dragging
7502 MarkTargetSquares(1);
7503 if(x == killX && y == killY) killX = killY = -1; else {
7504 killX = x; killY = y; //remeber this square as intermediate
7505 MarkTargetSquares(0);
7506 ReportClick("put", x, y); // and inform engine
7507 ReportClick("lift", x, y);
7511 DragPieceEnd(xPix, yPix); dragging = 0;
7512 /* Don't animate move and drag both */
7513 appData.animate = FALSE;
7516 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7517 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7518 ChessSquare piece = boards[currentMove][fromY][fromX];
7519 if(gameMode == EditPosition && piece != EmptySquare &&
7520 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7523 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7524 n = PieceToNumber(piece - (int)BlackPawn);
7525 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7526 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7527 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7529 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7530 n = PieceToNumber(piece);
7531 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7532 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7533 boards[currentMove][n][BOARD_WIDTH-2]++;
7535 boards[currentMove][fromY][fromX] = EmptySquare;
7539 MarkTargetSquares(1);
7540 DrawPosition(TRUE, boards[currentMove]);
7544 // off-board moves should not be highlighted
7545 if(x < 0 || y < 0) ClearHighlights();
7546 else ReportClick("put", x, y);
7548 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7550 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7551 SetHighlights(fromX, fromY, toX, toY);
7552 MarkTargetSquares(1);
7553 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7554 // [HGM] super: promotion to captured piece selected from holdings
7555 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7556 promotionChoice = TRUE;
7557 // kludge follows to temporarily execute move on display, without promoting yet
7558 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7559 boards[currentMove][toY][toX] = p;
7560 DrawPosition(FALSE, boards[currentMove]);
7561 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7562 boards[currentMove][toY][toX] = q;
7563 DisplayMessage("Click in holdings to choose piece", "");
7568 int oldMove = currentMove;
7569 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7570 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7571 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7572 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7573 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7574 DrawPosition(TRUE, boards[currentMove]);
7575 MarkTargetSquares(1);
7578 appData.animate = saveAnimate;
7579 if (appData.animate || appData.animateDragging) {
7580 /* Undo animation damage if needed */
7581 DrawPosition(FALSE, NULL);
7586 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7587 { // front-end-free part taken out of PieceMenuPopup
7588 int whichMenu; int xSqr, ySqr;
7590 if(seekGraphUp) { // [HGM] seekgraph
7591 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7592 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7596 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7597 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7598 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7599 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7600 if(action == Press) {
7601 originalFlip = flipView;
7602 flipView = !flipView; // temporarily flip board to see game from partners perspective
7603 DrawPosition(TRUE, partnerBoard);
7604 DisplayMessage(partnerStatus, "");
7606 } else if(action == Release) {
7607 flipView = originalFlip;
7608 DrawPosition(TRUE, boards[currentMove]);
7614 xSqr = EventToSquare(x, BOARD_WIDTH);
7615 ySqr = EventToSquare(y, BOARD_HEIGHT);
7616 if (action == Release) {
7617 if(pieceSweep != EmptySquare) {
7618 EditPositionMenuEvent(pieceSweep, toX, toY);
7619 pieceSweep = EmptySquare;
7620 } else UnLoadPV(); // [HGM] pv
7622 if (action != Press) return -2; // return code to be ignored
7625 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7627 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7628 if (xSqr < 0 || ySqr < 0) return -1;
7629 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7630 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7631 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7632 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7636 if(!appData.icsEngineAnalyze) return -1;
7637 case IcsPlayingWhite:
7638 case IcsPlayingBlack:
7639 if(!appData.zippyPlay) goto noZip;
7642 case MachinePlaysWhite:
7643 case MachinePlaysBlack:
7644 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7645 if (!appData.dropMenu) {
7647 return 2; // flag front-end to grab mouse events
7649 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7650 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7653 if (xSqr < 0 || ySqr < 0) return -1;
7654 if (!appData.dropMenu || appData.testLegality &&
7655 gameInfo.variant != VariantBughouse &&
7656 gameInfo.variant != VariantCrazyhouse) return -1;
7657 whichMenu = 1; // drop menu
7663 if (((*fromX = xSqr) < 0) ||
7664 ((*fromY = ySqr) < 0)) {
7665 *fromX = *fromY = -1;
7669 *fromX = BOARD_WIDTH - 1 - *fromX;
7671 *fromY = BOARD_HEIGHT - 1 - *fromY;
7677 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7679 // char * hint = lastHint;
7680 FrontEndProgramStats stats;
7682 stats.which = cps == &first ? 0 : 1;
7683 stats.depth = cpstats->depth;
7684 stats.nodes = cpstats->nodes;
7685 stats.score = cpstats->score;
7686 stats.time = cpstats->time;
7687 stats.pv = cpstats->movelist;
7688 stats.hint = lastHint;
7689 stats.an_move_index = 0;
7690 stats.an_move_count = 0;
7692 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7693 stats.hint = cpstats->move_name;
7694 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7695 stats.an_move_count = cpstats->nr_moves;
7698 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
7700 SetProgramStats( &stats );
7704 ClearEngineOutputPane (int which)
7706 static FrontEndProgramStats dummyStats;
7707 dummyStats.which = which;
7708 dummyStats.pv = "#";
7709 SetProgramStats( &dummyStats );
7712 #define MAXPLAYERS 500
7715 TourneyStandings (int display)
7717 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7718 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7719 char result, *p, *names[MAXPLAYERS];
7721 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7722 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7723 names[0] = p = strdup(appData.participants);
7724 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7726 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7728 while(result = appData.results[nr]) {
7729 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7730 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7731 wScore = bScore = 0;
7733 case '+': wScore = 2; break;
7734 case '-': bScore = 2; break;
7735 case '=': wScore = bScore = 1; break;
7737 case '*': return strdup("busy"); // tourney not finished
7745 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7746 for(w=0; w<nPlayers; w++) {
7748 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7749 ranking[w] = b; points[w] = bScore; score[b] = -2;
7751 p = malloc(nPlayers*34+1);
7752 for(w=0; w<nPlayers && w<display; w++)
7753 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7759 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7760 { // count all piece types
7762 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7763 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7764 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7767 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7768 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7769 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7770 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7771 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7772 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7777 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7779 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7780 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7782 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7783 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7784 if(myPawns == 2 && nMine == 3) // KPP
7785 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7786 if(myPawns == 1 && nMine == 2) // KP
7787 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7788 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7789 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7790 if(myPawns) return FALSE;
7791 if(pCnt[WhiteRook+side])
7792 return pCnt[BlackRook-side] ||
7793 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7794 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7795 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7796 if(pCnt[WhiteCannon+side]) {
7797 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7798 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7800 if(pCnt[WhiteKnight+side])
7801 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7806 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7808 VariantClass v = gameInfo.variant;
7810 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7811 if(v == VariantShatranj) return TRUE; // always winnable through baring
7812 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7813 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7815 if(v == VariantXiangqi) {
7816 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7818 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7819 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7820 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7821 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7822 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7823 if(stale) // we have at least one last-rank P plus perhaps C
7824 return majors // KPKX
7825 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7827 return pCnt[WhiteFerz+side] // KCAK
7828 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7829 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7830 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7832 } else if(v == VariantKnightmate) {
7833 if(nMine == 1) return FALSE;
7834 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7835 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7836 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7838 if(nMine == 1) return FALSE; // bare King
7839 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
7840 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7841 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7842 // by now we have King + 1 piece (or multiple Bishops on the same color)
7843 if(pCnt[WhiteKnight+side])
7844 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7845 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7846 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7848 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7849 if(pCnt[WhiteAlfil+side])
7850 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7851 if(pCnt[WhiteWazir+side])
7852 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7859 CompareWithRights (Board b1, Board b2)
7862 if(!CompareBoards(b1, b2)) return FALSE;
7863 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7864 /* compare castling rights */
7865 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7866 rights++; /* King lost rights, while rook still had them */
7867 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7868 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7869 rights++; /* but at least one rook lost them */
7871 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7873 if( b1[CASTLING][5] != NoRights ) {
7874 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7881 Adjudicate (ChessProgramState *cps)
7882 { // [HGM] some adjudications useful with buggy engines
7883 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7884 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7885 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7886 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7887 int k, drop, count = 0; static int bare = 1;
7888 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7889 Boolean canAdjudicate = !appData.icsActive;
7891 // most tests only when we understand the game, i.e. legality-checking on
7892 if( appData.testLegality )
7893 { /* [HGM] Some more adjudications for obstinate engines */
7894 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7895 static int moveCount = 6;
7897 char *reason = NULL;
7899 /* Count what is on board. */
7900 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7902 /* Some material-based adjudications that have to be made before stalemate test */
7903 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7904 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7905 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7906 if(canAdjudicate && appData.checkMates) {
7908 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7909 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7910 "Xboard adjudication: King destroyed", GE_XBOARD );
7915 /* Bare King in Shatranj (loses) or Losers (wins) */
7916 if( nrW == 1 || nrB == 1) {
7917 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7918 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7919 if(canAdjudicate && appData.checkMates) {
7921 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7922 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7923 "Xboard adjudication: Bare king", GE_XBOARD );
7927 if( gameInfo.variant == VariantShatranj && --bare < 0)
7929 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7930 if(canAdjudicate && appData.checkMates) {
7931 /* but only adjudicate if adjudication enabled */
7933 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7934 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7935 "Xboard adjudication: Bare king", GE_XBOARD );
7942 // don't wait for engine to announce game end if we can judge ourselves
7943 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7945 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7946 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7947 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7948 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7951 reason = "Xboard adjudication: 3rd check";
7952 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7962 reason = "Xboard adjudication: Stalemate";
7963 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7964 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7965 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7966 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7967 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7968 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7969 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7970 EP_CHECKMATE : EP_WINS);
7971 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7972 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7976 reason = "Xboard adjudication: Checkmate";
7977 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7978 if(gameInfo.variant == VariantShogi) {
7979 if(forwardMostMove > backwardMostMove
7980 && moveList[forwardMostMove-1][1] == '@'
7981 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7982 reason = "XBoard adjudication: pawn-drop mate";
7983 boards[forwardMostMove][EP_STATUS] = EP_WINS;
7989 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7991 result = GameIsDrawn; break;
7993 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7995 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7999 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8001 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8002 GameEnds( result, reason, GE_XBOARD );
8006 /* Next absolutely insufficient mating material. */
8007 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8008 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8009 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8011 /* always flag draws, for judging claims */
8012 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8014 if(canAdjudicate && appData.materialDraws) {
8015 /* but only adjudicate them if adjudication enabled */
8016 if(engineOpponent) {
8017 SendToProgram("force\n", engineOpponent); // suppress reply
8018 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8020 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8025 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8026 if(gameInfo.variant == VariantXiangqi ?
8027 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8029 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8030 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8031 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8032 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8034 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8035 { /* if the first 3 moves do not show a tactical win, declare draw */
8036 if(engineOpponent) {
8037 SendToProgram("force\n", engineOpponent); // suppress reply
8038 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8040 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8043 } else moveCount = 6;
8046 // Repetition draws and 50-move rule can be applied independently of legality testing
8048 /* Check for rep-draws */
8050 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8051 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8052 for(k = forwardMostMove-2;
8053 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8054 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8055 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8058 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8059 /* compare castling rights */
8060 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8061 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8062 rights++; /* King lost rights, while rook still had them */
8063 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8064 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8065 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8066 rights++; /* but at least one rook lost them */
8068 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8069 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8071 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8072 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8073 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8076 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8077 && appData.drawRepeats > 1) {
8078 /* adjudicate after user-specified nr of repeats */
8079 int result = GameIsDrawn;
8080 char *details = "XBoard adjudication: repetition draw";
8081 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8082 // [HGM] xiangqi: check for forbidden perpetuals
8083 int m, ourPerpetual = 1, hisPerpetual = 1;
8084 for(m=forwardMostMove; m>k; m-=2) {
8085 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8086 ourPerpetual = 0; // the current mover did not always check
8087 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8088 hisPerpetual = 0; // the opponent did not always check
8090 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8091 ourPerpetual, hisPerpetual);
8092 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8093 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8094 details = "Xboard adjudication: perpetual checking";
8096 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8097 break; // (or we would have caught him before). Abort repetition-checking loop.
8099 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8100 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8102 details = "Xboard adjudication: repetition";
8104 } else // it must be XQ
8105 // Now check for perpetual chases
8106 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8107 hisPerpetual = PerpetualChase(k, forwardMostMove);
8108 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8109 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8110 static char resdet[MSG_SIZ];
8111 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8113 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8115 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8116 break; // Abort repetition-checking loop.
8118 // if neither of us is checking or chasing all the time, or both are, it is draw
8120 if(engineOpponent) {
8121 SendToProgram("force\n", engineOpponent); // suppress reply
8122 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8124 GameEnds( result, details, GE_XBOARD );
8127 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8128 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8132 /* Now we test for 50-move draws. Determine ply count */
8133 count = forwardMostMove;
8134 /* look for last irreversble move */
8135 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8137 /* if we hit starting position, add initial plies */
8138 if( count == backwardMostMove )
8139 count -= initialRulePlies;
8140 count = forwardMostMove - count;
8141 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8142 // adjust reversible move counter for checks in Xiangqi
8143 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8144 if(i < backwardMostMove) i = backwardMostMove;
8145 while(i <= forwardMostMove) {
8146 lastCheck = inCheck; // check evasion does not count
8147 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8148 if(inCheck || lastCheck) count--; // check does not count
8153 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8154 /* this is used to judge if draw claims are legal */
8155 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8156 if(engineOpponent) {
8157 SendToProgram("force\n", engineOpponent); // suppress reply
8158 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8160 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8164 /* if draw offer is pending, treat it as a draw claim
8165 * when draw condition present, to allow engines a way to
8166 * claim draws before making their move to avoid a race
8167 * condition occurring after their move
8169 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8171 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8172 p = "Draw claim: 50-move rule";
8173 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8174 p = "Draw claim: 3-fold repetition";
8175 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8176 p = "Draw claim: insufficient mating material";
8177 if( p != NULL && canAdjudicate) {
8178 if(engineOpponent) {
8179 SendToProgram("force\n", engineOpponent); // suppress reply
8180 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8182 GameEnds( GameIsDrawn, p, GE_XBOARD );
8187 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8188 if(engineOpponent) {
8189 SendToProgram("force\n", engineOpponent); // suppress reply
8190 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8192 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8199 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8200 { // [HGM] book: this routine intercepts moves to simulate book replies
8201 char *bookHit = NULL;
8203 //first determine if the incoming move brings opponent into his book
8204 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8205 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8206 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8207 if(bookHit != NULL && !cps->bookSuspend) {
8208 // make sure opponent is not going to reply after receiving move to book position
8209 SendToProgram("force\n", cps);
8210 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8212 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8213 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8214 // now arrange restart after book miss
8216 // after a book hit we never send 'go', and the code after the call to this routine
8217 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8218 char buf[MSG_SIZ], *move = bookHit;
8220 int fromX, fromY, toX, toY;
8224 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8225 &fromX, &fromY, &toX, &toY, &promoChar)) {
8226 (void) CoordsToAlgebraic(boards[forwardMostMove],
8227 PosFlags(forwardMostMove),
8228 fromY, fromX, toY, toX, promoChar, move);
8230 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8234 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8235 SendToProgram(buf, cps);
8236 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8237 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8238 SendToProgram("go\n", cps);
8239 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8240 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8241 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8242 SendToProgram("go\n", cps);
8243 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8245 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8249 LoadError (char *errmess, ChessProgramState *cps)
8250 { // unloads engine and switches back to -ncp mode if it was first
8251 if(cps->initDone) return FALSE;
8252 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8253 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8256 appData.noChessProgram = TRUE;
8257 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8258 gameMode = BeginningOfGame; ModeHighlight();
8261 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8262 DisplayMessage("", ""); // erase waiting message
8263 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8268 ChessProgramState *savedState;
8270 DeferredBookMove (void)
8272 if(savedState->lastPing != savedState->lastPong)
8273 ScheduleDelayedEvent(DeferredBookMove, 10);
8275 HandleMachineMove(savedMessage, savedState);
8278 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8279 static ChessProgramState *stalledEngine;
8280 static char stashedInputMove[MSG_SIZ];
8283 HandleMachineMove (char *message, ChessProgramState *cps)
8285 static char firstLeg[20];
8286 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8287 char realname[MSG_SIZ];
8288 int fromX, fromY, toX, toY;
8292 int machineWhite, oldError;
8295 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8296 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8297 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8298 DisplayError(_("Invalid pairing from pairing engine"), 0);
8301 pairingReceived = 1;
8303 return; // Skim the pairing messages here.
8306 oldError = cps->userError; cps->userError = 0;
8308 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8310 * Kludge to ignore BEL characters
8312 while (*message == '\007') message++;
8315 * [HGM] engine debug message: ignore lines starting with '#' character
8317 if(cps->debug && *message == '#') return;
8320 * Look for book output
8322 if (cps == &first && bookRequested) {
8323 if (message[0] == '\t' || message[0] == ' ') {
8324 /* Part of the book output is here; append it */
8325 strcat(bookOutput, message);
8326 strcat(bookOutput, " \n");
8328 } else if (bookOutput[0] != NULLCHAR) {
8329 /* All of book output has arrived; display it */
8330 char *p = bookOutput;
8331 while (*p != NULLCHAR) {
8332 if (*p == '\t') *p = ' ';
8335 DisplayInformation(bookOutput);
8336 bookRequested = FALSE;
8337 /* Fall through to parse the current output */
8342 * Look for machine move.
8344 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8345 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8347 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8348 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8349 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8350 stalledEngine = cps;
8351 if(appData.ponderNextMove) { // bring opponent out of ponder
8352 if(gameMode == TwoMachinesPlay) {
8353 if(cps->other->pause)
8354 PauseEngine(cps->other);
8356 SendToProgram("easy\n", cps->other);
8363 /* This method is only useful on engines that support ping */
8364 if (cps->lastPing != cps->lastPong) {
8365 if (gameMode == BeginningOfGame) {
8366 /* Extra move from before last new; ignore */
8367 if (appData.debugMode) {
8368 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8371 if (appData.debugMode) {
8372 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8373 cps->which, gameMode);
8376 SendToProgram("undo\n", cps);
8382 case BeginningOfGame:
8383 /* Extra move from before last reset; ignore */
8384 if (appData.debugMode) {
8385 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8392 /* Extra move after we tried to stop. The mode test is
8393 not a reliable way of detecting this problem, but it's
8394 the best we can do on engines that don't support ping.
8396 if (appData.debugMode) {
8397 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8398 cps->which, gameMode);
8400 SendToProgram("undo\n", cps);
8403 case MachinePlaysWhite:
8404 case IcsPlayingWhite:
8405 machineWhite = TRUE;
8408 case MachinePlaysBlack:
8409 case IcsPlayingBlack:
8410 machineWhite = FALSE;
8413 case TwoMachinesPlay:
8414 machineWhite = (cps->twoMachinesColor[0] == 'w');
8417 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8418 if (appData.debugMode) {
8420 "Ignoring move out of turn by %s, gameMode %d"
8421 ", forwardMost %d\n",
8422 cps->which, gameMode, forwardMostMove);
8427 if(cps->alphaRank) AlphaRank(machineMove, 4);
8429 // [HGM] lion: (some very limited) support for Alien protocol
8431 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8432 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8434 } else if(firstLeg[0]) { // there was a previous leg;
8435 // only support case where same piece makes two step (and don't even test that!)
8436 char buf[20], *p = machineMove+1, *q = buf+1, f;
8437 safeStrCpy(buf, machineMove, 20);
8438 while(isdigit(*q)) q++; // find start of to-square
8439 safeStrCpy(machineMove, firstLeg, 20);
8440 while(isdigit(*p)) p++;
8441 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8442 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8443 firstLeg[0] = NULLCHAR;
8446 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8447 &fromX, &fromY, &toX, &toY, &promoChar)) {
8448 /* Machine move could not be parsed; ignore it. */
8449 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8450 machineMove, _(cps->which));
8451 DisplayMoveError(buf1);
8452 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8453 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8454 if (gameMode == TwoMachinesPlay) {
8455 GameEnds(machineWhite ? BlackWins : WhiteWins,
8461 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8462 /* So we have to redo legality test with true e.p. status here, */
8463 /* to make sure an illegal e.p. capture does not slip through, */
8464 /* to cause a forfeit on a justified illegal-move complaint */
8465 /* of the opponent. */
8466 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8468 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8469 fromY, fromX, toY, toX, promoChar);
8470 if(moveType == IllegalMove) {
8471 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8472 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8473 GameEnds(machineWhite ? BlackWins : WhiteWins,
8476 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8477 /* [HGM] Kludge to handle engines that send FRC-style castling
8478 when they shouldn't (like TSCP-Gothic) */
8480 case WhiteASideCastleFR:
8481 case BlackASideCastleFR:
8483 currentMoveString[2]++;
8485 case WhiteHSideCastleFR:
8486 case BlackHSideCastleFR:
8488 currentMoveString[2]--;
8490 default: ; // nothing to do, but suppresses warning of pedantic compilers
8493 hintRequested = FALSE;
8494 lastHint[0] = NULLCHAR;
8495 bookRequested = FALSE;
8496 /* Program may be pondering now */
8497 cps->maybeThinking = TRUE;
8498 if (cps->sendTime == 2) cps->sendTime = 1;
8499 if (cps->offeredDraw) cps->offeredDraw--;
8501 /* [AS] Save move info*/
8502 pvInfoList[ forwardMostMove ].score = programStats.score;
8503 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8504 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8506 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8508 /* Test suites abort the 'game' after one move */
8509 if(*appData.finger) {
8511 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8512 if(!f) f = fopen(appData.finger, "w");
8513 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8514 else { DisplayFatalError("Bad output file", errno, 0); return; }
8516 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8519 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8520 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8523 while( count < adjudicateLossPlies ) {
8524 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8527 score = -score; /* Flip score for winning side */
8530 if( score > adjudicateLossThreshold ) {
8537 if( count >= adjudicateLossPlies ) {
8538 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8540 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8541 "Xboard adjudication",
8548 if(Adjudicate(cps)) {
8549 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8550 return; // [HGM] adjudicate: for all automatic game ends
8554 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8556 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8557 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8559 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8561 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8563 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8564 char buf[3*MSG_SIZ];
8566 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8567 programStats.score / 100.,
8569 programStats.time / 100.,
8570 (unsigned int)programStats.nodes,
8571 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8572 programStats.movelist);
8574 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8579 /* [AS] Clear stats for next move */
8580 ClearProgramStats();
8581 thinkOutput[0] = NULLCHAR;
8582 hiddenThinkOutputState = 0;
8585 if (gameMode == TwoMachinesPlay) {
8586 /* [HGM] relaying draw offers moved to after reception of move */
8587 /* and interpreting offer as claim if it brings draw condition */
8588 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8589 SendToProgram("draw\n", cps->other);
8591 if (cps->other->sendTime) {
8592 SendTimeRemaining(cps->other,
8593 cps->other->twoMachinesColor[0] == 'w');
8595 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8596 if (firstMove && !bookHit) {
8598 if (cps->other->useColors) {
8599 SendToProgram(cps->other->twoMachinesColor, cps->other);
8601 SendToProgram("go\n", cps->other);
8603 cps->other->maybeThinking = TRUE;
8606 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8608 if (!pausing && appData.ringBellAfterMoves) {
8613 * Reenable menu items that were disabled while
8614 * machine was thinking
8616 if (gameMode != TwoMachinesPlay)
8617 SetUserThinkingEnables();
8619 // [HGM] book: after book hit opponent has received move and is now in force mode
8620 // force the book reply into it, and then fake that it outputted this move by jumping
8621 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8623 static char bookMove[MSG_SIZ]; // a bit generous?
8625 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8626 strcat(bookMove, bookHit);
8629 programStats.nodes = programStats.depth = programStats.time =
8630 programStats.score = programStats.got_only_move = 0;
8631 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8633 if(cps->lastPing != cps->lastPong) {
8634 savedMessage = message; // args for deferred call
8636 ScheduleDelayedEvent(DeferredBookMove, 10);
8645 /* Set special modes for chess engines. Later something general
8646 * could be added here; for now there is just one kludge feature,
8647 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8648 * when "xboard" is given as an interactive command.
8650 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8651 cps->useSigint = FALSE;
8652 cps->useSigterm = FALSE;
8654 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8655 ParseFeatures(message+8, cps);
8656 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8659 if (!strncmp(message, "setup ", 6) &&
8660 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8661 ) { // [HGM] allow first engine to define opening position
8662 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8663 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8665 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8666 if(startedFromSetupPosition) return;
8667 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8669 while(message[s] && message[s++] != ' ');
8670 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8671 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8672 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8673 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8674 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8675 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8678 ParseFEN(boards[0], &dummy, message+s, FALSE);
8679 DrawPosition(TRUE, boards[0]);
8680 startedFromSetupPosition = TRUE;
8683 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8684 * want this, I was asked to put it in, and obliged.
8686 if (!strncmp(message, "setboard ", 9)) {
8687 Board initial_position;
8689 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8691 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8692 DisplayError(_("Bad FEN received from engine"), 0);
8696 CopyBoard(boards[0], initial_position);
8697 initialRulePlies = FENrulePlies;
8698 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8699 else gameMode = MachinePlaysBlack;
8700 DrawPosition(FALSE, boards[currentMove]);
8706 * Look for communication commands
8708 if (!strncmp(message, "telluser ", 9)) {
8709 if(message[9] == '\\' && message[10] == '\\')
8710 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8712 DisplayNote(message + 9);
8715 if (!strncmp(message, "tellusererror ", 14)) {
8717 if(message[14] == '\\' && message[15] == '\\')
8718 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8720 DisplayError(message + 14, 0);
8723 if (!strncmp(message, "tellopponent ", 13)) {
8724 if (appData.icsActive) {
8726 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8730 DisplayNote(message + 13);
8734 if (!strncmp(message, "tellothers ", 11)) {
8735 if (appData.icsActive) {
8737 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8740 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8743 if (!strncmp(message, "tellall ", 8)) {
8744 if (appData.icsActive) {
8746 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8750 DisplayNote(message + 8);
8754 if (strncmp(message, "warning", 7) == 0) {
8755 /* Undocumented feature, use tellusererror in new code */
8756 DisplayError(message, 0);
8759 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8760 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8761 strcat(realname, " query");
8762 AskQuestion(realname, buf2, buf1, cps->pr);
8765 /* Commands from the engine directly to ICS. We don't allow these to be
8766 * sent until we are logged on. Crafty kibitzes have been known to
8767 * interfere with the login process.
8770 if (!strncmp(message, "tellics ", 8)) {
8771 SendToICS(message + 8);
8775 if (!strncmp(message, "tellicsnoalias ", 15)) {
8776 SendToICS(ics_prefix);
8777 SendToICS(message + 15);
8781 /* The following are for backward compatibility only */
8782 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8783 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8784 SendToICS(ics_prefix);
8790 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8793 if(!strncmp(message, "highlight ", 10)) {
8794 if(appData.testLegality && appData.markers) return;
8795 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8798 if(!strncmp(message, "click ", 6)) {
8799 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8800 if(appData.testLegality || !appData.oneClick) return;
8801 sscanf(message+6, "%c%d%c", &f, &y, &c);
8802 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8803 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8804 x = x*squareSize + (x+1)*lineGap + squareSize/2;
8805 y = y*squareSize + (y+1)*lineGap + squareSize/2;
8806 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8807 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8808 LeftClick(Release, lastLeftX, lastLeftY);
8809 controlKey = (c == ',');
8810 LeftClick(Press, x, y);
8811 LeftClick(Release, x, y);
8812 first.highlight = f;
8816 * If the move is illegal, cancel it and redraw the board.
8817 * Also deal with other error cases. Matching is rather loose
8818 * here to accommodate engines written before the spec.
8820 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8821 strncmp(message, "Error", 5) == 0) {
8822 if (StrStr(message, "name") ||
8823 StrStr(message, "rating") || StrStr(message, "?") ||
8824 StrStr(message, "result") || StrStr(message, "board") ||
8825 StrStr(message, "bk") || StrStr(message, "computer") ||
8826 StrStr(message, "variant") || StrStr(message, "hint") ||
8827 StrStr(message, "random") || StrStr(message, "depth") ||
8828 StrStr(message, "accepted")) {
8831 if (StrStr(message, "protover")) {
8832 /* Program is responding to input, so it's apparently done
8833 initializing, and this error message indicates it is
8834 protocol version 1. So we don't need to wait any longer
8835 for it to initialize and send feature commands. */
8836 FeatureDone(cps, 1);
8837 cps->protocolVersion = 1;
8840 cps->maybeThinking = FALSE;
8842 if (StrStr(message, "draw")) {
8843 /* Program doesn't have "draw" command */
8844 cps->sendDrawOffers = 0;
8847 if (cps->sendTime != 1 &&
8848 (StrStr(message, "time") || StrStr(message, "otim"))) {
8849 /* Program apparently doesn't have "time" or "otim" command */
8853 if (StrStr(message, "analyze")) {
8854 cps->analysisSupport = FALSE;
8855 cps->analyzing = FALSE;
8856 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8857 EditGameEvent(); // [HGM] try to preserve loaded game
8858 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8859 DisplayError(buf2, 0);
8862 if (StrStr(message, "(no matching move)st")) {
8863 /* Special kludge for GNU Chess 4 only */
8864 cps->stKludge = TRUE;
8865 SendTimeControl(cps, movesPerSession, timeControl,
8866 timeIncrement, appData.searchDepth,
8870 if (StrStr(message, "(no matching move)sd")) {
8871 /* Special kludge for GNU Chess 4 only */
8872 cps->sdKludge = TRUE;
8873 SendTimeControl(cps, movesPerSession, timeControl,
8874 timeIncrement, appData.searchDepth,
8878 if (!StrStr(message, "llegal")) {
8881 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8882 gameMode == IcsIdle) return;
8883 if (forwardMostMove <= backwardMostMove) return;
8884 if (pausing) PauseEvent();
8885 if(appData.forceIllegal) {
8886 // [HGM] illegal: machine refused move; force position after move into it
8887 SendToProgram("force\n", cps);
8888 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8889 // we have a real problem now, as SendBoard will use the a2a3 kludge
8890 // when black is to move, while there might be nothing on a2 or black
8891 // might already have the move. So send the board as if white has the move.
8892 // But first we must change the stm of the engine, as it refused the last move
8893 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8894 if(WhiteOnMove(forwardMostMove)) {
8895 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8896 SendBoard(cps, forwardMostMove); // kludgeless board
8898 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8899 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8900 SendBoard(cps, forwardMostMove+1); // kludgeless board
8902 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8903 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8904 gameMode == TwoMachinesPlay)
8905 SendToProgram("go\n", cps);
8908 if (gameMode == PlayFromGameFile) {
8909 /* Stop reading this game file */
8910 gameMode = EditGame;
8913 /* [HGM] illegal-move claim should forfeit game when Xboard */
8914 /* only passes fully legal moves */
8915 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8916 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8917 "False illegal-move claim", GE_XBOARD );
8918 return; // do not take back move we tested as valid
8920 currentMove = forwardMostMove-1;
8921 DisplayMove(currentMove-1); /* before DisplayMoveError */
8922 SwitchClocks(forwardMostMove-1); // [HGM] race
8923 DisplayBothClocks();
8924 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8925 parseList[currentMove], _(cps->which));
8926 DisplayMoveError(buf1);
8927 DrawPosition(FALSE, boards[currentMove]);
8929 SetUserThinkingEnables();
8932 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8933 /* Program has a broken "time" command that
8934 outputs a string not ending in newline.
8940 * If chess program startup fails, exit with an error message.
8941 * Attempts to recover here are futile. [HGM] Well, we try anyway
8943 if ((StrStr(message, "unknown host") != NULL)
8944 || (StrStr(message, "No remote directory") != NULL)
8945 || (StrStr(message, "not found") != NULL)
8946 || (StrStr(message, "No such file") != NULL)
8947 || (StrStr(message, "can't alloc") != NULL)
8948 || (StrStr(message, "Permission denied") != NULL)) {
8950 cps->maybeThinking = FALSE;
8951 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8952 _(cps->which), cps->program, cps->host, message);
8953 RemoveInputSource(cps->isr);
8954 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8955 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8956 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8962 * Look for hint output
8964 if (sscanf(message, "Hint: %s", buf1) == 1) {
8965 if (cps == &first && hintRequested) {
8966 hintRequested = FALSE;
8967 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8968 &fromX, &fromY, &toX, &toY, &promoChar)) {
8969 (void) CoordsToAlgebraic(boards[forwardMostMove],
8970 PosFlags(forwardMostMove),
8971 fromY, fromX, toY, toX, promoChar, buf1);
8972 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8973 DisplayInformation(buf2);
8975 /* Hint move could not be parsed!? */
8976 snprintf(buf2, sizeof(buf2),
8977 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8978 buf1, _(cps->which));
8979 DisplayError(buf2, 0);
8982 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8988 * Ignore other messages if game is not in progress
8990 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8991 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8994 * look for win, lose, draw, or draw offer
8996 if (strncmp(message, "1-0", 3) == 0) {
8997 char *p, *q, *r = "";
8998 p = strchr(message, '{');
9006 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9008 } else if (strncmp(message, "0-1", 3) == 0) {
9009 char *p, *q, *r = "";
9010 p = strchr(message, '{');
9018 /* Kludge for Arasan 4.1 bug */
9019 if (strcmp(r, "Black resigns") == 0) {
9020 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9023 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9025 } else if (strncmp(message, "1/2", 3) == 0) {
9026 char *p, *q, *r = "";
9027 p = strchr(message, '{');
9036 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9039 } else if (strncmp(message, "White resign", 12) == 0) {
9040 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9042 } else if (strncmp(message, "Black resign", 12) == 0) {
9043 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9045 } else if (strncmp(message, "White matches", 13) == 0 ||
9046 strncmp(message, "Black matches", 13) == 0 ) {
9047 /* [HGM] ignore GNUShogi noises */
9049 } else if (strncmp(message, "White", 5) == 0 &&
9050 message[5] != '(' &&
9051 StrStr(message, "Black") == NULL) {
9052 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9054 } else if (strncmp(message, "Black", 5) == 0 &&
9055 message[5] != '(') {
9056 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9058 } else if (strcmp(message, "resign") == 0 ||
9059 strcmp(message, "computer resigns") == 0) {
9061 case MachinePlaysBlack:
9062 case IcsPlayingBlack:
9063 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9065 case MachinePlaysWhite:
9066 case IcsPlayingWhite:
9067 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9069 case TwoMachinesPlay:
9070 if (cps->twoMachinesColor[0] == 'w')
9071 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9073 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9080 } else if (strncmp(message, "opponent mates", 14) == 0) {
9082 case MachinePlaysBlack:
9083 case IcsPlayingBlack:
9084 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9086 case MachinePlaysWhite:
9087 case IcsPlayingWhite:
9088 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9090 case TwoMachinesPlay:
9091 if (cps->twoMachinesColor[0] == 'w')
9092 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9094 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9101 } else if (strncmp(message, "computer mates", 14) == 0) {
9103 case MachinePlaysBlack:
9104 case IcsPlayingBlack:
9105 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9107 case MachinePlaysWhite:
9108 case IcsPlayingWhite:
9109 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9111 case TwoMachinesPlay:
9112 if (cps->twoMachinesColor[0] == 'w')
9113 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9115 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9122 } else if (strncmp(message, "checkmate", 9) == 0) {
9123 if (WhiteOnMove(forwardMostMove)) {
9124 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9126 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9129 } else if (strstr(message, "Draw") != NULL ||
9130 strstr(message, "game is a draw") != NULL) {
9131 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9133 } else if (strstr(message, "offer") != NULL &&
9134 strstr(message, "draw") != NULL) {
9136 if (appData.zippyPlay && first.initDone) {
9137 /* Relay offer to ICS */
9138 SendToICS(ics_prefix);
9139 SendToICS("draw\n");
9142 cps->offeredDraw = 2; /* valid until this engine moves twice */
9143 if (gameMode == TwoMachinesPlay) {
9144 if (cps->other->offeredDraw) {
9145 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9146 /* [HGM] in two-machine mode we delay relaying draw offer */
9147 /* until after we also have move, to see if it is really claim */
9149 } else if (gameMode == MachinePlaysWhite ||
9150 gameMode == MachinePlaysBlack) {
9151 if (userOfferedDraw) {
9152 DisplayInformation(_("Machine accepts your draw offer"));
9153 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9155 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9162 * Look for thinking output
9164 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9165 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9167 int plylev, mvleft, mvtot, curscore, time;
9168 char mvname[MOVE_LEN];
9172 int prefixHint = FALSE;
9173 mvname[0] = NULLCHAR;
9176 case MachinePlaysBlack:
9177 case IcsPlayingBlack:
9178 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9180 case MachinePlaysWhite:
9181 case IcsPlayingWhite:
9182 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9187 case IcsObserving: /* [DM] icsEngineAnalyze */
9188 if (!appData.icsEngineAnalyze) ignore = TRUE;
9190 case TwoMachinesPlay:
9191 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9201 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9203 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9204 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9206 if (plyext != ' ' && plyext != '\t') {
9210 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9211 if( cps->scoreIsAbsolute &&
9212 ( gameMode == MachinePlaysBlack ||
9213 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9214 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9215 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9216 !WhiteOnMove(currentMove)
9219 curscore = -curscore;
9222 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9224 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9227 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9228 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9229 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9230 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9231 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9232 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9236 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9237 DisplayError(_("failed writing PV"), 0);
9240 tempStats.depth = plylev;
9241 tempStats.nodes = nodes;
9242 tempStats.time = time;
9243 tempStats.score = curscore;
9244 tempStats.got_only_move = 0;
9246 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9249 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9250 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9251 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9252 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9253 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9254 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9255 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9256 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9259 /* Buffer overflow protection */
9260 if (pv[0] != NULLCHAR) {
9261 if (strlen(pv) >= sizeof(tempStats.movelist)
9262 && appData.debugMode) {
9264 "PV is too long; using the first %u bytes.\n",
9265 (unsigned) sizeof(tempStats.movelist) - 1);
9268 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9270 sprintf(tempStats.movelist, " no PV\n");
9273 if (tempStats.seen_stat) {
9274 tempStats.ok_to_send = 1;
9277 if (strchr(tempStats.movelist, '(') != NULL) {
9278 tempStats.line_is_book = 1;
9279 tempStats.nr_moves = 0;
9280 tempStats.moves_left = 0;
9282 tempStats.line_is_book = 0;
9285 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9286 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9288 SendProgramStatsToFrontend( cps, &tempStats );
9291 [AS] Protect the thinkOutput buffer from overflow... this
9292 is only useful if buf1 hasn't overflowed first!
9294 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9296 (gameMode == TwoMachinesPlay ?
9297 ToUpper(cps->twoMachinesColor[0]) : ' '),
9298 ((double) curscore) / 100.0,
9299 prefixHint ? lastHint : "",
9300 prefixHint ? " " : "" );
9302 if( buf1[0] != NULLCHAR ) {
9303 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9305 if( strlen(pv) > max_len ) {
9306 if( appData.debugMode) {
9307 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9309 pv[max_len+1] = '\0';
9312 strcat( thinkOutput, pv);
9315 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9316 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9317 DisplayMove(currentMove - 1);
9321 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9322 /* crafty (9.25+) says "(only move) <move>"
9323 * if there is only 1 legal move
9325 sscanf(p, "(only move) %s", buf1);
9326 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9327 sprintf(programStats.movelist, "%s (only move)", buf1);
9328 programStats.depth = 1;
9329 programStats.nr_moves = 1;
9330 programStats.moves_left = 1;
9331 programStats.nodes = 1;
9332 programStats.time = 1;
9333 programStats.got_only_move = 1;
9335 /* Not really, but we also use this member to
9336 mean "line isn't going to change" (Crafty
9337 isn't searching, so stats won't change) */
9338 programStats.line_is_book = 1;
9340 SendProgramStatsToFrontend( cps, &programStats );
9342 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9343 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9344 DisplayMove(currentMove - 1);
9347 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9348 &time, &nodes, &plylev, &mvleft,
9349 &mvtot, mvname) >= 5) {
9350 /* The stat01: line is from Crafty (9.29+) in response
9351 to the "." command */
9352 programStats.seen_stat = 1;
9353 cps->maybeThinking = TRUE;
9355 if (programStats.got_only_move || !appData.periodicUpdates)
9358 programStats.depth = plylev;
9359 programStats.time = time;
9360 programStats.nodes = nodes;
9361 programStats.moves_left = mvleft;
9362 programStats.nr_moves = mvtot;
9363 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9364 programStats.ok_to_send = 1;
9365 programStats.movelist[0] = '\0';
9367 SendProgramStatsToFrontend( cps, &programStats );
9371 } else if (strncmp(message,"++",2) == 0) {
9372 /* Crafty 9.29+ outputs this */
9373 programStats.got_fail = 2;
9376 } else if (strncmp(message,"--",2) == 0) {
9377 /* Crafty 9.29+ outputs this */
9378 programStats.got_fail = 1;
9381 } else if (thinkOutput[0] != NULLCHAR &&
9382 strncmp(message, " ", 4) == 0) {
9383 unsigned message_len;
9386 while (*p && *p == ' ') p++;
9388 message_len = strlen( p );
9390 /* [AS] Avoid buffer overflow */
9391 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9392 strcat(thinkOutput, " ");
9393 strcat(thinkOutput, p);
9396 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9397 strcat(programStats.movelist, " ");
9398 strcat(programStats.movelist, p);
9401 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9402 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9403 DisplayMove(currentMove - 1);
9411 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9412 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9414 ChessProgramStats cpstats;
9416 if (plyext != ' ' && plyext != '\t') {
9420 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9421 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9422 curscore = -curscore;
9425 cpstats.depth = plylev;
9426 cpstats.nodes = nodes;
9427 cpstats.time = time;
9428 cpstats.score = curscore;
9429 cpstats.got_only_move = 0;
9430 cpstats.movelist[0] = '\0';
9432 if (buf1[0] != NULLCHAR) {
9433 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9436 cpstats.ok_to_send = 0;
9437 cpstats.line_is_book = 0;
9438 cpstats.nr_moves = 0;
9439 cpstats.moves_left = 0;
9441 SendProgramStatsToFrontend( cps, &cpstats );
9448 /* Parse a game score from the character string "game", and
9449 record it as the history of the current game. The game
9450 score is NOT assumed to start from the standard position.
9451 The display is not updated in any way.
9454 ParseGameHistory (char *game)
9457 int fromX, fromY, toX, toY, boardIndex;
9462 if (appData.debugMode)
9463 fprintf(debugFP, "Parsing game history: %s\n", game);
9465 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9466 gameInfo.site = StrSave(appData.icsHost);
9467 gameInfo.date = PGNDate();
9468 gameInfo.round = StrSave("-");
9470 /* Parse out names of players */
9471 while (*game == ' ') game++;
9473 while (*game != ' ') *p++ = *game++;
9475 gameInfo.white = StrSave(buf);
9476 while (*game == ' ') game++;
9478 while (*game != ' ' && *game != '\n') *p++ = *game++;
9480 gameInfo.black = StrSave(buf);
9483 boardIndex = blackPlaysFirst ? 1 : 0;
9486 yyboardindex = boardIndex;
9487 moveType = (ChessMove) Myylex();
9489 case IllegalMove: /* maybe suicide chess, etc. */
9490 if (appData.debugMode) {
9491 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9492 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9493 setbuf(debugFP, NULL);
9495 case WhitePromotion:
9496 case BlackPromotion:
9497 case WhiteNonPromotion:
9498 case BlackNonPromotion:
9500 case WhiteCapturesEnPassant:
9501 case BlackCapturesEnPassant:
9502 case WhiteKingSideCastle:
9503 case WhiteQueenSideCastle:
9504 case BlackKingSideCastle:
9505 case BlackQueenSideCastle:
9506 case WhiteKingSideCastleWild:
9507 case WhiteQueenSideCastleWild:
9508 case BlackKingSideCastleWild:
9509 case BlackQueenSideCastleWild:
9511 case WhiteHSideCastleFR:
9512 case WhiteASideCastleFR:
9513 case BlackHSideCastleFR:
9514 case BlackASideCastleFR:
9516 fromX = currentMoveString[0] - AAA;
9517 fromY = currentMoveString[1] - ONE;
9518 toX = currentMoveString[2] - AAA;
9519 toY = currentMoveString[3] - ONE;
9520 promoChar = currentMoveString[4];
9524 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9525 fromX = moveType == WhiteDrop ?
9526 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9527 (int) CharToPiece(ToLower(currentMoveString[0]));
9529 toX = currentMoveString[2] - AAA;
9530 toY = currentMoveString[3] - ONE;
9531 promoChar = NULLCHAR;
9535 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9536 if (appData.debugMode) {
9537 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9538 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9539 setbuf(debugFP, NULL);
9541 DisplayError(buf, 0);
9543 case ImpossibleMove:
9545 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9546 if (appData.debugMode) {
9547 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9548 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9549 setbuf(debugFP, NULL);
9551 DisplayError(buf, 0);
9554 if (boardIndex < backwardMostMove) {
9555 /* Oops, gap. How did that happen? */
9556 DisplayError(_("Gap in move list"), 0);
9559 backwardMostMove = blackPlaysFirst ? 1 : 0;
9560 if (boardIndex > forwardMostMove) {
9561 forwardMostMove = boardIndex;
9565 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9566 strcat(parseList[boardIndex-1], " ");
9567 strcat(parseList[boardIndex-1], yy_text);
9579 case GameUnfinished:
9580 if (gameMode == IcsExamining) {
9581 if (boardIndex < backwardMostMove) {
9582 /* Oops, gap. How did that happen? */
9585 backwardMostMove = blackPlaysFirst ? 1 : 0;
9588 gameInfo.result = moveType;
9589 p = strchr(yy_text, '{');
9590 if (p == NULL) p = strchr(yy_text, '(');
9593 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9595 q = strchr(p, *p == '{' ? '}' : ')');
9596 if (q != NULL) *q = NULLCHAR;
9599 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9600 gameInfo.resultDetails = StrSave(p);
9603 if (boardIndex >= forwardMostMove &&
9604 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9605 backwardMostMove = blackPlaysFirst ? 1 : 0;
9608 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9609 fromY, fromX, toY, toX, promoChar,
9610 parseList[boardIndex]);
9611 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9612 /* currentMoveString is set as a side-effect of yylex */
9613 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9614 strcat(moveList[boardIndex], "\n");
9616 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9617 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9623 if(gameInfo.variant != VariantShogi)
9624 strcat(parseList[boardIndex - 1], "+");
9628 strcat(parseList[boardIndex - 1], "#");
9635 /* Apply a move to the given board */
9637 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9639 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9640 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9642 /* [HGM] compute & store e.p. status and castling rights for new position */
9643 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9645 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9646 oldEP = (signed char)board[EP_STATUS];
9647 board[EP_STATUS] = EP_NONE;
9649 if (fromY == DROP_RANK) {
9651 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9652 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9655 piece = board[toY][toX] = (ChessSquare) fromX;
9660 if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9661 victim = board[killY][killX],
9662 board[killY][killX] = EmptySquare,
9663 board[EP_STATUS] = EP_CAPTURE;
9665 if( board[toY][toX] != EmptySquare ) {
9666 board[EP_STATUS] = EP_CAPTURE;
9667 if( (fromX != toX || fromY != toY) && // not igui!
9668 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9669 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
9670 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9674 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9675 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9676 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9678 if( board[fromY][fromX] == WhitePawn ) {
9679 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9680 board[EP_STATUS] = EP_PAWN_MOVE;
9682 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9683 gameInfo.variant != VariantBerolina || toX < fromX)
9684 board[EP_STATUS] = toX | berolina;
9685 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9686 gameInfo.variant != VariantBerolina || toX > fromX)
9687 board[EP_STATUS] = toX;
9690 if( board[fromY][fromX] == BlackPawn ) {
9691 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9692 board[EP_STATUS] = EP_PAWN_MOVE;
9693 if( toY-fromY== -2) {
9694 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9695 gameInfo.variant != VariantBerolina || toX < fromX)
9696 board[EP_STATUS] = toX | berolina;
9697 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9698 gameInfo.variant != VariantBerolina || toX > fromX)
9699 board[EP_STATUS] = toX;
9703 for(i=0; i<nrCastlingRights; i++) {
9704 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9705 board[CASTLING][i] == toX && castlingRank[i] == toY
9706 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9709 if(gameInfo.variant == VariantSChess) { // update virginity
9710 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9711 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9712 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9713 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9716 if (fromX == toX && fromY == toY) return;
9718 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9719 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9720 if(gameInfo.variant == VariantKnightmate)
9721 king += (int) WhiteUnicorn - (int) WhiteKing;
9723 /* Code added by Tord: */
9724 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9725 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9726 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9727 board[fromY][fromX] = EmptySquare;
9728 board[toY][toX] = EmptySquare;
9729 if((toX > fromX) != (piece == WhiteRook)) {
9730 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9732 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9734 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9735 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9736 board[fromY][fromX] = EmptySquare;
9737 board[toY][toX] = EmptySquare;
9738 if((toX > fromX) != (piece == BlackRook)) {
9739 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9741 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9743 /* End of code added by Tord */
9745 } else if (board[fromY][fromX] == king
9746 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9747 && toY == fromY && toX > fromX+1) {
9748 board[fromY][fromX] = EmptySquare;
9749 board[toY][toX] = king;
9750 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9751 board[fromY][BOARD_RGHT-1] = EmptySquare;
9752 } else if (board[fromY][fromX] == king
9753 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9754 && toY == fromY && toX < fromX-1) {
9755 board[fromY][fromX] = EmptySquare;
9756 board[toY][toX] = king;
9757 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9758 board[fromY][BOARD_LEFT] = EmptySquare;
9759 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9760 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9761 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9763 /* white pawn promotion */
9764 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9765 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9766 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9767 board[fromY][fromX] = EmptySquare;
9768 } else if ((fromY >= BOARD_HEIGHT>>1)
9769 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9771 && gameInfo.variant != VariantXiangqi
9772 && gameInfo.variant != VariantBerolina
9773 && (board[fromY][fromX] == WhitePawn)
9774 && (board[toY][toX] == EmptySquare)) {
9775 board[fromY][fromX] = EmptySquare;
9776 board[toY][toX] = WhitePawn;
9777 captured = board[toY - 1][toX];
9778 board[toY - 1][toX] = EmptySquare;
9779 } else if ((fromY == BOARD_HEIGHT-4)
9781 && gameInfo.variant == VariantBerolina
9782 && (board[fromY][fromX] == WhitePawn)
9783 && (board[toY][toX] == EmptySquare)) {
9784 board[fromY][fromX] = EmptySquare;
9785 board[toY][toX] = WhitePawn;
9786 if(oldEP & EP_BEROLIN_A) {
9787 captured = board[fromY][fromX-1];
9788 board[fromY][fromX-1] = EmptySquare;
9789 }else{ captured = board[fromY][fromX+1];
9790 board[fromY][fromX+1] = EmptySquare;
9792 } else if (board[fromY][fromX] == king
9793 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9794 && toY == fromY && toX > fromX+1) {
9795 board[fromY][fromX] = EmptySquare;
9796 board[toY][toX] = king;
9797 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9798 board[fromY][BOARD_RGHT-1] = EmptySquare;
9799 } else if (board[fromY][fromX] == king
9800 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9801 && toY == fromY && toX < fromX-1) {
9802 board[fromY][fromX] = EmptySquare;
9803 board[toY][toX] = king;
9804 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9805 board[fromY][BOARD_LEFT] = EmptySquare;
9806 } else if (fromY == 7 && fromX == 3
9807 && board[fromY][fromX] == BlackKing
9808 && toY == 7 && toX == 5) {
9809 board[fromY][fromX] = EmptySquare;
9810 board[toY][toX] = BlackKing;
9811 board[fromY][7] = EmptySquare;
9812 board[toY][4] = BlackRook;
9813 } else if (fromY == 7 && fromX == 3
9814 && board[fromY][fromX] == BlackKing
9815 && toY == 7 && toX == 1) {
9816 board[fromY][fromX] = EmptySquare;
9817 board[toY][toX] = BlackKing;
9818 board[fromY][0] = EmptySquare;
9819 board[toY][2] = BlackRook;
9820 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9821 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9822 && toY < promoRank && promoChar
9824 /* black pawn promotion */
9825 board[toY][toX] = CharToPiece(ToLower(promoChar));
9826 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9827 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9828 board[fromY][fromX] = EmptySquare;
9829 } else if ((fromY < BOARD_HEIGHT>>1)
9830 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9832 && gameInfo.variant != VariantXiangqi
9833 && gameInfo.variant != VariantBerolina
9834 && (board[fromY][fromX] == BlackPawn)
9835 && (board[toY][toX] == EmptySquare)) {
9836 board[fromY][fromX] = EmptySquare;
9837 board[toY][toX] = BlackPawn;
9838 captured = board[toY + 1][toX];
9839 board[toY + 1][toX] = EmptySquare;
9840 } else if ((fromY == 3)
9842 && gameInfo.variant == VariantBerolina
9843 && (board[fromY][fromX] == BlackPawn)
9844 && (board[toY][toX] == EmptySquare)) {
9845 board[fromY][fromX] = EmptySquare;
9846 board[toY][toX] = BlackPawn;
9847 if(oldEP & EP_BEROLIN_A) {
9848 captured = board[fromY][fromX-1];
9849 board[fromY][fromX-1] = EmptySquare;
9850 }else{ captured = board[fromY][fromX+1];
9851 board[fromY][fromX+1] = EmptySquare;
9854 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9855 board[fromY][fromX] = EmptySquare;
9856 board[toY][toX] = piece;
9860 if (gameInfo.holdingsWidth != 0) {
9862 /* !!A lot more code needs to be written to support holdings */
9863 /* [HGM] OK, so I have written it. Holdings are stored in the */
9864 /* penultimate board files, so they are automaticlly stored */
9865 /* in the game history. */
9866 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9867 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9868 /* Delete from holdings, by decreasing count */
9869 /* and erasing image if necessary */
9870 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9871 if(p < (int) BlackPawn) { /* white drop */
9872 p -= (int)WhitePawn;
9873 p = PieceToNumber((ChessSquare)p);
9874 if(p >= gameInfo.holdingsSize) p = 0;
9875 if(--board[p][BOARD_WIDTH-2] <= 0)
9876 board[p][BOARD_WIDTH-1] = EmptySquare;
9877 if((int)board[p][BOARD_WIDTH-2] < 0)
9878 board[p][BOARD_WIDTH-2] = 0;
9879 } else { /* black drop */
9880 p -= (int)BlackPawn;
9881 p = PieceToNumber((ChessSquare)p);
9882 if(p >= gameInfo.holdingsSize) p = 0;
9883 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9884 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9885 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9886 board[BOARD_HEIGHT-1-p][1] = 0;
9889 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9890 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9891 /* [HGM] holdings: Add to holdings, if holdings exist */
9892 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9893 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9894 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9897 if (p >= (int) BlackPawn) {
9898 p -= (int)BlackPawn;
9899 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9900 /* in Shogi restore piece to its original first */
9901 captured = (ChessSquare) (DEMOTED captured);
9904 p = PieceToNumber((ChessSquare)p);
9905 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9906 board[p][BOARD_WIDTH-2]++;
9907 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9909 p -= (int)WhitePawn;
9910 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9911 captured = (ChessSquare) (DEMOTED captured);
9914 p = PieceToNumber((ChessSquare)p);
9915 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9916 board[BOARD_HEIGHT-1-p][1]++;
9917 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9920 } else if (gameInfo.variant == VariantAtomic) {
9921 if (captured != EmptySquare) {
9923 for (y = toY-1; y <= toY+1; y++) {
9924 for (x = toX-1; x <= toX+1; x++) {
9925 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9926 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9927 board[y][x] = EmptySquare;
9931 board[toY][toX] = EmptySquare;
9934 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9935 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9937 if(promoChar == '+') {
9938 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9939 board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9940 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9941 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9942 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9943 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9944 board[toY][toX] = newPiece;
9946 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9947 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9948 // [HGM] superchess: take promotion piece out of holdings
9949 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9950 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9951 if(!--board[k][BOARD_WIDTH-2])
9952 board[k][BOARD_WIDTH-1] = EmptySquare;
9954 if(!--board[BOARD_HEIGHT-1-k][1])
9955 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9960 /* Updates forwardMostMove */
9962 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9964 int x = toX, y = toY;
9965 char *s = parseList[forwardMostMove];
9966 ChessSquare p = boards[forwardMostMove][toY][toX];
9967 // forwardMostMove++; // [HGM] bare: moved downstream
9969 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
9970 (void) CoordsToAlgebraic(boards[forwardMostMove],
9971 PosFlags(forwardMostMove),
9972 fromY, fromX, y, x, promoChar,
9974 if(killX >= 0 && killY >= 0)
9975 sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
9977 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9978 int timeLeft; static int lastLoadFlag=0; int king, piece;
9979 piece = boards[forwardMostMove][fromY][fromX];
9980 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9981 if(gameInfo.variant == VariantKnightmate)
9982 king += (int) WhiteUnicorn - (int) WhiteKing;
9983 if(forwardMostMove == 0) {
9984 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9985 fprintf(serverMoves, "%s;", UserName());
9986 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9987 fprintf(serverMoves, "%s;", second.tidy);
9988 fprintf(serverMoves, "%s;", first.tidy);
9989 if(gameMode == MachinePlaysWhite)
9990 fprintf(serverMoves, "%s;", UserName());
9991 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9992 fprintf(serverMoves, "%s;", second.tidy);
9993 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9994 lastLoadFlag = loadFlag;
9996 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9997 // print castling suffix
9998 if( toY == fromY && piece == king ) {
10000 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10002 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10005 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10006 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10007 boards[forwardMostMove][toY][toX] == EmptySquare
10008 && fromX != toX && fromY != toY)
10009 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10010 // promotion suffix
10011 if(promoChar != NULLCHAR) {
10012 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10013 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10014 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10015 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10018 char buf[MOVE_LEN*2], *p; int len;
10019 fprintf(serverMoves, "/%d/%d",
10020 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10021 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10022 else timeLeft = blackTimeRemaining/1000;
10023 fprintf(serverMoves, "/%d", timeLeft);
10024 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10025 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10026 if(p = strchr(buf, '=')) *p = NULLCHAR;
10027 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10028 fprintf(serverMoves, "/%s", buf);
10030 fflush(serverMoves);
10033 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10034 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10037 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10038 if (commentList[forwardMostMove+1] != NULL) {
10039 free(commentList[forwardMostMove+1]);
10040 commentList[forwardMostMove+1] = NULL;
10042 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10043 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10044 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10045 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10046 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10047 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10048 adjustedClock = FALSE;
10049 gameInfo.result = GameUnfinished;
10050 if (gameInfo.resultDetails != NULL) {
10051 free(gameInfo.resultDetails);
10052 gameInfo.resultDetails = NULL;
10054 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10055 moveList[forwardMostMove - 1]);
10056 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10062 if(gameInfo.variant != VariantShogi)
10063 strcat(parseList[forwardMostMove - 1], "+");
10067 strcat(parseList[forwardMostMove - 1], "#");
10071 killX = killY = -1; // [HGM] lion: used up
10074 /* Updates currentMove if not pausing */
10076 ShowMove (int fromX, int fromY, int toX, int toY)
10078 int instant = (gameMode == PlayFromGameFile) ?
10079 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10080 if(appData.noGUI) return;
10081 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10083 if (forwardMostMove == currentMove + 1) {
10084 AnimateMove(boards[forwardMostMove - 1],
10085 fromX, fromY, toX, toY);
10088 currentMove = forwardMostMove;
10091 if (instant) return;
10093 DisplayMove(currentMove - 1);
10094 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10095 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10096 SetHighlights(fromX, fromY, toX, toY);
10099 DrawPosition(FALSE, boards[currentMove]);
10100 DisplayBothClocks();
10101 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10105 SendEgtPath (ChessProgramState *cps)
10106 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10107 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10109 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10112 char c, *q = name+1, *r, *s;
10114 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10115 while(*p && *p != ',') *q++ = *p++;
10116 *q++ = ':'; *q = 0;
10117 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10118 strcmp(name, ",nalimov:") == 0 ) {
10119 // take nalimov path from the menu-changeable option first, if it is defined
10120 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10121 SendToProgram(buf,cps); // send egtbpath command for nalimov
10123 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10124 (s = StrStr(appData.egtFormats, name)) != NULL) {
10125 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10126 s = r = StrStr(s, ":") + 1; // beginning of path info
10127 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10128 c = *r; *r = 0; // temporarily null-terminate path info
10129 *--q = 0; // strip of trailig ':' from name
10130 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10132 SendToProgram(buf,cps); // send egtbpath command for this format
10134 if(*p == ',') p++; // read away comma to position for next format name
10139 NonStandardBoardSize ()
10141 /* [HGM] Awkward testing. Should really be a table */
10142 int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10143 if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10144 if( gameInfo.variant == VariantXiangqi )
10145 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10146 if( gameInfo.variant == VariantShogi )
10147 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10148 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10149 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10150 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10151 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10152 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10153 if( gameInfo.variant == VariantCourier )
10154 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10155 if( gameInfo.variant == VariantSuper )
10156 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10157 if( gameInfo.variant == VariantGreat )
10158 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10159 if( gameInfo.variant == VariantSChess )
10160 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10161 if( gameInfo.variant == VariantGrand )
10162 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10163 if( gameInfo.variant == VariantChu )
10164 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
10169 InitChessProgram (ChessProgramState *cps, int setup)
10170 /* setup needed to setup FRC opening position */
10172 char buf[MSG_SIZ], b[MSG_SIZ];
10173 if (appData.noChessProgram) return;
10174 hintRequested = FALSE;
10175 bookRequested = FALSE;
10177 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10178 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10179 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10180 if(cps->memSize) { /* [HGM] memory */
10181 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10182 SendToProgram(buf, cps);
10184 SendEgtPath(cps); /* [HGM] EGT */
10185 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10186 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10187 SendToProgram(buf, cps);
10190 SendToProgram(cps->initString, cps);
10191 if (gameInfo.variant != VariantNormal &&
10192 gameInfo.variant != VariantLoadable
10193 /* [HGM] also send variant if board size non-standard */
10194 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10196 char *v = VariantName(gameInfo.variant);
10197 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10198 /* [HGM] in protocol 1 we have to assume all variants valid */
10199 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10200 DisplayFatalError(buf, 0, 1);
10204 if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10205 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10206 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10207 /* [HGM] varsize: try first if this defiant size variant is specifically known */
10208 if(StrStr(cps->variants, b) == NULL) {
10209 // specific sized variant not known, check if general sizing allowed
10210 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10211 if(StrStr(cps->variants, "boardsize") == NULL) {
10212 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10213 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10214 DisplayFatalError(buf, 0, 1);
10217 /* [HGM] here we really should compare with the maximum supported board size */
10220 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10221 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10222 SendToProgram(buf, cps);
10224 currentlyInitializedVariant = gameInfo.variant;
10226 /* [HGM] send opening position in FRC to first engine */
10228 SendToProgram("force\n", cps);
10230 /* engine is now in force mode! Set flag to wake it up after first move. */
10231 setboardSpoiledMachineBlack = 1;
10234 if (cps->sendICS) {
10235 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10236 SendToProgram(buf, cps);
10238 cps->maybeThinking = FALSE;
10239 cps->offeredDraw = 0;
10240 if (!appData.icsActive) {
10241 SendTimeControl(cps, movesPerSession, timeControl,
10242 timeIncrement, appData.searchDepth,
10245 if (appData.showThinking
10246 // [HGM] thinking: four options require thinking output to be sent
10247 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10249 SendToProgram("post\n", cps);
10251 SendToProgram("hard\n", cps);
10252 if (!appData.ponderNextMove) {
10253 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10254 it without being sure what state we are in first. "hard"
10255 is not a toggle, so that one is OK.
10257 SendToProgram("easy\n", cps);
10259 if (cps->usePing) {
10260 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10261 SendToProgram(buf, cps);
10263 cps->initDone = TRUE;
10264 ClearEngineOutputPane(cps == &second);
10269 ResendOptions (ChessProgramState *cps)
10270 { // send the stored value of the options
10273 Option *opt = cps->option;
10274 for(i=0; i<cps->nrOptions; i++, opt++) {
10275 switch(opt->type) {
10279 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10282 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10285 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10291 SendToProgram(buf, cps);
10296 StartChessProgram (ChessProgramState *cps)
10301 if (appData.noChessProgram) return;
10302 cps->initDone = FALSE;
10304 if (strcmp(cps->host, "localhost") == 0) {
10305 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10306 } else if (*appData.remoteShell == NULLCHAR) {
10307 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10309 if (*appData.remoteUser == NULLCHAR) {
10310 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10313 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10314 cps->host, appData.remoteUser, cps->program);
10316 err = StartChildProcess(buf, "", &cps->pr);
10320 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10321 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10322 if(cps != &first) return;
10323 appData.noChessProgram = TRUE;
10326 // DisplayFatalError(buf, err, 1);
10327 // cps->pr = NoProc;
10328 // cps->isr = NULL;
10332 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10333 if (cps->protocolVersion > 1) {
10334 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10335 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10336 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10337 cps->comboCnt = 0; // and values of combo boxes
10339 SendToProgram(buf, cps);
10340 if(cps->reload) ResendOptions(cps);
10342 SendToProgram("xboard\n", cps);
10347 TwoMachinesEventIfReady P((void))
10349 static int curMess = 0;
10350 if (first.lastPing != first.lastPong) {
10351 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10352 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10355 if (second.lastPing != second.lastPong) {
10356 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10357 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10360 DisplayMessage("", ""); curMess = 0;
10361 TwoMachinesEvent();
10365 MakeName (char *template)
10369 static char buf[MSG_SIZ];
10373 clock = time((time_t *)NULL);
10374 tm = localtime(&clock);
10376 while(*p++ = *template++) if(p[-1] == '%') {
10377 switch(*template++) {
10378 case 0: *p = 0; return buf;
10379 case 'Y': i = tm->tm_year+1900; break;
10380 case 'y': i = tm->tm_year-100; break;
10381 case 'M': i = tm->tm_mon+1; break;
10382 case 'd': i = tm->tm_mday; break;
10383 case 'h': i = tm->tm_hour; break;
10384 case 'm': i = tm->tm_min; break;
10385 case 's': i = tm->tm_sec; break;
10388 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10394 CountPlayers (char *p)
10397 while(p = strchr(p, '\n')) p++, n++; // count participants
10402 WriteTourneyFile (char *results, FILE *f)
10403 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10404 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10405 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10406 // create a file with tournament description
10407 fprintf(f, "-participants {%s}\n", appData.participants);
10408 fprintf(f, "-seedBase %d\n", appData.seedBase);
10409 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10410 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10411 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10412 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10413 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10414 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10415 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10416 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10417 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10418 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10419 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10420 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10421 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10422 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10423 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10424 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10425 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10426 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10427 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10428 fprintf(f, "-smpCores %d\n", appData.smpCores);
10430 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10432 fprintf(f, "-mps %d\n", appData.movesPerSession);
10433 fprintf(f, "-tc %s\n", appData.timeControl);
10434 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10436 fprintf(f, "-results \"%s\"\n", results);
10441 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10444 Substitute (char *participants, int expunge)
10446 int i, changed, changes=0, nPlayers=0;
10447 char *p, *q, *r, buf[MSG_SIZ];
10448 if(participants == NULL) return;
10449 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10450 r = p = participants; q = appData.participants;
10451 while(*p && *p == *q) {
10452 if(*p == '\n') r = p+1, nPlayers++;
10455 if(*p) { // difference
10456 while(*p && *p++ != '\n');
10457 while(*q && *q++ != '\n');
10458 changed = nPlayers;
10459 changes = 1 + (strcmp(p, q) != 0);
10461 if(changes == 1) { // a single engine mnemonic was changed
10462 q = r; while(*q) nPlayers += (*q++ == '\n');
10463 p = buf; while(*r && (*p = *r++) != '\n') p++;
10465 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10466 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10467 if(mnemonic[i]) { // The substitute is valid
10469 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10470 flock(fileno(f), LOCK_EX);
10471 ParseArgsFromFile(f);
10472 fseek(f, 0, SEEK_SET);
10473 FREE(appData.participants); appData.participants = participants;
10474 if(expunge) { // erase results of replaced engine
10475 int len = strlen(appData.results), w, b, dummy;
10476 for(i=0; i<len; i++) {
10477 Pairing(i, nPlayers, &w, &b, &dummy);
10478 if((w == changed || b == changed) && appData.results[i] == '*') {
10479 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10484 for(i=0; i<len; i++) {
10485 Pairing(i, nPlayers, &w, &b, &dummy);
10486 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10489 WriteTourneyFile(appData.results, f);
10490 fclose(f); // release lock
10493 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10495 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10496 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10497 free(participants);
10502 CheckPlayers (char *participants)
10505 char buf[MSG_SIZ], *p;
10506 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10507 while(p = strchr(participants, '\n')) {
10509 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10511 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10513 DisplayError(buf, 0);
10517 participants = p + 1;
10523 CreateTourney (char *name)
10526 if(matchMode && strcmp(name, appData.tourneyFile)) {
10527 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10529 if(name[0] == NULLCHAR) {
10530 if(appData.participants[0])
10531 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10534 f = fopen(name, "r");
10535 if(f) { // file exists
10536 ASSIGN(appData.tourneyFile, name);
10537 ParseArgsFromFile(f); // parse it
10539 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10540 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10541 DisplayError(_("Not enough participants"), 0);
10544 if(CheckPlayers(appData.participants)) return 0;
10545 ASSIGN(appData.tourneyFile, name);
10546 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10547 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10550 appData.noChessProgram = FALSE;
10551 appData.clockMode = TRUE;
10557 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10559 char buf[MSG_SIZ], *p, *q;
10560 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10561 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10562 skip = !all && group[0]; // if group requested, we start in skip mode
10563 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10564 p = names; q = buf; header = 0;
10565 while(*p && *p != '\n') *q++ = *p++;
10567 if(*p == '\n') p++;
10568 if(buf[0] == '#') {
10569 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10570 depth++; // we must be entering a new group
10571 if(all) continue; // suppress printing group headers when complete list requested
10573 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10575 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10576 if(engineList[i]) free(engineList[i]);
10577 engineList[i] = strdup(buf);
10578 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10579 if(engineMnemonic[i]) free(engineMnemonic[i]);
10580 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10582 sscanf(q + 8, "%s", buf + strlen(buf));
10585 engineMnemonic[i] = strdup(buf);
10588 engineList[i] = engineMnemonic[i] = NULL;
10592 // following implemented as macro to avoid type limitations
10593 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10596 SwapEngines (int n)
10597 { // swap settings for first engine and other engine (so far only some selected options)
10602 SWAP(chessProgram, p)
10604 SWAP(hasOwnBookUCI, h)
10605 SWAP(protocolVersion, h)
10607 SWAP(scoreIsAbsolute, h)
10612 SWAP(engOptions, p)
10613 SWAP(engInitString, p)
10614 SWAP(computerString, p)
10616 SWAP(fenOverride, p)
10618 SWAP(accumulateTC, h)
10623 GetEngineLine (char *s, int n)
10627 extern char *icsNames;
10628 if(!s || !*s) return 0;
10629 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10630 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10631 if(!mnemonic[i]) return 0;
10632 if(n == 11) return 1; // just testing if there was a match
10633 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10634 if(n == 1) SwapEngines(n);
10635 ParseArgsFromString(buf);
10636 if(n == 1) SwapEngines(n);
10637 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10638 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10639 ParseArgsFromString(buf);
10645 SetPlayer (int player, char *p)
10646 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10648 char buf[MSG_SIZ], *engineName;
10649 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10650 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10651 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10653 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10654 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10655 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10656 ParseArgsFromString(buf);
10657 } else { // no engine with this nickname is installed!
10658 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10659 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10660 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10662 DisplayError(buf, 0);
10669 char *recentEngines;
10672 RecentEngineEvent (int nr)
10675 // SwapEngines(1); // bump first to second
10676 // ReplaceEngine(&second, 1); // and load it there
10677 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10678 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10679 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10680 ReplaceEngine(&first, 0);
10681 FloatToFront(&appData.recentEngineList, command[n]);
10686 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10687 { // determine players from game number
10688 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10690 if(appData.tourneyType == 0) {
10691 roundsPerCycle = (nPlayers - 1) | 1;
10692 pairingsPerRound = nPlayers / 2;
10693 } else if(appData.tourneyType > 0) {
10694 roundsPerCycle = nPlayers - appData.tourneyType;
10695 pairingsPerRound = appData.tourneyType;
10697 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10698 gamesPerCycle = gamesPerRound * roundsPerCycle;
10699 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10700 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10701 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10702 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10703 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10704 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10706 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10707 if(appData.roundSync) *syncInterval = gamesPerRound;
10709 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10711 if(appData.tourneyType == 0) {
10712 if(curPairing == (nPlayers-1)/2 ) {
10713 *whitePlayer = curRound;
10714 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10716 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10717 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10718 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10719 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10721 } else if(appData.tourneyType > 1) {
10722 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10723 *whitePlayer = curRound + appData.tourneyType;
10724 } else if(appData.tourneyType > 0) {
10725 *whitePlayer = curPairing;
10726 *blackPlayer = curRound + appData.tourneyType;
10729 // take care of white/black alternation per round.
10730 // For cycles and games this is already taken care of by default, derived from matchGame!
10731 return curRound & 1;
10735 NextTourneyGame (int nr, int *swapColors)
10736 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10738 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10740 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10741 tf = fopen(appData.tourneyFile, "r");
10742 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10743 ParseArgsFromFile(tf); fclose(tf);
10744 InitTimeControls(); // TC might be altered from tourney file
10746 nPlayers = CountPlayers(appData.participants); // count participants
10747 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10748 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10751 p = q = appData.results;
10752 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10753 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10754 DisplayMessage(_("Waiting for other game(s)"),"");
10755 waitingForGame = TRUE;
10756 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10759 waitingForGame = FALSE;
10762 if(appData.tourneyType < 0) {
10763 if(nr>=0 && !pairingReceived) {
10765 if(pairing.pr == NoProc) {
10766 if(!appData.pairingEngine[0]) {
10767 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10770 StartChessProgram(&pairing); // starts the pairing engine
10772 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10773 SendToProgram(buf, &pairing);
10774 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10775 SendToProgram(buf, &pairing);
10776 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10778 pairingReceived = 0; // ... so we continue here
10780 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10781 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10782 matchGame = 1; roundNr = nr / syncInterval + 1;
10785 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10787 // redefine engines, engine dir, etc.
10788 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10789 if(first.pr == NoProc) {
10790 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10791 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10793 if(second.pr == NoProc) {
10795 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10796 SwapEngines(1); // and make that valid for second engine by swapping
10797 InitEngine(&second, 1);
10799 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10800 UpdateLogos(FALSE); // leave display to ModeHiglight()
10806 { // performs game initialization that does not invoke engines, and then tries to start the game
10807 int res, firstWhite, swapColors = 0;
10808 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10809 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
10811 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10812 if(strcmp(buf, currentDebugFile)) { // name has changed
10813 FILE *f = fopen(buf, "w");
10814 if(f) { // if opening the new file failed, just keep using the old one
10815 ASSIGN(currentDebugFile, buf);
10819 if(appData.serverFileName) {
10820 if(serverFP) fclose(serverFP);
10821 serverFP = fopen(appData.serverFileName, "w");
10822 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10823 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10827 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10828 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10829 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10830 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10831 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10832 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10833 Reset(FALSE, first.pr != NoProc);
10834 res = LoadGameOrPosition(matchGame); // setup game
10835 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10836 if(!res) return; // abort when bad game/pos file
10837 TwoMachinesEvent();
10841 UserAdjudicationEvent (int result)
10843 ChessMove gameResult = GameIsDrawn;
10846 gameResult = WhiteWins;
10848 else if( result < 0 ) {
10849 gameResult = BlackWins;
10852 if( gameMode == TwoMachinesPlay ) {
10853 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10858 // [HGM] save: calculate checksum of game to make games easily identifiable
10860 StringCheckSum (char *s)
10863 if(s==NULL) return 0;
10864 while(*s) i = i*259 + *s++;
10872 for(i=backwardMostMove; i<forwardMostMove; i++) {
10873 sum += pvInfoList[i].depth;
10874 sum += StringCheckSum(parseList[i]);
10875 sum += StringCheckSum(commentList[i]);
10878 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10879 return sum + StringCheckSum(commentList[i]);
10880 } // end of save patch
10883 GameEnds (ChessMove result, char *resultDetails, int whosays)
10885 GameMode nextGameMode;
10887 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10889 if(endingGame) return; /* [HGM] crash: forbid recursion */
10891 if(twoBoards) { // [HGM] dual: switch back to one board
10892 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10893 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10895 if (appData.debugMode) {
10896 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10897 result, resultDetails ? resultDetails : "(null)", whosays);
10900 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10902 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10904 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10905 /* If we are playing on ICS, the server decides when the
10906 game is over, but the engine can offer to draw, claim
10910 if (appData.zippyPlay && first.initDone) {
10911 if (result == GameIsDrawn) {
10912 /* In case draw still needs to be claimed */
10913 SendToICS(ics_prefix);
10914 SendToICS("draw\n");
10915 } else if (StrCaseStr(resultDetails, "resign")) {
10916 SendToICS(ics_prefix);
10917 SendToICS("resign\n");
10921 endingGame = 0; /* [HGM] crash */
10925 /* If we're loading the game from a file, stop */
10926 if (whosays == GE_FILE) {
10927 (void) StopLoadGameTimer();
10931 /* Cancel draw offers */
10932 first.offeredDraw = second.offeredDraw = 0;
10934 /* If this is an ICS game, only ICS can really say it's done;
10935 if not, anyone can. */
10936 isIcsGame = (gameMode == IcsPlayingWhite ||
10937 gameMode == IcsPlayingBlack ||
10938 gameMode == IcsObserving ||
10939 gameMode == IcsExamining);
10941 if (!isIcsGame || whosays == GE_ICS) {
10942 /* OK -- not an ICS game, or ICS said it was done */
10944 if (!isIcsGame && !appData.noChessProgram)
10945 SetUserThinkingEnables();
10947 /* [HGM] if a machine claims the game end we verify this claim */
10948 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10949 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10951 ChessMove trueResult = (ChessMove) -1;
10953 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10954 first.twoMachinesColor[0] :
10955 second.twoMachinesColor[0] ;
10957 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10958 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10959 /* [HGM] verify: engine mate claims accepted if they were flagged */
10960 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10962 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10963 /* [HGM] verify: engine mate claims accepted if they were flagged */
10964 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10966 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10967 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10970 // now verify win claims, but not in drop games, as we don't understand those yet
10971 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10972 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10973 (result == WhiteWins && claimer == 'w' ||
10974 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10975 if (appData.debugMode) {
10976 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10977 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10979 if(result != trueResult) {
10980 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10981 result = claimer == 'w' ? BlackWins : WhiteWins;
10982 resultDetails = buf;
10985 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10986 && (forwardMostMove <= backwardMostMove ||
10987 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10988 (claimer=='b')==(forwardMostMove&1))
10990 /* [HGM] verify: draws that were not flagged are false claims */
10991 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10992 result = claimer == 'w' ? BlackWins : WhiteWins;
10993 resultDetails = buf;
10995 /* (Claiming a loss is accepted no questions asked!) */
10996 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10997 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10998 result = GameUnfinished;
10999 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11001 /* [HGM] bare: don't allow bare King to win */
11002 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11003 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11004 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11005 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11006 && result != GameIsDrawn)
11007 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11008 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11009 int p = (signed char)boards[forwardMostMove][i][j] - color;
11010 if(p >= 0 && p <= (int)WhiteKing) k++;
11012 if (appData.debugMode) {
11013 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11014 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11017 result = GameIsDrawn;
11018 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11019 resultDetails = buf;
11025 if(serverMoves != NULL && !loadFlag) { char c = '=';
11026 if(result==WhiteWins) c = '+';
11027 if(result==BlackWins) c = '-';
11028 if(resultDetails != NULL)
11029 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11031 if (resultDetails != NULL) {
11032 gameInfo.result = result;
11033 gameInfo.resultDetails = StrSave(resultDetails);
11035 /* display last move only if game was not loaded from file */
11036 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11037 DisplayMove(currentMove - 1);
11039 if (forwardMostMove != 0) {
11040 if (gameMode != PlayFromGameFile && gameMode != EditGame
11041 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11043 if (*appData.saveGameFile != NULLCHAR) {
11044 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11045 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11047 SaveGameToFile(appData.saveGameFile, TRUE);
11048 } else if (appData.autoSaveGames) {
11049 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11051 if (*appData.savePositionFile != NULLCHAR) {
11052 SavePositionToFile(appData.savePositionFile);
11054 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11058 /* Tell program how game ended in case it is learning */
11059 /* [HGM] Moved this to after saving the PGN, just in case */
11060 /* engine died and we got here through time loss. In that */
11061 /* case we will get a fatal error writing the pipe, which */
11062 /* would otherwise lose us the PGN. */
11063 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11064 /* output during GameEnds should never be fatal anymore */
11065 if (gameMode == MachinePlaysWhite ||
11066 gameMode == MachinePlaysBlack ||
11067 gameMode == TwoMachinesPlay ||
11068 gameMode == IcsPlayingWhite ||
11069 gameMode == IcsPlayingBlack ||
11070 gameMode == BeginningOfGame) {
11072 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11074 if (first.pr != NoProc) {
11075 SendToProgram(buf, &first);
11077 if (second.pr != NoProc &&
11078 gameMode == TwoMachinesPlay) {
11079 SendToProgram(buf, &second);
11084 if (appData.icsActive) {
11085 if (appData.quietPlay &&
11086 (gameMode == IcsPlayingWhite ||
11087 gameMode == IcsPlayingBlack)) {
11088 SendToICS(ics_prefix);
11089 SendToICS("set shout 1\n");
11091 nextGameMode = IcsIdle;
11092 ics_user_moved = FALSE;
11093 /* clean up premove. It's ugly when the game has ended and the
11094 * premove highlights are still on the board.
11097 gotPremove = FALSE;
11098 ClearPremoveHighlights();
11099 DrawPosition(FALSE, boards[currentMove]);
11101 if (whosays == GE_ICS) {
11104 if (gameMode == IcsPlayingWhite)
11106 else if(gameMode == IcsPlayingBlack)
11107 PlayIcsLossSound();
11110 if (gameMode == IcsPlayingBlack)
11112 else if(gameMode == IcsPlayingWhite)
11113 PlayIcsLossSound();
11116 PlayIcsDrawSound();
11119 PlayIcsUnfinishedSound();
11122 if(appData.quitNext) { ExitEvent(0); return; }
11123 } else if (gameMode == EditGame ||
11124 gameMode == PlayFromGameFile ||
11125 gameMode == AnalyzeMode ||
11126 gameMode == AnalyzeFile) {
11127 nextGameMode = gameMode;
11129 nextGameMode = EndOfGame;
11134 nextGameMode = gameMode;
11137 if (appData.noChessProgram) {
11138 gameMode = nextGameMode;
11140 endingGame = 0; /* [HGM] crash */
11145 /* Put first chess program into idle state */
11146 if (first.pr != NoProc &&
11147 (gameMode == MachinePlaysWhite ||
11148 gameMode == MachinePlaysBlack ||
11149 gameMode == TwoMachinesPlay ||
11150 gameMode == IcsPlayingWhite ||
11151 gameMode == IcsPlayingBlack ||
11152 gameMode == BeginningOfGame)) {
11153 SendToProgram("force\n", &first);
11154 if (first.usePing) {
11156 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11157 SendToProgram(buf, &first);
11160 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11161 /* Kill off first chess program */
11162 if (first.isr != NULL)
11163 RemoveInputSource(first.isr);
11166 if (first.pr != NoProc) {
11168 DoSleep( appData.delayBeforeQuit );
11169 SendToProgram("quit\n", &first);
11170 DoSleep( appData.delayAfterQuit );
11171 DestroyChildProcess(first.pr, first.useSigterm);
11172 first.reload = TRUE;
11176 if (second.reuse) {
11177 /* Put second chess program into idle state */
11178 if (second.pr != NoProc &&
11179 gameMode == TwoMachinesPlay) {
11180 SendToProgram("force\n", &second);
11181 if (second.usePing) {
11183 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11184 SendToProgram(buf, &second);
11187 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11188 /* Kill off second chess program */
11189 if (second.isr != NULL)
11190 RemoveInputSource(second.isr);
11193 if (second.pr != NoProc) {
11194 DoSleep( appData.delayBeforeQuit );
11195 SendToProgram("quit\n", &second);
11196 DoSleep( appData.delayAfterQuit );
11197 DestroyChildProcess(second.pr, second.useSigterm);
11198 second.reload = TRUE;
11200 second.pr = NoProc;
11203 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11204 char resChar = '=';
11208 if (first.twoMachinesColor[0] == 'w') {
11211 second.matchWins++;
11216 if (first.twoMachinesColor[0] == 'b') {
11219 second.matchWins++;
11222 case GameUnfinished:
11228 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11229 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11230 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11231 ReserveGame(nextGame, resChar); // sets nextGame
11232 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11233 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11234 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11236 if (nextGame <= appData.matchGames && !abortMatch) {
11237 gameMode = nextGameMode;
11238 matchGame = nextGame; // this will be overruled in tourney mode!
11239 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11240 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11241 endingGame = 0; /* [HGM] crash */
11244 gameMode = nextGameMode;
11245 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11246 first.tidy, second.tidy,
11247 first.matchWins, second.matchWins,
11248 appData.matchGames - (first.matchWins + second.matchWins));
11249 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11250 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11251 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11252 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11253 first.twoMachinesColor = "black\n";
11254 second.twoMachinesColor = "white\n";
11256 first.twoMachinesColor = "white\n";
11257 second.twoMachinesColor = "black\n";
11261 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11262 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11264 gameMode = nextGameMode;
11266 endingGame = 0; /* [HGM] crash */
11267 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11268 if(matchMode == TRUE) { // match through command line: exit with or without popup
11270 ToNrEvent(forwardMostMove);
11271 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11273 } else DisplayFatalError(buf, 0, 0);
11274 } else { // match through menu; just stop, with or without popup
11275 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11278 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11279 } else DisplayNote(buf);
11281 if(ranking) free(ranking);
11285 /* Assumes program was just initialized (initString sent).
11286 Leaves program in force mode. */
11288 FeedMovesToProgram (ChessProgramState *cps, int upto)
11292 if (appData.debugMode)
11293 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11294 startedFromSetupPosition ? "position and " : "",
11295 backwardMostMove, upto, cps->which);
11296 if(currentlyInitializedVariant != gameInfo.variant) {
11298 // [HGM] variantswitch: make engine aware of new variant
11299 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11300 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11301 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11302 SendToProgram(buf, cps);
11303 currentlyInitializedVariant = gameInfo.variant;
11305 SendToProgram("force\n", cps);
11306 if (startedFromSetupPosition) {
11307 SendBoard(cps, backwardMostMove);
11308 if (appData.debugMode) {
11309 fprintf(debugFP, "feedMoves\n");
11312 for (i = backwardMostMove; i < upto; i++) {
11313 SendMoveToProgram(i, cps);
11319 ResurrectChessProgram ()
11321 /* The chess program may have exited.
11322 If so, restart it and feed it all the moves made so far. */
11323 static int doInit = 0;
11325 if (appData.noChessProgram) return 1;
11327 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11328 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11329 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11330 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11332 if (first.pr != NoProc) return 1;
11333 StartChessProgram(&first);
11335 InitChessProgram(&first, FALSE);
11336 FeedMovesToProgram(&first, currentMove);
11338 if (!first.sendTime) {
11339 /* can't tell gnuchess what its clock should read,
11340 so we bow to its notion. */
11342 timeRemaining[0][currentMove] = whiteTimeRemaining;
11343 timeRemaining[1][currentMove] = blackTimeRemaining;
11346 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11347 appData.icsEngineAnalyze) && first.analysisSupport) {
11348 SendToProgram("analyze\n", &first);
11349 first.analyzing = TRUE;
11355 * Button procedures
11358 Reset (int redraw, int init)
11362 if (appData.debugMode) {
11363 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11364 redraw, init, gameMode);
11366 CleanupTail(); // [HGM] vari: delete any stored variations
11367 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11368 pausing = pauseExamInvalid = FALSE;
11369 startedFromSetupPosition = blackPlaysFirst = FALSE;
11371 whiteFlag = blackFlag = FALSE;
11372 userOfferedDraw = FALSE;
11373 hintRequested = bookRequested = FALSE;
11374 first.maybeThinking = FALSE;
11375 second.maybeThinking = FALSE;
11376 first.bookSuspend = FALSE; // [HGM] book
11377 second.bookSuspend = FALSE;
11378 thinkOutput[0] = NULLCHAR;
11379 lastHint[0] = NULLCHAR;
11380 ClearGameInfo(&gameInfo);
11381 gameInfo.variant = StringToVariant(appData.variant);
11382 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11383 ics_user_moved = ics_clock_paused = FALSE;
11384 ics_getting_history = H_FALSE;
11386 white_holding[0] = black_holding[0] = NULLCHAR;
11387 ClearProgramStats();
11388 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11392 flipView = appData.flipView;
11393 ClearPremoveHighlights();
11394 gotPremove = FALSE;
11395 alarmSounded = FALSE;
11397 GameEnds(EndOfFile, NULL, GE_PLAYER);
11398 if(appData.serverMovesName != NULL) {
11399 /* [HGM] prepare to make moves file for broadcasting */
11400 clock_t t = clock();
11401 if(serverMoves != NULL) fclose(serverMoves);
11402 serverMoves = fopen(appData.serverMovesName, "r");
11403 if(serverMoves != NULL) {
11404 fclose(serverMoves);
11405 /* delay 15 sec before overwriting, so all clients can see end */
11406 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11408 serverMoves = fopen(appData.serverMovesName, "w");
11412 gameMode = BeginningOfGame;
11414 if(appData.icsActive) gameInfo.variant = VariantNormal;
11415 currentMove = forwardMostMove = backwardMostMove = 0;
11416 MarkTargetSquares(1);
11417 InitPosition(redraw);
11418 for (i = 0; i < MAX_MOVES; i++) {
11419 if (commentList[i] != NULL) {
11420 free(commentList[i]);
11421 commentList[i] = NULL;
11425 timeRemaining[0][0] = whiteTimeRemaining;
11426 timeRemaining[1][0] = blackTimeRemaining;
11428 if (first.pr == NoProc) {
11429 StartChessProgram(&first);
11432 InitChessProgram(&first, startedFromSetupPosition);
11435 DisplayMessage("", "");
11436 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11437 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11438 ClearMap(); // [HGM] exclude: invalidate map
11442 AutoPlayGameLoop ()
11445 if (!AutoPlayOneMove())
11447 if (matchMode || appData.timeDelay == 0)
11449 if (appData.timeDelay < 0)
11451 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11459 ReloadGame(1); // next game
11465 int fromX, fromY, toX, toY;
11467 if (appData.debugMode) {
11468 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11471 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11474 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11475 pvInfoList[currentMove].depth = programStats.depth;
11476 pvInfoList[currentMove].score = programStats.score;
11477 pvInfoList[currentMove].time = 0;
11478 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11479 else { // append analysis of final position as comment
11481 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11482 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11484 programStats.depth = 0;
11487 if (currentMove >= forwardMostMove) {
11488 if(gameMode == AnalyzeFile) {
11489 if(appData.loadGameIndex == -1) {
11490 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11491 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11493 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11496 // gameMode = EndOfGame;
11497 // ModeHighlight();
11499 /* [AS] Clear current move marker at the end of a game */
11500 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11505 toX = moveList[currentMove][2] - AAA;
11506 toY = moveList[currentMove][3] - ONE;
11508 if (moveList[currentMove][1] == '@') {
11509 if (appData.highlightLastMove) {
11510 SetHighlights(-1, -1, toX, toY);
11513 fromX = moveList[currentMove][0] - AAA;
11514 fromY = moveList[currentMove][1] - ONE;
11516 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11518 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11520 if (appData.highlightLastMove) {
11521 SetHighlights(fromX, fromY, toX, toY);
11524 DisplayMove(currentMove);
11525 SendMoveToProgram(currentMove++, &first);
11526 DisplayBothClocks();
11527 DrawPosition(FALSE, boards[currentMove]);
11528 // [HGM] PV info: always display, routine tests if empty
11529 DisplayComment(currentMove - 1, commentList[currentMove]);
11535 LoadGameOneMove (ChessMove readAhead)
11537 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11538 char promoChar = NULLCHAR;
11539 ChessMove moveType;
11540 char move[MSG_SIZ];
11543 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11544 gameMode != AnalyzeMode && gameMode != Training) {
11549 yyboardindex = forwardMostMove;
11550 if (readAhead != EndOfFile) {
11551 moveType = readAhead;
11553 if (gameFileFP == NULL)
11555 moveType = (ChessMove) Myylex();
11559 switch (moveType) {
11561 if (appData.debugMode)
11562 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11565 /* append the comment but don't display it */
11566 AppendComment(currentMove, p, FALSE);
11569 case WhiteCapturesEnPassant:
11570 case BlackCapturesEnPassant:
11571 case WhitePromotion:
11572 case BlackPromotion:
11573 case WhiteNonPromotion:
11574 case BlackNonPromotion:
11576 case WhiteKingSideCastle:
11577 case WhiteQueenSideCastle:
11578 case BlackKingSideCastle:
11579 case BlackQueenSideCastle:
11580 case WhiteKingSideCastleWild:
11581 case WhiteQueenSideCastleWild:
11582 case BlackKingSideCastleWild:
11583 case BlackQueenSideCastleWild:
11585 case WhiteHSideCastleFR:
11586 case WhiteASideCastleFR:
11587 case BlackHSideCastleFR:
11588 case BlackASideCastleFR:
11590 if (appData.debugMode)
11591 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11592 fromX = currentMoveString[0] - AAA;
11593 fromY = currentMoveString[1] - ONE;
11594 toX = currentMoveString[2] - AAA;
11595 toY = currentMoveString[3] - ONE;
11596 promoChar = currentMoveString[4];
11601 if (appData.debugMode)
11602 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11603 fromX = moveType == WhiteDrop ?
11604 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11605 (int) CharToPiece(ToLower(currentMoveString[0]));
11607 toX = currentMoveString[2] - AAA;
11608 toY = currentMoveString[3] - ONE;
11614 case GameUnfinished:
11615 if (appData.debugMode)
11616 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11617 p = strchr(yy_text, '{');
11618 if (p == NULL) p = strchr(yy_text, '(');
11621 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11623 q = strchr(p, *p == '{' ? '}' : ')');
11624 if (q != NULL) *q = NULLCHAR;
11627 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11628 GameEnds(moveType, p, GE_FILE);
11630 if (cmailMsgLoaded) {
11632 flipView = WhiteOnMove(currentMove);
11633 if (moveType == GameUnfinished) flipView = !flipView;
11634 if (appData.debugMode)
11635 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11640 if (appData.debugMode)
11641 fprintf(debugFP, "Parser hit end of file\n");
11642 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11648 if (WhiteOnMove(currentMove)) {
11649 GameEnds(BlackWins, "Black mates", GE_FILE);
11651 GameEnds(WhiteWins, "White mates", GE_FILE);
11655 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11661 case MoveNumberOne:
11662 if (lastLoadGameStart == GNUChessGame) {
11663 /* GNUChessGames have numbers, but they aren't move numbers */
11664 if (appData.debugMode)
11665 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11666 yy_text, (int) moveType);
11667 return LoadGameOneMove(EndOfFile); /* tail recursion */
11669 /* else fall thru */
11674 /* Reached start of next game in file */
11675 if (appData.debugMode)
11676 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11677 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11683 if (WhiteOnMove(currentMove)) {
11684 GameEnds(BlackWins, "Black mates", GE_FILE);
11686 GameEnds(WhiteWins, "White mates", GE_FILE);
11690 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11696 case PositionDiagram: /* should not happen; ignore */
11697 case ElapsedTime: /* ignore */
11698 case NAG: /* ignore */
11699 if (appData.debugMode)
11700 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11701 yy_text, (int) moveType);
11702 return LoadGameOneMove(EndOfFile); /* tail recursion */
11705 if (appData.testLegality) {
11706 if (appData.debugMode)
11707 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11708 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11709 (forwardMostMove / 2) + 1,
11710 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11711 DisplayError(move, 0);
11714 if (appData.debugMode)
11715 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11716 yy_text, currentMoveString);
11717 fromX = currentMoveString[0] - AAA;
11718 fromY = currentMoveString[1] - ONE;
11719 toX = currentMoveString[2] - AAA;
11720 toY = currentMoveString[3] - ONE;
11721 promoChar = currentMoveString[4];
11725 case AmbiguousMove:
11726 if (appData.debugMode)
11727 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11728 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11729 (forwardMostMove / 2) + 1,
11730 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11731 DisplayError(move, 0);
11736 case ImpossibleMove:
11737 if (appData.debugMode)
11738 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11739 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11740 (forwardMostMove / 2) + 1,
11741 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11742 DisplayError(move, 0);
11748 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11749 DrawPosition(FALSE, boards[currentMove]);
11750 DisplayBothClocks();
11751 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11752 DisplayComment(currentMove - 1, commentList[currentMove]);
11754 (void) StopLoadGameTimer();
11756 cmailOldMove = forwardMostMove;
11759 /* currentMoveString is set as a side-effect of yylex */
11761 thinkOutput[0] = NULLCHAR;
11762 MakeMove(fromX, fromY, toX, toY, promoChar);
11763 currentMove = forwardMostMove;
11768 /* Load the nth game from the given file */
11770 LoadGameFromFile (char *filename, int n, char *title, int useList)
11775 if (strcmp(filename, "-") == 0) {
11779 f = fopen(filename, "rb");
11781 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11782 DisplayError(buf, errno);
11786 if (fseek(f, 0, 0) == -1) {
11787 /* f is not seekable; probably a pipe */
11790 if (useList && n == 0) {
11791 int error = GameListBuild(f);
11793 DisplayError(_("Cannot build game list"), error);
11794 } else if (!ListEmpty(&gameList) &&
11795 ((ListGame *) gameList.tailPred)->number > 1) {
11796 GameListPopUp(f, title);
11803 return LoadGame(f, n, title, FALSE);
11808 MakeRegisteredMove ()
11810 int fromX, fromY, toX, toY;
11812 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11813 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11816 if (appData.debugMode)
11817 fprintf(debugFP, "Restoring %s for game %d\n",
11818 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11820 thinkOutput[0] = NULLCHAR;
11821 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11822 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11823 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11824 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11825 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11826 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11827 MakeMove(fromX, fromY, toX, toY, promoChar);
11828 ShowMove(fromX, fromY, toX, toY);
11830 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11837 if (WhiteOnMove(currentMove)) {
11838 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11840 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11845 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11852 if (WhiteOnMove(currentMove)) {
11853 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11855 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11860 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11871 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11873 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11877 if (gameNumber > nCmailGames) {
11878 DisplayError(_("No more games in this message"), 0);
11881 if (f == lastLoadGameFP) {
11882 int offset = gameNumber - lastLoadGameNumber;
11884 cmailMsg[0] = NULLCHAR;
11885 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11886 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11887 nCmailMovesRegistered--;
11889 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11890 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11891 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11894 if (! RegisterMove()) return FALSE;
11898 retVal = LoadGame(f, gameNumber, title, useList);
11900 /* Make move registered during previous look at this game, if any */
11901 MakeRegisteredMove();
11903 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11904 commentList[currentMove]
11905 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11906 DisplayComment(currentMove - 1, commentList[currentMove]);
11912 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11914 ReloadGame (int offset)
11916 int gameNumber = lastLoadGameNumber + offset;
11917 if (lastLoadGameFP == NULL) {
11918 DisplayError(_("No game has been loaded yet"), 0);
11921 if (gameNumber <= 0) {
11922 DisplayError(_("Can't back up any further"), 0);
11925 if (cmailMsgLoaded) {
11926 return CmailLoadGame(lastLoadGameFP, gameNumber,
11927 lastLoadGameTitle, lastLoadGameUseList);
11929 return LoadGame(lastLoadGameFP, gameNumber,
11930 lastLoadGameTitle, lastLoadGameUseList);
11934 int keys[EmptySquare+1];
11937 PositionMatches (Board b1, Board b2)
11940 switch(appData.searchMode) {
11941 case 1: return CompareWithRights(b1, b2);
11943 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11944 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11948 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11949 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11950 sum += keys[b1[r][f]] - keys[b2[r][f]];
11954 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11955 sum += keys[b1[r][f]] - keys[b2[r][f]];
11967 int pieceList[256], quickBoard[256];
11968 ChessSquare pieceType[256] = { EmptySquare };
11969 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11970 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11971 int soughtTotal, turn;
11972 Boolean epOK, flipSearch;
11975 unsigned char piece, to;
11978 #define DSIZE (250000)
11980 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11981 Move *moveDatabase = initialSpace;
11982 unsigned int movePtr, dataSize = DSIZE;
11985 MakePieceList (Board board, int *counts)
11987 int r, f, n=Q_PROMO, total=0;
11988 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11989 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11990 int sq = f + (r<<4);
11991 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11992 quickBoard[sq] = ++n;
11994 pieceType[n] = board[r][f];
11995 counts[board[r][f]]++;
11996 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11997 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12001 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12006 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12008 int sq = fromX + (fromY<<4);
12009 int piece = quickBoard[sq];
12010 quickBoard[sq] = 0;
12011 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12012 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12013 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12014 moveDatabase[movePtr++].piece = Q_WCASTL;
12015 quickBoard[sq] = piece;
12016 piece = quickBoard[from]; quickBoard[from] = 0;
12017 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12019 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12020 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12021 moveDatabase[movePtr++].piece = Q_BCASTL;
12022 quickBoard[sq] = piece;
12023 piece = quickBoard[from]; quickBoard[from] = 0;
12024 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12026 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12027 quickBoard[(fromY<<4)+toX] = 0;
12028 moveDatabase[movePtr].piece = Q_EP;
12029 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12030 moveDatabase[movePtr].to = sq;
12032 if(promoPiece != pieceType[piece]) {
12033 moveDatabase[movePtr++].piece = Q_PROMO;
12034 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12036 moveDatabase[movePtr].piece = piece;
12037 quickBoard[sq] = piece;
12042 PackGame (Board board)
12044 Move *newSpace = NULL;
12045 moveDatabase[movePtr].piece = 0; // terminate previous game
12046 if(movePtr > dataSize) {
12047 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12048 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12049 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12052 Move *p = moveDatabase, *q = newSpace;
12053 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12054 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12055 moveDatabase = newSpace;
12056 } else { // calloc failed, we must be out of memory. Too bad...
12057 dataSize = 0; // prevent calloc events for all subsequent games
12058 return 0; // and signal this one isn't cached
12062 MakePieceList(board, counts);
12067 QuickCompare (Board board, int *minCounts, int *maxCounts)
12068 { // compare according to search mode
12070 switch(appData.searchMode)
12072 case 1: // exact position match
12073 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12074 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12075 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12078 case 2: // can have extra material on empty squares
12079 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12080 if(board[r][f] == EmptySquare) continue;
12081 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12084 case 3: // material with exact Pawn structure
12085 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12086 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12087 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12088 } // fall through to material comparison
12089 case 4: // exact material
12090 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12092 case 6: // material range with given imbalance
12093 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12094 // fall through to range comparison
12095 case 5: // material range
12096 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12102 QuickScan (Board board, Move *move)
12103 { // reconstruct game,and compare all positions in it
12104 int cnt=0, stretch=0, total = MakePieceList(board, counts);
12106 int piece = move->piece;
12107 int to = move->to, from = pieceList[piece];
12108 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12109 if(!piece) return -1;
12110 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12111 piece = (++move)->piece;
12112 from = pieceList[piece];
12113 counts[pieceType[piece]]--;
12114 pieceType[piece] = (ChessSquare) move->to;
12115 counts[move->to]++;
12116 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12117 counts[pieceType[quickBoard[to]]]--;
12118 quickBoard[to] = 0; total--;
12121 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12122 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12123 from = pieceList[piece]; // so this must be King
12124 quickBoard[from] = 0;
12125 pieceList[piece] = to;
12126 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12127 quickBoard[from] = 0; // rook
12128 quickBoard[to] = piece;
12129 to = move->to; piece = move->piece;
12133 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12134 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12135 quickBoard[from] = 0;
12137 quickBoard[to] = piece;
12138 pieceList[piece] = to;
12140 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12141 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12142 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12143 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12145 static int lastCounts[EmptySquare+1];
12147 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12148 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12149 } else stretch = 0;
12150 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12159 flipSearch = FALSE;
12160 CopyBoard(soughtBoard, boards[currentMove]);
12161 soughtTotal = MakePieceList(soughtBoard, maxSought);
12162 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12163 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12164 CopyBoard(reverseBoard, boards[currentMove]);
12165 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12166 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12167 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12168 reverseBoard[r][f] = piece;
12170 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12171 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12172 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12173 || (boards[currentMove][CASTLING][2] == NoRights ||
12174 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12175 && (boards[currentMove][CASTLING][5] == NoRights ||
12176 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12179 CopyBoard(flipBoard, soughtBoard);
12180 CopyBoard(rotateBoard, reverseBoard);
12181 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12182 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12183 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12186 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12187 if(appData.searchMode >= 5) {
12188 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12189 MakePieceList(soughtBoard, minSought);
12190 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12192 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12193 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12196 GameInfo dummyInfo;
12197 static int creatingBook;
12200 GameContainsPosition (FILE *f, ListGame *lg)
12202 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12203 int fromX, fromY, toX, toY;
12205 static int initDone=FALSE;
12207 // weed out games based on numerical tag comparison
12208 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12209 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12210 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12211 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12213 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12216 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12217 else CopyBoard(boards[scratch], initialPosition); // default start position
12220 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12221 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12224 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12225 fseek(f, lg->offset, 0);
12228 yyboardindex = scratch;
12229 quickFlag = plyNr+1;
12234 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12240 if(plyNr) return -1; // after we have seen moves, this is for new game
12243 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12244 case ImpossibleMove:
12245 case WhiteWins: // game ends here with these four
12248 case GameUnfinished:
12252 if(appData.testLegality) return -1;
12253 case WhiteCapturesEnPassant:
12254 case BlackCapturesEnPassant:
12255 case WhitePromotion:
12256 case BlackPromotion:
12257 case WhiteNonPromotion:
12258 case BlackNonPromotion:
12260 case WhiteKingSideCastle:
12261 case WhiteQueenSideCastle:
12262 case BlackKingSideCastle:
12263 case BlackQueenSideCastle:
12264 case WhiteKingSideCastleWild:
12265 case WhiteQueenSideCastleWild:
12266 case BlackKingSideCastleWild:
12267 case BlackQueenSideCastleWild:
12268 case WhiteHSideCastleFR:
12269 case WhiteASideCastleFR:
12270 case BlackHSideCastleFR:
12271 case BlackASideCastleFR:
12272 fromX = currentMoveString[0] - AAA;
12273 fromY = currentMoveString[1] - ONE;
12274 toX = currentMoveString[2] - AAA;
12275 toY = currentMoveString[3] - ONE;
12276 promoChar = currentMoveString[4];
12280 fromX = next == WhiteDrop ?
12281 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12282 (int) CharToPiece(ToLower(currentMoveString[0]));
12284 toX = currentMoveString[2] - AAA;
12285 toY = currentMoveString[3] - ONE;
12289 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12291 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12292 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12293 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12294 if(appData.findMirror) {
12295 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12296 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12301 /* Load the nth game from open file f */
12303 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12307 int gn = gameNumber;
12308 ListGame *lg = NULL;
12309 int numPGNTags = 0;
12311 GameMode oldGameMode;
12312 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12314 if (appData.debugMode)
12315 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12317 if (gameMode == Training )
12318 SetTrainingModeOff();
12320 oldGameMode = gameMode;
12321 if (gameMode != BeginningOfGame) {
12322 Reset(FALSE, TRUE);
12326 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12327 fclose(lastLoadGameFP);
12331 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12334 fseek(f, lg->offset, 0);
12335 GameListHighlight(gameNumber);
12336 pos = lg->position;
12340 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12341 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12343 DisplayError(_("Game number out of range"), 0);
12348 if (fseek(f, 0, 0) == -1) {
12349 if (f == lastLoadGameFP ?
12350 gameNumber == lastLoadGameNumber + 1 :
12354 DisplayError(_("Can't seek on game file"), 0);
12359 lastLoadGameFP = f;
12360 lastLoadGameNumber = gameNumber;
12361 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12362 lastLoadGameUseList = useList;
12366 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12367 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12368 lg->gameInfo.black);
12370 } else if (*title != NULLCHAR) {
12371 if (gameNumber > 1) {
12372 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12375 DisplayTitle(title);
12379 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12380 gameMode = PlayFromGameFile;
12384 currentMove = forwardMostMove = backwardMostMove = 0;
12385 CopyBoard(boards[0], initialPosition);
12389 * Skip the first gn-1 games in the file.
12390 * Also skip over anything that precedes an identifiable
12391 * start of game marker, to avoid being confused by
12392 * garbage at the start of the file. Currently
12393 * recognized start of game markers are the move number "1",
12394 * the pattern "gnuchess .* game", the pattern
12395 * "^[#;%] [^ ]* game file", and a PGN tag block.
12396 * A game that starts with one of the latter two patterns
12397 * will also have a move number 1, possibly
12398 * following a position diagram.
12399 * 5-4-02: Let's try being more lenient and allowing a game to
12400 * start with an unnumbered move. Does that break anything?
12402 cm = lastLoadGameStart = EndOfFile;
12404 yyboardindex = forwardMostMove;
12405 cm = (ChessMove) Myylex();
12408 if (cmailMsgLoaded) {
12409 nCmailGames = CMAIL_MAX_GAMES - gn;
12412 DisplayError(_("Game not found in file"), 0);
12419 lastLoadGameStart = cm;
12422 case MoveNumberOne:
12423 switch (lastLoadGameStart) {
12428 case MoveNumberOne:
12430 gn--; /* count this game */
12431 lastLoadGameStart = cm;
12440 switch (lastLoadGameStart) {
12443 case MoveNumberOne:
12445 gn--; /* count this game */
12446 lastLoadGameStart = cm;
12449 lastLoadGameStart = cm; /* game counted already */
12457 yyboardindex = forwardMostMove;
12458 cm = (ChessMove) Myylex();
12459 } while (cm == PGNTag || cm == Comment);
12466 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12467 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12468 != CMAIL_OLD_RESULT) {
12470 cmailResult[ CMAIL_MAX_GAMES
12471 - gn - 1] = CMAIL_OLD_RESULT;
12477 /* Only a NormalMove can be at the start of a game
12478 * without a position diagram. */
12479 if (lastLoadGameStart == EndOfFile ) {
12481 lastLoadGameStart = MoveNumberOne;
12490 if (appData.debugMode)
12491 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12493 if (cm == XBoardGame) {
12494 /* Skip any header junk before position diagram and/or move 1 */
12496 yyboardindex = forwardMostMove;
12497 cm = (ChessMove) Myylex();
12499 if (cm == EndOfFile ||
12500 cm == GNUChessGame || cm == XBoardGame) {
12501 /* Empty game; pretend end-of-file and handle later */
12506 if (cm == MoveNumberOne || cm == PositionDiagram ||
12507 cm == PGNTag || cm == Comment)
12510 } else if (cm == GNUChessGame) {
12511 if (gameInfo.event != NULL) {
12512 free(gameInfo.event);
12514 gameInfo.event = StrSave(yy_text);
12517 startedFromSetupPosition = FALSE;
12518 while (cm == PGNTag) {
12519 if (appData.debugMode)
12520 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12521 err = ParsePGNTag(yy_text, &gameInfo);
12522 if (!err) numPGNTags++;
12524 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12525 if(gameInfo.variant != oldVariant) {
12526 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12527 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12528 InitPosition(TRUE);
12529 oldVariant = gameInfo.variant;
12530 if (appData.debugMode)
12531 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12535 if (gameInfo.fen != NULL) {
12536 Board initial_position;
12537 startedFromSetupPosition = TRUE;
12538 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12540 DisplayError(_("Bad FEN position in file"), 0);
12543 CopyBoard(boards[0], initial_position);
12544 if (blackPlaysFirst) {
12545 currentMove = forwardMostMove = backwardMostMove = 1;
12546 CopyBoard(boards[1], initial_position);
12547 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12548 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12549 timeRemaining[0][1] = whiteTimeRemaining;
12550 timeRemaining[1][1] = blackTimeRemaining;
12551 if (commentList[0] != NULL) {
12552 commentList[1] = commentList[0];
12553 commentList[0] = NULL;
12556 currentMove = forwardMostMove = backwardMostMove = 0;
12558 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12560 initialRulePlies = FENrulePlies;
12561 for( i=0; i< nrCastlingRights; i++ )
12562 initialRights[i] = initial_position[CASTLING][i];
12564 yyboardindex = forwardMostMove;
12565 free(gameInfo.fen);
12566 gameInfo.fen = NULL;
12569 yyboardindex = forwardMostMove;
12570 cm = (ChessMove) Myylex();
12572 /* Handle comments interspersed among the tags */
12573 while (cm == Comment) {
12575 if (appData.debugMode)
12576 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12578 AppendComment(currentMove, p, FALSE);
12579 yyboardindex = forwardMostMove;
12580 cm = (ChessMove) Myylex();
12584 /* don't rely on existence of Event tag since if game was
12585 * pasted from clipboard the Event tag may not exist
12587 if (numPGNTags > 0){
12589 if (gameInfo.variant == VariantNormal) {
12590 VariantClass v = StringToVariant(gameInfo.event);
12591 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12592 if(v < VariantShogi) gameInfo.variant = v;
12595 if( appData.autoDisplayTags ) {
12596 tags = PGNTags(&gameInfo);
12597 TagsPopUp(tags, CmailMsg());
12602 /* Make something up, but don't display it now */
12607 if (cm == PositionDiagram) {
12610 Board initial_position;
12612 if (appData.debugMode)
12613 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12615 if (!startedFromSetupPosition) {
12617 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12618 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12629 initial_position[i][j++] = CharToPiece(*p);
12632 while (*p == ' ' || *p == '\t' ||
12633 *p == '\n' || *p == '\r') p++;
12635 if (strncmp(p, "black", strlen("black"))==0)
12636 blackPlaysFirst = TRUE;
12638 blackPlaysFirst = FALSE;
12639 startedFromSetupPosition = TRUE;
12641 CopyBoard(boards[0], initial_position);
12642 if (blackPlaysFirst) {
12643 currentMove = forwardMostMove = backwardMostMove = 1;
12644 CopyBoard(boards[1], initial_position);
12645 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12646 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12647 timeRemaining[0][1] = whiteTimeRemaining;
12648 timeRemaining[1][1] = blackTimeRemaining;
12649 if (commentList[0] != NULL) {
12650 commentList[1] = commentList[0];
12651 commentList[0] = NULL;
12654 currentMove = forwardMostMove = backwardMostMove = 0;
12657 yyboardindex = forwardMostMove;
12658 cm = (ChessMove) Myylex();
12661 if(!creatingBook) {
12662 if (first.pr == NoProc) {
12663 StartChessProgram(&first);
12665 InitChessProgram(&first, FALSE);
12666 SendToProgram("force\n", &first);
12667 if (startedFromSetupPosition) {
12668 SendBoard(&first, forwardMostMove);
12669 if (appData.debugMode) {
12670 fprintf(debugFP, "Load Game\n");
12672 DisplayBothClocks();
12676 /* [HGM] server: flag to write setup moves in broadcast file as one */
12677 loadFlag = appData.suppressLoadMoves;
12679 while (cm == Comment) {
12681 if (appData.debugMode)
12682 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12684 AppendComment(currentMove, p, FALSE);
12685 yyboardindex = forwardMostMove;
12686 cm = (ChessMove) Myylex();
12689 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12690 cm == WhiteWins || cm == BlackWins ||
12691 cm == GameIsDrawn || cm == GameUnfinished) {
12692 DisplayMessage("", _("No moves in game"));
12693 if (cmailMsgLoaded) {
12694 if (appData.debugMode)
12695 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12699 DrawPosition(FALSE, boards[currentMove]);
12700 DisplayBothClocks();
12701 gameMode = EditGame;
12708 // [HGM] PV info: routine tests if comment empty
12709 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12710 DisplayComment(currentMove - 1, commentList[currentMove]);
12712 if (!matchMode && appData.timeDelay != 0)
12713 DrawPosition(FALSE, boards[currentMove]);
12715 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12716 programStats.ok_to_send = 1;
12719 /* if the first token after the PGN tags is a move
12720 * and not move number 1, retrieve it from the parser
12722 if (cm != MoveNumberOne)
12723 LoadGameOneMove(cm);
12725 /* load the remaining moves from the file */
12726 while (LoadGameOneMove(EndOfFile)) {
12727 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12728 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12731 /* rewind to the start of the game */
12732 currentMove = backwardMostMove;
12734 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12736 if (oldGameMode == AnalyzeFile) {
12737 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12738 AnalyzeFileEvent();
12740 if (oldGameMode == AnalyzeMode) {
12741 AnalyzeFileEvent();
12744 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12745 long int w, b; // [HGM] adjourn: restore saved clock times
12746 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12747 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12748 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12749 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12753 if(creatingBook) return TRUE;
12754 if (!matchMode && pos > 0) {
12755 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12757 if (matchMode || appData.timeDelay == 0) {
12759 } else if (appData.timeDelay > 0) {
12760 AutoPlayGameLoop();
12763 if (appData.debugMode)
12764 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12766 loadFlag = 0; /* [HGM] true game starts */
12770 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12772 ReloadPosition (int offset)
12774 int positionNumber = lastLoadPositionNumber + offset;
12775 if (lastLoadPositionFP == NULL) {
12776 DisplayError(_("No position has been loaded yet"), 0);
12779 if (positionNumber <= 0) {
12780 DisplayError(_("Can't back up any further"), 0);
12783 return LoadPosition(lastLoadPositionFP, positionNumber,
12784 lastLoadPositionTitle);
12787 /* Load the nth position from the given file */
12789 LoadPositionFromFile (char *filename, int n, char *title)
12794 if (strcmp(filename, "-") == 0) {
12795 return LoadPosition(stdin, n, "stdin");
12797 f = fopen(filename, "rb");
12799 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12800 DisplayError(buf, errno);
12803 return LoadPosition(f, n, title);
12808 /* Load the nth position from the given open file, and close it */
12810 LoadPosition (FILE *f, int positionNumber, char *title)
12812 char *p, line[MSG_SIZ];
12813 Board initial_position;
12814 int i, j, fenMode, pn;
12816 if (gameMode == Training )
12817 SetTrainingModeOff();
12819 if (gameMode != BeginningOfGame) {
12820 Reset(FALSE, TRUE);
12822 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12823 fclose(lastLoadPositionFP);
12825 if (positionNumber == 0) positionNumber = 1;
12826 lastLoadPositionFP = f;
12827 lastLoadPositionNumber = positionNumber;
12828 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12829 if (first.pr == NoProc && !appData.noChessProgram) {
12830 StartChessProgram(&first);
12831 InitChessProgram(&first, FALSE);
12833 pn = positionNumber;
12834 if (positionNumber < 0) {
12835 /* Negative position number means to seek to that byte offset */
12836 if (fseek(f, -positionNumber, 0) == -1) {
12837 DisplayError(_("Can't seek on position file"), 0);
12842 if (fseek(f, 0, 0) == -1) {
12843 if (f == lastLoadPositionFP ?
12844 positionNumber == lastLoadPositionNumber + 1 :
12845 positionNumber == 1) {
12848 DisplayError(_("Can't seek on position file"), 0);
12853 /* See if this file is FEN or old-style xboard */
12854 if (fgets(line, MSG_SIZ, f) == NULL) {
12855 DisplayError(_("Position not found in file"), 0);
12858 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12859 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12862 if (fenMode || line[0] == '#') pn--;
12864 /* skip positions before number pn */
12865 if (fgets(line, MSG_SIZ, f) == NULL) {
12867 DisplayError(_("Position not found in file"), 0);
12870 if (fenMode || line[0] == '#') pn--;
12875 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12876 DisplayError(_("Bad FEN position in file"), 0);
12880 (void) fgets(line, MSG_SIZ, f);
12881 (void) fgets(line, MSG_SIZ, f);
12883 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12884 (void) fgets(line, MSG_SIZ, f);
12885 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12888 initial_position[i][j++] = CharToPiece(*p);
12892 blackPlaysFirst = FALSE;
12894 (void) fgets(line, MSG_SIZ, f);
12895 if (strncmp(line, "black", strlen("black"))==0)
12896 blackPlaysFirst = TRUE;
12899 startedFromSetupPosition = TRUE;
12901 CopyBoard(boards[0], initial_position);
12902 if (blackPlaysFirst) {
12903 currentMove = forwardMostMove = backwardMostMove = 1;
12904 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12905 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12906 CopyBoard(boards[1], initial_position);
12907 DisplayMessage("", _("Black to play"));
12909 currentMove = forwardMostMove = backwardMostMove = 0;
12910 DisplayMessage("", _("White to play"));
12912 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12913 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12914 SendToProgram("force\n", &first);
12915 SendBoard(&first, forwardMostMove);
12917 if (appData.debugMode) {
12919 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12920 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12921 fprintf(debugFP, "Load Position\n");
12924 if (positionNumber > 1) {
12925 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12926 DisplayTitle(line);
12928 DisplayTitle(title);
12930 gameMode = EditGame;
12933 timeRemaining[0][1] = whiteTimeRemaining;
12934 timeRemaining[1][1] = blackTimeRemaining;
12935 DrawPosition(FALSE, boards[currentMove]);
12942 CopyPlayerNameIntoFileName (char **dest, char *src)
12944 while (*src != NULLCHAR && *src != ',') {
12949 *(*dest)++ = *src++;
12955 DefaultFileName (char *ext)
12957 static char def[MSG_SIZ];
12960 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12962 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12964 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12966 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12973 /* Save the current game to the given file */
12975 SaveGameToFile (char *filename, int append)
12979 int result, i, t,tot=0;
12981 if (strcmp(filename, "-") == 0) {
12982 return SaveGame(stdout, 0, NULL);
12984 for(i=0; i<10; i++) { // upto 10 tries
12985 f = fopen(filename, append ? "a" : "w");
12986 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12987 if(f || errno != 13) break;
12988 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12992 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12993 DisplayError(buf, errno);
12996 safeStrCpy(buf, lastMsg, MSG_SIZ);
12997 DisplayMessage(_("Waiting for access to save file"), "");
12998 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12999 DisplayMessage(_("Saving game"), "");
13000 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13001 result = SaveGame(f, 0, NULL);
13002 DisplayMessage(buf, "");
13009 SavePart (char *str)
13011 static char buf[MSG_SIZ];
13014 p = strchr(str, ' ');
13015 if (p == NULL) return str;
13016 strncpy(buf, str, p - str);
13017 buf[p - str] = NULLCHAR;
13021 #define PGN_MAX_LINE 75
13023 #define PGN_SIDE_WHITE 0
13024 #define PGN_SIDE_BLACK 1
13027 FindFirstMoveOutOfBook (int side)
13031 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13032 int index = backwardMostMove;
13033 int has_book_hit = 0;
13035 if( (index % 2) != side ) {
13039 while( index < forwardMostMove ) {
13040 /* Check to see if engine is in book */
13041 int depth = pvInfoList[index].depth;
13042 int score = pvInfoList[index].score;
13048 else if( score == 0 && depth == 63 ) {
13049 in_book = 1; /* Zappa */
13051 else if( score == 2 && depth == 99 ) {
13052 in_book = 1; /* Abrok */
13055 has_book_hit += in_book;
13071 GetOutOfBookInfo (char * buf)
13075 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13077 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13078 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13082 if( oob[0] >= 0 || oob[1] >= 0 ) {
13083 for( i=0; i<2; i++ ) {
13087 if( i > 0 && oob[0] >= 0 ) {
13088 strcat( buf, " " );
13091 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13092 sprintf( buf+strlen(buf), "%s%.2f",
13093 pvInfoList[idx].score >= 0 ? "+" : "",
13094 pvInfoList[idx].score / 100.0 );
13100 /* Save game in PGN style and close the file */
13102 SaveGamePGN (FILE *f)
13104 int i, offset, linelen, newblock;
13107 int movelen, numlen, blank;
13108 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13110 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13112 PrintPGNTags(f, &gameInfo);
13114 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13116 if (backwardMostMove > 0 || startedFromSetupPosition) {
13117 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13118 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13119 fprintf(f, "\n{--------------\n");
13120 PrintPosition(f, backwardMostMove);
13121 fprintf(f, "--------------}\n");
13125 /* [AS] Out of book annotation */
13126 if( appData.saveOutOfBookInfo ) {
13129 GetOutOfBookInfo( buf );
13131 if( buf[0] != '\0' ) {
13132 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13139 i = backwardMostMove;
13143 while (i < forwardMostMove) {
13144 /* Print comments preceding this move */
13145 if (commentList[i] != NULL) {
13146 if (linelen > 0) fprintf(f, "\n");
13147 fprintf(f, "%s", commentList[i]);
13152 /* Format move number */
13154 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13157 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13159 numtext[0] = NULLCHAR;
13161 numlen = strlen(numtext);
13164 /* Print move number */
13165 blank = linelen > 0 && numlen > 0;
13166 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13175 fprintf(f, "%s", numtext);
13179 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13180 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13183 blank = linelen > 0 && movelen > 0;
13184 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13193 fprintf(f, "%s", move_buffer);
13194 linelen += movelen;
13196 /* [AS] Add PV info if present */
13197 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13198 /* [HGM] add time */
13199 char buf[MSG_SIZ]; int seconds;
13201 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13207 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13210 seconds = (seconds + 4)/10; // round to full seconds
13212 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13214 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13217 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13218 pvInfoList[i].score >= 0 ? "+" : "",
13219 pvInfoList[i].score / 100.0,
13220 pvInfoList[i].depth,
13223 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13225 /* Print score/depth */
13226 blank = linelen > 0 && movelen > 0;
13227 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13236 fprintf(f, "%s", move_buffer);
13237 linelen += movelen;
13243 /* Start a new line */
13244 if (linelen > 0) fprintf(f, "\n");
13246 /* Print comments after last move */
13247 if (commentList[i] != NULL) {
13248 fprintf(f, "%s\n", commentList[i]);
13252 if (gameInfo.resultDetails != NULL &&
13253 gameInfo.resultDetails[0] != NULLCHAR) {
13254 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13255 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13256 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13257 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13258 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13260 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13264 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13268 /* Save game in old style and close the file */
13270 SaveGameOldStyle (FILE *f)
13275 tm = time((time_t *) NULL);
13277 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13280 if (backwardMostMove > 0 || startedFromSetupPosition) {
13281 fprintf(f, "\n[--------------\n");
13282 PrintPosition(f, backwardMostMove);
13283 fprintf(f, "--------------]\n");
13288 i = backwardMostMove;
13289 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13291 while (i < forwardMostMove) {
13292 if (commentList[i] != NULL) {
13293 fprintf(f, "[%s]\n", commentList[i]);
13296 if ((i % 2) == 1) {
13297 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13300 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13302 if (commentList[i] != NULL) {
13306 if (i >= forwardMostMove) {
13310 fprintf(f, "%s\n", parseList[i]);
13315 if (commentList[i] != NULL) {
13316 fprintf(f, "[%s]\n", commentList[i]);
13319 /* This isn't really the old style, but it's close enough */
13320 if (gameInfo.resultDetails != NULL &&
13321 gameInfo.resultDetails[0] != NULLCHAR) {
13322 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13323 gameInfo.resultDetails);
13325 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13332 /* Save the current game to open file f and close the file */
13334 SaveGame (FILE *f, int dummy, char *dummy2)
13336 if (gameMode == EditPosition) EditPositionDone(TRUE);
13337 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13338 if (appData.oldSaveStyle)
13339 return SaveGameOldStyle(f);
13341 return SaveGamePGN(f);
13344 /* Save the current position to the given file */
13346 SavePositionToFile (char *filename)
13351 if (strcmp(filename, "-") == 0) {
13352 return SavePosition(stdout, 0, NULL);
13354 f = fopen(filename, "a");
13356 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13357 DisplayError(buf, errno);
13360 safeStrCpy(buf, lastMsg, MSG_SIZ);
13361 DisplayMessage(_("Waiting for access to save file"), "");
13362 flock(fileno(f), LOCK_EX); // [HGM] lock
13363 DisplayMessage(_("Saving position"), "");
13364 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13365 SavePosition(f, 0, NULL);
13366 DisplayMessage(buf, "");
13372 /* Save the current position to the given open file and close the file */
13374 SavePosition (FILE *f, int dummy, char *dummy2)
13379 if (gameMode == EditPosition) EditPositionDone(TRUE);
13380 if (appData.oldSaveStyle) {
13381 tm = time((time_t *) NULL);
13383 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13385 fprintf(f, "[--------------\n");
13386 PrintPosition(f, currentMove);
13387 fprintf(f, "--------------]\n");
13389 fen = PositionToFEN(currentMove, NULL, 1);
13390 fprintf(f, "%s\n", fen);
13398 ReloadCmailMsgEvent (int unregister)
13401 static char *inFilename = NULL;
13402 static char *outFilename;
13404 struct stat inbuf, outbuf;
13407 /* Any registered moves are unregistered if unregister is set, */
13408 /* i.e. invoked by the signal handler */
13410 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13411 cmailMoveRegistered[i] = FALSE;
13412 if (cmailCommentList[i] != NULL) {
13413 free(cmailCommentList[i]);
13414 cmailCommentList[i] = NULL;
13417 nCmailMovesRegistered = 0;
13420 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13421 cmailResult[i] = CMAIL_NOT_RESULT;
13425 if (inFilename == NULL) {
13426 /* Because the filenames are static they only get malloced once */
13427 /* and they never get freed */
13428 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13429 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13431 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13432 sprintf(outFilename, "%s.out", appData.cmailGameName);
13435 status = stat(outFilename, &outbuf);
13437 cmailMailedMove = FALSE;
13439 status = stat(inFilename, &inbuf);
13440 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13443 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13444 counts the games, notes how each one terminated, etc.
13446 It would be nice to remove this kludge and instead gather all
13447 the information while building the game list. (And to keep it
13448 in the game list nodes instead of having a bunch of fixed-size
13449 parallel arrays.) Note this will require getting each game's
13450 termination from the PGN tags, as the game list builder does
13451 not process the game moves. --mann
13453 cmailMsgLoaded = TRUE;
13454 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13456 /* Load first game in the file or popup game menu */
13457 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13459 #endif /* !WIN32 */
13467 char string[MSG_SIZ];
13469 if ( cmailMailedMove
13470 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13471 return TRUE; /* Allow free viewing */
13474 /* Unregister move to ensure that we don't leave RegisterMove */
13475 /* with the move registered when the conditions for registering no */
13477 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13478 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13479 nCmailMovesRegistered --;
13481 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13483 free(cmailCommentList[lastLoadGameNumber - 1]);
13484 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13488 if (cmailOldMove == -1) {
13489 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13493 if (currentMove > cmailOldMove + 1) {
13494 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13498 if (currentMove < cmailOldMove) {
13499 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13503 if (forwardMostMove > currentMove) {
13504 /* Silently truncate extra moves */
13508 if ( (currentMove == cmailOldMove + 1)
13509 || ( (currentMove == cmailOldMove)
13510 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13511 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13512 if (gameInfo.result != GameUnfinished) {
13513 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13516 if (commentList[currentMove] != NULL) {
13517 cmailCommentList[lastLoadGameNumber - 1]
13518 = StrSave(commentList[currentMove]);
13520 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13522 if (appData.debugMode)
13523 fprintf(debugFP, "Saving %s for game %d\n",
13524 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13526 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13528 f = fopen(string, "w");
13529 if (appData.oldSaveStyle) {
13530 SaveGameOldStyle(f); /* also closes the file */
13532 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13533 f = fopen(string, "w");
13534 SavePosition(f, 0, NULL); /* also closes the file */
13536 fprintf(f, "{--------------\n");
13537 PrintPosition(f, currentMove);
13538 fprintf(f, "--------------}\n\n");
13540 SaveGame(f, 0, NULL); /* also closes the file*/
13543 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13544 nCmailMovesRegistered ++;
13545 } else if (nCmailGames == 1) {
13546 DisplayError(_("You have not made a move yet"), 0);
13557 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13558 FILE *commandOutput;
13559 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13560 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13566 if (! cmailMsgLoaded) {
13567 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13571 if (nCmailGames == nCmailResults) {
13572 DisplayError(_("No unfinished games"), 0);
13576 #if CMAIL_PROHIBIT_REMAIL
13577 if (cmailMailedMove) {
13578 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);
13579 DisplayError(msg, 0);
13584 if (! (cmailMailedMove || RegisterMove())) return;
13586 if ( cmailMailedMove
13587 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13588 snprintf(string, MSG_SIZ, partCommandString,
13589 appData.debugMode ? " -v" : "", appData.cmailGameName);
13590 commandOutput = popen(string, "r");
13592 if (commandOutput == NULL) {
13593 DisplayError(_("Failed to invoke cmail"), 0);
13595 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13596 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13598 if (nBuffers > 1) {
13599 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13600 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13601 nBytes = MSG_SIZ - 1;
13603 (void) memcpy(msg, buffer, nBytes);
13605 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13607 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13608 cmailMailedMove = TRUE; /* Prevent >1 moves */
13611 for (i = 0; i < nCmailGames; i ++) {
13612 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13617 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13619 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13621 appData.cmailGameName,
13623 LoadGameFromFile(buffer, 1, buffer, FALSE);
13624 cmailMsgLoaded = FALSE;
13628 DisplayInformation(msg);
13629 pclose(commandOutput);
13632 if ((*cmailMsg) != '\0') {
13633 DisplayInformation(cmailMsg);
13638 #endif /* !WIN32 */
13647 int prependComma = 0;
13649 char string[MSG_SIZ]; /* Space for game-list */
13652 if (!cmailMsgLoaded) return "";
13654 if (cmailMailedMove) {
13655 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13657 /* Create a list of games left */
13658 snprintf(string, MSG_SIZ, "[");
13659 for (i = 0; i < nCmailGames; i ++) {
13660 if (! ( cmailMoveRegistered[i]
13661 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13662 if (prependComma) {
13663 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13665 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13669 strcat(string, number);
13672 strcat(string, "]");
13674 if (nCmailMovesRegistered + nCmailResults == 0) {
13675 switch (nCmailGames) {
13677 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13681 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13685 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13690 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13692 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13697 if (nCmailResults == nCmailGames) {
13698 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13700 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13705 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13717 if (gameMode == Training)
13718 SetTrainingModeOff();
13721 cmailMsgLoaded = FALSE;
13722 if (appData.icsActive) {
13723 SendToICS(ics_prefix);
13724 SendToICS("refresh\n");
13729 ExitEvent (int status)
13733 /* Give up on clean exit */
13737 /* Keep trying for clean exit */
13741 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13743 if (telnetISR != NULL) {
13744 RemoveInputSource(telnetISR);
13746 if (icsPR != NoProc) {
13747 DestroyChildProcess(icsPR, TRUE);
13750 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13751 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13753 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13754 /* make sure this other one finishes before killing it! */
13755 if(endingGame) { int count = 0;
13756 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13757 while(endingGame && count++ < 10) DoSleep(1);
13758 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13761 /* Kill off chess programs */
13762 if (first.pr != NoProc) {
13765 DoSleep( appData.delayBeforeQuit );
13766 SendToProgram("quit\n", &first);
13767 DoSleep( appData.delayAfterQuit );
13768 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13770 if (second.pr != NoProc) {
13771 DoSleep( appData.delayBeforeQuit );
13772 SendToProgram("quit\n", &second);
13773 DoSleep( appData.delayAfterQuit );
13774 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13776 if (first.isr != NULL) {
13777 RemoveInputSource(first.isr);
13779 if (second.isr != NULL) {
13780 RemoveInputSource(second.isr);
13783 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13784 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13786 ShutDownFrontEnd();
13791 PauseEngine (ChessProgramState *cps)
13793 SendToProgram("pause\n", cps);
13798 UnPauseEngine (ChessProgramState *cps)
13800 SendToProgram("resume\n", cps);
13807 if (appData.debugMode)
13808 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13812 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13814 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13815 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13816 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13818 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13819 HandleMachineMove(stashedInputMove, stalledEngine);
13820 stalledEngine = NULL;
13823 if (gameMode == MachinePlaysWhite ||
13824 gameMode == TwoMachinesPlay ||
13825 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13826 if(first.pause) UnPauseEngine(&first);
13827 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13828 if(second.pause) UnPauseEngine(&second);
13829 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13832 DisplayBothClocks();
13834 if (gameMode == PlayFromGameFile) {
13835 if (appData.timeDelay >= 0)
13836 AutoPlayGameLoop();
13837 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13838 Reset(FALSE, TRUE);
13839 SendToICS(ics_prefix);
13840 SendToICS("refresh\n");
13841 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13842 ForwardInner(forwardMostMove);
13844 pauseExamInvalid = FALSE;
13846 switch (gameMode) {
13850 pauseExamForwardMostMove = forwardMostMove;
13851 pauseExamInvalid = FALSE;
13854 case IcsPlayingWhite:
13855 case IcsPlayingBlack:
13859 case PlayFromGameFile:
13860 (void) StopLoadGameTimer();
13864 case BeginningOfGame:
13865 if (appData.icsActive) return;
13866 /* else fall through */
13867 case MachinePlaysWhite:
13868 case MachinePlaysBlack:
13869 case TwoMachinesPlay:
13870 if (forwardMostMove == 0)
13871 return; /* don't pause if no one has moved */
13872 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13873 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13874 if(onMove->pause) { // thinking engine can be paused
13875 PauseEngine(onMove); // do it
13876 if(onMove->other->pause) // pondering opponent can always be paused immediately
13877 PauseEngine(onMove->other);
13879 SendToProgram("easy\n", onMove->other);
13881 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13882 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13884 PauseEngine(&first);
13886 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13887 } else { // human on move, pause pondering by either method
13889 PauseEngine(&first);
13890 else if(appData.ponderNextMove)
13891 SendToProgram("easy\n", &first);
13894 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13904 EditCommentEvent ()
13906 char title[MSG_SIZ];
13908 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13909 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13911 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13912 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13913 parseList[currentMove - 1]);
13916 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13923 char *tags = PGNTags(&gameInfo);
13925 EditTagsPopUp(tags, NULL);
13932 if(second.analyzing) {
13933 SendToProgram("exit\n", &second);
13934 second.analyzing = FALSE;
13936 if (second.pr == NoProc) StartChessProgram(&second);
13937 InitChessProgram(&second, FALSE);
13938 FeedMovesToProgram(&second, currentMove);
13940 SendToProgram("analyze\n", &second);
13941 second.analyzing = TRUE;
13945 /* Toggle ShowThinking */
13947 ToggleShowThinking()
13949 appData.showThinking = !appData.showThinking;
13950 ShowThinkingEvent();
13954 AnalyzeModeEvent ()
13958 if (!first.analysisSupport) {
13959 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13960 DisplayError(buf, 0);
13963 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13964 if (appData.icsActive) {
13965 if (gameMode != IcsObserving) {
13966 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13967 DisplayError(buf, 0);
13969 if (appData.icsEngineAnalyze) {
13970 if (appData.debugMode)
13971 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13977 /* if enable, user wants to disable icsEngineAnalyze */
13978 if (appData.icsEngineAnalyze) {
13983 appData.icsEngineAnalyze = TRUE;
13984 if (appData.debugMode)
13985 fprintf(debugFP, "ICS engine analyze starting... \n");
13988 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13989 if (appData.noChessProgram || gameMode == AnalyzeMode)
13992 if (gameMode != AnalyzeFile) {
13993 if (!appData.icsEngineAnalyze) {
13995 if (gameMode != EditGame) return 0;
13997 if (!appData.showThinking) ToggleShowThinking();
13998 ResurrectChessProgram();
13999 SendToProgram("analyze\n", &first);
14000 first.analyzing = TRUE;
14001 /*first.maybeThinking = TRUE;*/
14002 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14003 EngineOutputPopUp();
14005 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14010 StartAnalysisClock();
14011 GetTimeMark(&lastNodeCountTime);
14017 AnalyzeFileEvent ()
14019 if (appData.noChessProgram || gameMode == AnalyzeFile)
14022 if (!first.analysisSupport) {
14024 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14025 DisplayError(buf, 0);
14029 if (gameMode != AnalyzeMode) {
14030 keepInfo = 1; // mere annotating should not alter PGN tags
14033 if (gameMode != EditGame) return;
14034 if (!appData.showThinking) ToggleShowThinking();
14035 ResurrectChessProgram();
14036 SendToProgram("analyze\n", &first);
14037 first.analyzing = TRUE;
14038 /*first.maybeThinking = TRUE;*/
14039 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14040 EngineOutputPopUp();
14042 gameMode = AnalyzeFile;
14046 StartAnalysisClock();
14047 GetTimeMark(&lastNodeCountTime);
14049 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14050 AnalysisPeriodicEvent(1);
14054 MachineWhiteEvent ()
14057 char *bookHit = NULL;
14059 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14063 if (gameMode == PlayFromGameFile ||
14064 gameMode == TwoMachinesPlay ||
14065 gameMode == Training ||
14066 gameMode == AnalyzeMode ||
14067 gameMode == EndOfGame)
14070 if (gameMode == EditPosition)
14071 EditPositionDone(TRUE);
14073 if (!WhiteOnMove(currentMove)) {
14074 DisplayError(_("It is not White's turn"), 0);
14078 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14081 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14082 gameMode == AnalyzeFile)
14085 ResurrectChessProgram(); /* in case it isn't running */
14086 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14087 gameMode = MachinePlaysWhite;
14090 gameMode = MachinePlaysWhite;
14094 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14096 if (first.sendName) {
14097 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14098 SendToProgram(buf, &first);
14100 if (first.sendTime) {
14101 if (first.useColors) {
14102 SendToProgram("black\n", &first); /*gnu kludge*/
14104 SendTimeRemaining(&first, TRUE);
14106 if (first.useColors) {
14107 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14109 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14110 SetMachineThinkingEnables();
14111 first.maybeThinking = TRUE;
14115 if (appData.autoFlipView && !flipView) {
14116 flipView = !flipView;
14117 DrawPosition(FALSE, NULL);
14118 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14121 if(bookHit) { // [HGM] book: simulate book reply
14122 static char bookMove[MSG_SIZ]; // a bit generous?
14124 programStats.nodes = programStats.depth = programStats.time =
14125 programStats.score = programStats.got_only_move = 0;
14126 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14128 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14129 strcat(bookMove, bookHit);
14130 HandleMachineMove(bookMove, &first);
14135 MachineBlackEvent ()
14138 char *bookHit = NULL;
14140 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14144 if (gameMode == PlayFromGameFile ||
14145 gameMode == TwoMachinesPlay ||
14146 gameMode == Training ||
14147 gameMode == AnalyzeMode ||
14148 gameMode == EndOfGame)
14151 if (gameMode == EditPosition)
14152 EditPositionDone(TRUE);
14154 if (WhiteOnMove(currentMove)) {
14155 DisplayError(_("It is not Black's turn"), 0);
14159 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14162 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14163 gameMode == AnalyzeFile)
14166 ResurrectChessProgram(); /* in case it isn't running */
14167 gameMode = MachinePlaysBlack;
14171 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14173 if (first.sendName) {
14174 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14175 SendToProgram(buf, &first);
14177 if (first.sendTime) {
14178 if (first.useColors) {
14179 SendToProgram("white\n", &first); /*gnu kludge*/
14181 SendTimeRemaining(&first, FALSE);
14183 if (first.useColors) {
14184 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14186 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14187 SetMachineThinkingEnables();
14188 first.maybeThinking = TRUE;
14191 if (appData.autoFlipView && flipView) {
14192 flipView = !flipView;
14193 DrawPosition(FALSE, NULL);
14194 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14196 if(bookHit) { // [HGM] book: simulate book reply
14197 static char bookMove[MSG_SIZ]; // a bit generous?
14199 programStats.nodes = programStats.depth = programStats.time =
14200 programStats.score = programStats.got_only_move = 0;
14201 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14203 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14204 strcat(bookMove, bookHit);
14205 HandleMachineMove(bookMove, &first);
14211 DisplayTwoMachinesTitle ()
14214 if (appData.matchGames > 0) {
14215 if(appData.tourneyFile[0]) {
14216 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14217 gameInfo.white, _("vs."), gameInfo.black,
14218 nextGame+1, appData.matchGames+1,
14219 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14221 if (first.twoMachinesColor[0] == 'w') {
14222 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14223 gameInfo.white, _("vs."), gameInfo.black,
14224 first.matchWins, second.matchWins,
14225 matchGame - 1 - (first.matchWins + second.matchWins));
14227 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14228 gameInfo.white, _("vs."), gameInfo.black,
14229 second.matchWins, first.matchWins,
14230 matchGame - 1 - (first.matchWins + second.matchWins));
14233 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14239 SettingsMenuIfReady ()
14241 if (second.lastPing != second.lastPong) {
14242 DisplayMessage("", _("Waiting for second chess program"));
14243 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14247 DisplayMessage("", "");
14248 SettingsPopUp(&second);
14252 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14255 if (cps->pr == NoProc) {
14256 StartChessProgram(cps);
14257 if (cps->protocolVersion == 1) {
14259 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14261 /* kludge: allow timeout for initial "feature" command */
14262 if(retry != TwoMachinesEventIfReady) FreezeUI();
14263 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14264 DisplayMessage("", buf);
14265 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14273 TwoMachinesEvent P((void))
14277 ChessProgramState *onmove;
14278 char *bookHit = NULL;
14279 static int stalling = 0;
14283 if (appData.noChessProgram) return;
14285 switch (gameMode) {
14286 case TwoMachinesPlay:
14288 case MachinePlaysWhite:
14289 case MachinePlaysBlack:
14290 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14291 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14295 case BeginningOfGame:
14296 case PlayFromGameFile:
14299 if (gameMode != EditGame) return;
14302 EditPositionDone(TRUE);
14313 // forwardMostMove = currentMove;
14314 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14315 startingEngine = TRUE;
14317 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14319 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14320 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14321 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14324 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14326 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14327 startingEngine = FALSE;
14328 DisplayError("second engine does not play this", 0);
14333 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14334 SendToProgram("force\n", &second);
14336 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14339 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14340 if(appData.matchPause>10000 || appData.matchPause<10)
14341 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14342 wait = SubtractTimeMarks(&now, &pauseStart);
14343 if(wait < appData.matchPause) {
14344 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14347 // we are now committed to starting the game
14349 DisplayMessage("", "");
14350 if (startedFromSetupPosition) {
14351 SendBoard(&second, backwardMostMove);
14352 if (appData.debugMode) {
14353 fprintf(debugFP, "Two Machines\n");
14356 for (i = backwardMostMove; i < forwardMostMove; i++) {
14357 SendMoveToProgram(i, &second);
14360 gameMode = TwoMachinesPlay;
14361 pausing = startingEngine = FALSE;
14362 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14364 DisplayTwoMachinesTitle();
14366 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14371 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14372 SendToProgram(first.computerString, &first);
14373 if (first.sendName) {
14374 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14375 SendToProgram(buf, &first);
14377 SendToProgram(second.computerString, &second);
14378 if (second.sendName) {
14379 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14380 SendToProgram(buf, &second);
14384 if (!first.sendTime || !second.sendTime) {
14385 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14386 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14388 if (onmove->sendTime) {
14389 if (onmove->useColors) {
14390 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14392 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14394 if (onmove->useColors) {
14395 SendToProgram(onmove->twoMachinesColor, onmove);
14397 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14398 // SendToProgram("go\n", onmove);
14399 onmove->maybeThinking = TRUE;
14400 SetMachineThinkingEnables();
14404 if(bookHit) { // [HGM] book: simulate book reply
14405 static char bookMove[MSG_SIZ]; // a bit generous?
14407 programStats.nodes = programStats.depth = programStats.time =
14408 programStats.score = programStats.got_only_move = 0;
14409 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14411 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14412 strcat(bookMove, bookHit);
14413 savedMessage = bookMove; // args for deferred call
14414 savedState = onmove;
14415 ScheduleDelayedEvent(DeferredBookMove, 1);
14422 if (gameMode == Training) {
14423 SetTrainingModeOff();
14424 gameMode = PlayFromGameFile;
14425 DisplayMessage("", _("Training mode off"));
14427 gameMode = Training;
14428 animateTraining = appData.animate;
14430 /* make sure we are not already at the end of the game */
14431 if (currentMove < forwardMostMove) {
14432 SetTrainingModeOn();
14433 DisplayMessage("", _("Training mode on"));
14435 gameMode = PlayFromGameFile;
14436 DisplayError(_("Already at end of game"), 0);
14445 if (!appData.icsActive) return;
14446 switch (gameMode) {
14447 case IcsPlayingWhite:
14448 case IcsPlayingBlack:
14451 case BeginningOfGame:
14459 EditPositionDone(TRUE);
14472 gameMode = IcsIdle;
14482 switch (gameMode) {
14484 SetTrainingModeOff();
14486 case MachinePlaysWhite:
14487 case MachinePlaysBlack:
14488 case BeginningOfGame:
14489 SendToProgram("force\n", &first);
14490 SetUserThinkingEnables();
14492 case PlayFromGameFile:
14493 (void) StopLoadGameTimer();
14494 if (gameFileFP != NULL) {
14499 EditPositionDone(TRUE);
14504 SendToProgram("force\n", &first);
14506 case TwoMachinesPlay:
14507 GameEnds(EndOfFile, NULL, GE_PLAYER);
14508 ResurrectChessProgram();
14509 SetUserThinkingEnables();
14512 ResurrectChessProgram();
14514 case IcsPlayingBlack:
14515 case IcsPlayingWhite:
14516 DisplayError(_("Warning: You are still playing a game"), 0);
14519 DisplayError(_("Warning: You are still observing a game"), 0);
14522 DisplayError(_("Warning: You are still examining a game"), 0);
14533 first.offeredDraw = second.offeredDraw = 0;
14535 if (gameMode == PlayFromGameFile) {
14536 whiteTimeRemaining = timeRemaining[0][currentMove];
14537 blackTimeRemaining = timeRemaining[1][currentMove];
14541 if (gameMode == MachinePlaysWhite ||
14542 gameMode == MachinePlaysBlack ||
14543 gameMode == TwoMachinesPlay ||
14544 gameMode == EndOfGame) {
14545 i = forwardMostMove;
14546 while (i > currentMove) {
14547 SendToProgram("undo\n", &first);
14550 if(!adjustedClock) {
14551 whiteTimeRemaining = timeRemaining[0][currentMove];
14552 blackTimeRemaining = timeRemaining[1][currentMove];
14553 DisplayBothClocks();
14555 if (whiteFlag || blackFlag) {
14556 whiteFlag = blackFlag = 0;
14561 gameMode = EditGame;
14568 EditPositionEvent ()
14570 if (gameMode == EditPosition) {
14576 if (gameMode != EditGame) return;
14578 gameMode = EditPosition;
14581 if (currentMove > 0)
14582 CopyBoard(boards[0], boards[currentMove]);
14584 blackPlaysFirst = !WhiteOnMove(currentMove);
14586 currentMove = forwardMostMove = backwardMostMove = 0;
14587 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14589 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14595 /* [DM] icsEngineAnalyze - possible call from other functions */
14596 if (appData.icsEngineAnalyze) {
14597 appData.icsEngineAnalyze = FALSE;
14599 DisplayMessage("",_("Close ICS engine analyze..."));
14601 if (first.analysisSupport && first.analyzing) {
14602 SendToBoth("exit\n");
14603 first.analyzing = second.analyzing = FALSE;
14605 thinkOutput[0] = NULLCHAR;
14609 EditPositionDone (Boolean fakeRights)
14611 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14613 startedFromSetupPosition = TRUE;
14614 InitChessProgram(&first, FALSE);
14615 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14616 boards[0][EP_STATUS] = EP_NONE;
14617 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14618 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14619 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14620 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14621 } else boards[0][CASTLING][2] = NoRights;
14622 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14623 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14624 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14625 } else boards[0][CASTLING][5] = NoRights;
14626 if(gameInfo.variant == VariantSChess) {
14628 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14629 boards[0][VIRGIN][i] = 0;
14630 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14631 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14635 SendToProgram("force\n", &first);
14636 if (blackPlaysFirst) {
14637 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14638 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14639 currentMove = forwardMostMove = backwardMostMove = 1;
14640 CopyBoard(boards[1], boards[0]);
14642 currentMove = forwardMostMove = backwardMostMove = 0;
14644 SendBoard(&first, forwardMostMove);
14645 if (appData.debugMode) {
14646 fprintf(debugFP, "EditPosDone\n");
14649 DisplayMessage("", "");
14650 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14651 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14652 gameMode = EditGame;
14654 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14655 ClearHighlights(); /* [AS] */
14658 /* Pause for `ms' milliseconds */
14659 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14661 TimeDelay (long ms)
14668 } while (SubtractTimeMarks(&m2, &m1) < ms);
14671 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14673 SendMultiLineToICS (char *buf)
14675 char temp[MSG_SIZ+1], *p;
14682 strncpy(temp, buf, len);
14687 if (*p == '\n' || *p == '\r')
14692 strcat(temp, "\n");
14694 SendToPlayer(temp, strlen(temp));
14698 SetWhiteToPlayEvent ()
14700 if (gameMode == EditPosition) {
14701 blackPlaysFirst = FALSE;
14702 DisplayBothClocks(); /* works because currentMove is 0 */
14703 } else if (gameMode == IcsExamining) {
14704 SendToICS(ics_prefix);
14705 SendToICS("tomove white\n");
14710 SetBlackToPlayEvent ()
14712 if (gameMode == EditPosition) {
14713 blackPlaysFirst = TRUE;
14714 currentMove = 1; /* kludge */
14715 DisplayBothClocks();
14717 } else if (gameMode == IcsExamining) {
14718 SendToICS(ics_prefix);
14719 SendToICS("tomove black\n");
14724 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14727 ChessSquare piece = boards[0][y][x];
14728 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14729 static int lastVariant;
14731 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14733 switch (selection) {
14735 CopyBoard(currentBoard, boards[0]);
14736 CopyBoard(menuBoard, initialPosition);
14737 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14738 SendToICS(ics_prefix);
14739 SendToICS("bsetup clear\n");
14740 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14741 SendToICS(ics_prefix);
14742 SendToICS("clearboard\n");
14745 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14746 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14747 for (y = 0; y < BOARD_HEIGHT; y++) {
14748 if (gameMode == IcsExamining) {
14749 if (boards[currentMove][y][x] != EmptySquare) {
14750 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14755 if(boards[0][y][x] != p) nonEmpty++;
14756 boards[0][y][x] = p;
14759 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14761 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14762 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
14763 ChessSquare p = menuBoard[0][x];
14764 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14765 p = menuBoard[BOARD_HEIGHT-1][x];
14766 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14768 DisplayMessage("Clicking clock again restores position", "");
14769 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14770 if(!nonEmpty) { // asked to clear an empty board
14771 CopyBoard(boards[0], menuBoard);
14773 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14774 CopyBoard(boards[0], initialPosition);
14776 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14777 && !CompareBoards(nullBoard, erasedBoard)) {
14778 CopyBoard(boards[0], erasedBoard);
14780 CopyBoard(erasedBoard, currentBoard);
14784 if (gameMode == EditPosition) {
14785 DrawPosition(FALSE, boards[0]);
14790 SetWhiteToPlayEvent();
14794 SetBlackToPlayEvent();
14798 if (gameMode == IcsExamining) {
14799 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14800 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14803 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14804 if(x == BOARD_LEFT-2) {
14805 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14806 boards[0][y][1] = 0;
14808 if(x == BOARD_RGHT+1) {
14809 if(y >= gameInfo.holdingsSize) break;
14810 boards[0][y][BOARD_WIDTH-2] = 0;
14813 boards[0][y][x] = EmptySquare;
14814 DrawPosition(FALSE, boards[0]);
14819 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14820 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14821 selection = (ChessSquare) (PROMOTED piece);
14822 } else if(piece == EmptySquare) selection = WhiteSilver;
14823 else selection = (ChessSquare)((int)piece - 1);
14827 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14828 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14829 selection = (ChessSquare) (DEMOTED piece);
14830 } else if(piece == EmptySquare) selection = BlackSilver;
14831 else selection = (ChessSquare)((int)piece + 1);
14836 if(gameInfo.variant == VariantShatranj ||
14837 gameInfo.variant == VariantXiangqi ||
14838 gameInfo.variant == VariantCourier ||
14839 gameInfo.variant == VariantASEAN ||
14840 gameInfo.variant == VariantMakruk )
14841 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14846 if(gameInfo.variant == VariantXiangqi)
14847 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14848 if(gameInfo.variant == VariantKnightmate)
14849 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14852 if (gameMode == IcsExamining) {
14853 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14854 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14855 PieceToChar(selection), AAA + x, ONE + y);
14858 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14860 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14861 n = PieceToNumber(selection - BlackPawn);
14862 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14863 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14864 boards[0][BOARD_HEIGHT-1-n][1]++;
14866 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14867 n = PieceToNumber(selection);
14868 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14869 boards[0][n][BOARD_WIDTH-1] = selection;
14870 boards[0][n][BOARD_WIDTH-2]++;
14873 boards[0][y][x] = selection;
14874 DrawPosition(TRUE, boards[0]);
14876 fromX = fromY = -1;
14884 DropMenuEvent (ChessSquare selection, int x, int y)
14886 ChessMove moveType;
14888 switch (gameMode) {
14889 case IcsPlayingWhite:
14890 case MachinePlaysBlack:
14891 if (!WhiteOnMove(currentMove)) {
14892 DisplayMoveError(_("It is Black's turn"));
14895 moveType = WhiteDrop;
14897 case IcsPlayingBlack:
14898 case MachinePlaysWhite:
14899 if (WhiteOnMove(currentMove)) {
14900 DisplayMoveError(_("It is White's turn"));
14903 moveType = BlackDrop;
14906 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14912 if (moveType == BlackDrop && selection < BlackPawn) {
14913 selection = (ChessSquare) ((int) selection
14914 + (int) BlackPawn - (int) WhitePawn);
14916 if (boards[currentMove][y][x] != EmptySquare) {
14917 DisplayMoveError(_("That square is occupied"));
14921 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14927 /* Accept a pending offer of any kind from opponent */
14929 if (appData.icsActive) {
14930 SendToICS(ics_prefix);
14931 SendToICS("accept\n");
14932 } else if (cmailMsgLoaded) {
14933 if (currentMove == cmailOldMove &&
14934 commentList[cmailOldMove] != NULL &&
14935 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14936 "Black offers a draw" : "White offers a draw")) {
14938 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14939 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14941 DisplayError(_("There is no pending offer on this move"), 0);
14942 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14945 /* Not used for offers from chess program */
14952 /* Decline a pending offer of any kind from opponent */
14954 if (appData.icsActive) {
14955 SendToICS(ics_prefix);
14956 SendToICS("decline\n");
14957 } else if (cmailMsgLoaded) {
14958 if (currentMove == cmailOldMove &&
14959 commentList[cmailOldMove] != NULL &&
14960 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14961 "Black offers a draw" : "White offers a draw")) {
14963 AppendComment(cmailOldMove, "Draw declined", TRUE);
14964 DisplayComment(cmailOldMove - 1, "Draw declined");
14967 DisplayError(_("There is no pending offer on this move"), 0);
14970 /* Not used for offers from chess program */
14977 /* Issue ICS rematch command */
14978 if (appData.icsActive) {
14979 SendToICS(ics_prefix);
14980 SendToICS("rematch\n");
14987 /* Call your opponent's flag (claim a win on time) */
14988 if (appData.icsActive) {
14989 SendToICS(ics_prefix);
14990 SendToICS("flag\n");
14992 switch (gameMode) {
14995 case MachinePlaysWhite:
14998 GameEnds(GameIsDrawn, "Both players ran out of time",
15001 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15003 DisplayError(_("Your opponent is not out of time"), 0);
15006 case MachinePlaysBlack:
15009 GameEnds(GameIsDrawn, "Both players ran out of time",
15012 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15014 DisplayError(_("Your opponent is not out of time"), 0);
15022 ClockClick (int which)
15023 { // [HGM] code moved to back-end from winboard.c
15024 if(which) { // black clock
15025 if (gameMode == EditPosition || gameMode == IcsExamining) {
15026 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15027 SetBlackToPlayEvent();
15028 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15029 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15030 } else if (shiftKey) {
15031 AdjustClock(which, -1);
15032 } else if (gameMode == IcsPlayingWhite ||
15033 gameMode == MachinePlaysBlack) {
15036 } else { // white clock
15037 if (gameMode == EditPosition || gameMode == IcsExamining) {
15038 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15039 SetWhiteToPlayEvent();
15040 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15041 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15042 } else if (shiftKey) {
15043 AdjustClock(which, -1);
15044 } else if (gameMode == IcsPlayingBlack ||
15045 gameMode == MachinePlaysWhite) {
15054 /* Offer draw or accept pending draw offer from opponent */
15056 if (appData.icsActive) {
15057 /* Note: tournament rules require draw offers to be
15058 made after you make your move but before you punch
15059 your clock. Currently ICS doesn't let you do that;
15060 instead, you immediately punch your clock after making
15061 a move, but you can offer a draw at any time. */
15063 SendToICS(ics_prefix);
15064 SendToICS("draw\n");
15065 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15066 } else if (cmailMsgLoaded) {
15067 if (currentMove == cmailOldMove &&
15068 commentList[cmailOldMove] != NULL &&
15069 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15070 "Black offers a draw" : "White offers a draw")) {
15071 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15072 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15073 } else if (currentMove == cmailOldMove + 1) {
15074 char *offer = WhiteOnMove(cmailOldMove) ?
15075 "White offers a draw" : "Black offers a draw";
15076 AppendComment(currentMove, offer, TRUE);
15077 DisplayComment(currentMove - 1, offer);
15078 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15080 DisplayError(_("You must make your move before offering a draw"), 0);
15081 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15083 } else if (first.offeredDraw) {
15084 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15086 if (first.sendDrawOffers) {
15087 SendToProgram("draw\n", &first);
15088 userOfferedDraw = TRUE;
15096 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15098 if (appData.icsActive) {
15099 SendToICS(ics_prefix);
15100 SendToICS("adjourn\n");
15102 /* Currently GNU Chess doesn't offer or accept Adjourns */
15110 /* Offer Abort or accept pending Abort offer from opponent */
15112 if (appData.icsActive) {
15113 SendToICS(ics_prefix);
15114 SendToICS("abort\n");
15116 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15123 /* Resign. You can do this even if it's not your turn. */
15125 if (appData.icsActive) {
15126 SendToICS(ics_prefix);
15127 SendToICS("resign\n");
15129 switch (gameMode) {
15130 case MachinePlaysWhite:
15131 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15133 case MachinePlaysBlack:
15134 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15137 if (cmailMsgLoaded) {
15139 if (WhiteOnMove(cmailOldMove)) {
15140 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15142 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15144 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15155 StopObservingEvent ()
15157 /* Stop observing current games */
15158 SendToICS(ics_prefix);
15159 SendToICS("unobserve\n");
15163 StopExaminingEvent ()
15165 /* Stop observing current game */
15166 SendToICS(ics_prefix);
15167 SendToICS("unexamine\n");
15171 ForwardInner (int target)
15173 int limit; int oldSeekGraphUp = seekGraphUp;
15175 if (appData.debugMode)
15176 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15177 target, currentMove, forwardMostMove);
15179 if (gameMode == EditPosition)
15182 seekGraphUp = FALSE;
15183 MarkTargetSquares(1);
15185 if (gameMode == PlayFromGameFile && !pausing)
15188 if (gameMode == IcsExamining && pausing)
15189 limit = pauseExamForwardMostMove;
15191 limit = forwardMostMove;
15193 if (target > limit) target = limit;
15195 if (target > 0 && moveList[target - 1][0]) {
15196 int fromX, fromY, toX, toY;
15197 toX = moveList[target - 1][2] - AAA;
15198 toY = moveList[target - 1][3] - ONE;
15199 if (moveList[target - 1][1] == '@') {
15200 if (appData.highlightLastMove) {
15201 SetHighlights(-1, -1, toX, toY);
15204 fromX = moveList[target - 1][0] - AAA;
15205 fromY = moveList[target - 1][1] - ONE;
15206 if (target == currentMove + 1) {
15207 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15209 if (appData.highlightLastMove) {
15210 SetHighlights(fromX, fromY, toX, toY);
15214 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15215 gameMode == Training || gameMode == PlayFromGameFile ||
15216 gameMode == AnalyzeFile) {
15217 while (currentMove < target) {
15218 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15219 SendMoveToProgram(currentMove++, &first);
15222 currentMove = target;
15225 if (gameMode == EditGame || gameMode == EndOfGame) {
15226 whiteTimeRemaining = timeRemaining[0][currentMove];
15227 blackTimeRemaining = timeRemaining[1][currentMove];
15229 DisplayBothClocks();
15230 DisplayMove(currentMove - 1);
15231 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15232 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15233 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15234 DisplayComment(currentMove - 1, commentList[currentMove]);
15236 ClearMap(); // [HGM] exclude: invalidate map
15243 if (gameMode == IcsExamining && !pausing) {
15244 SendToICS(ics_prefix);
15245 SendToICS("forward\n");
15247 ForwardInner(currentMove + 1);
15254 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15255 /* to optimze, we temporarily turn off analysis mode while we feed
15256 * the remaining moves to the engine. Otherwise we get analysis output
15259 if (first.analysisSupport) {
15260 SendToProgram("exit\nforce\n", &first);
15261 first.analyzing = FALSE;
15265 if (gameMode == IcsExamining && !pausing) {
15266 SendToICS(ics_prefix);
15267 SendToICS("forward 999999\n");
15269 ForwardInner(forwardMostMove);
15272 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15273 /* we have fed all the moves, so reactivate analysis mode */
15274 SendToProgram("analyze\n", &first);
15275 first.analyzing = TRUE;
15276 /*first.maybeThinking = TRUE;*/
15277 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15282 BackwardInner (int target)
15284 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15286 if (appData.debugMode)
15287 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15288 target, currentMove, forwardMostMove);
15290 if (gameMode == EditPosition) return;
15291 seekGraphUp = FALSE;
15292 MarkTargetSquares(1);
15293 if (currentMove <= backwardMostMove) {
15295 DrawPosition(full_redraw, boards[currentMove]);
15298 if (gameMode == PlayFromGameFile && !pausing)
15301 if (moveList[target][0]) {
15302 int fromX, fromY, toX, toY;
15303 toX = moveList[target][2] - AAA;
15304 toY = moveList[target][3] - ONE;
15305 if (moveList[target][1] == '@') {
15306 if (appData.highlightLastMove) {
15307 SetHighlights(-1, -1, toX, toY);
15310 fromX = moveList[target][0] - AAA;
15311 fromY = moveList[target][1] - ONE;
15312 if (target == currentMove - 1) {
15313 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15315 if (appData.highlightLastMove) {
15316 SetHighlights(fromX, fromY, toX, toY);
15320 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15321 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15322 while (currentMove > target) {
15323 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15324 // null move cannot be undone. Reload program with move history before it.
15326 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15327 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15329 SendBoard(&first, i);
15330 if(second.analyzing) SendBoard(&second, i);
15331 for(currentMove=i; currentMove<target; currentMove++) {
15332 SendMoveToProgram(currentMove, &first);
15333 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15337 SendToBoth("undo\n");
15341 currentMove = target;
15344 if (gameMode == EditGame || gameMode == EndOfGame) {
15345 whiteTimeRemaining = timeRemaining[0][currentMove];
15346 blackTimeRemaining = timeRemaining[1][currentMove];
15348 DisplayBothClocks();
15349 DisplayMove(currentMove - 1);
15350 DrawPosition(full_redraw, boards[currentMove]);
15351 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15352 // [HGM] PV info: routine tests if comment empty
15353 DisplayComment(currentMove - 1, commentList[currentMove]);
15354 ClearMap(); // [HGM] exclude: invalidate map
15360 if (gameMode == IcsExamining && !pausing) {
15361 SendToICS(ics_prefix);
15362 SendToICS("backward\n");
15364 BackwardInner(currentMove - 1);
15371 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15372 /* to optimize, we temporarily turn off analysis mode while we undo
15373 * all the moves. Otherwise we get analysis output after each undo.
15375 if (first.analysisSupport) {
15376 SendToProgram("exit\nforce\n", &first);
15377 first.analyzing = FALSE;
15381 if (gameMode == IcsExamining && !pausing) {
15382 SendToICS(ics_prefix);
15383 SendToICS("backward 999999\n");
15385 BackwardInner(backwardMostMove);
15388 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15389 /* we have fed all the moves, so reactivate analysis mode */
15390 SendToProgram("analyze\n", &first);
15391 first.analyzing = TRUE;
15392 /*first.maybeThinking = TRUE;*/
15393 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15400 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15401 if (to >= forwardMostMove) to = forwardMostMove;
15402 if (to <= backwardMostMove) to = backwardMostMove;
15403 if (to < currentMove) {
15411 RevertEvent (Boolean annotate)
15413 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15416 if (gameMode != IcsExamining) {
15417 DisplayError(_("You are not examining a game"), 0);
15421 DisplayError(_("You can't revert while pausing"), 0);
15424 SendToICS(ics_prefix);
15425 SendToICS("revert\n");
15429 RetractMoveEvent ()
15431 switch (gameMode) {
15432 case MachinePlaysWhite:
15433 case MachinePlaysBlack:
15434 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15435 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15438 if (forwardMostMove < 2) return;
15439 currentMove = forwardMostMove = forwardMostMove - 2;
15440 whiteTimeRemaining = timeRemaining[0][currentMove];
15441 blackTimeRemaining = timeRemaining[1][currentMove];
15442 DisplayBothClocks();
15443 DisplayMove(currentMove - 1);
15444 ClearHighlights();/*!! could figure this out*/
15445 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15446 SendToProgram("remove\n", &first);
15447 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15450 case BeginningOfGame:
15454 case IcsPlayingWhite:
15455 case IcsPlayingBlack:
15456 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15457 SendToICS(ics_prefix);
15458 SendToICS("takeback 2\n");
15460 SendToICS(ics_prefix);
15461 SendToICS("takeback 1\n");
15470 ChessProgramState *cps;
15472 switch (gameMode) {
15473 case MachinePlaysWhite:
15474 if (!WhiteOnMove(forwardMostMove)) {
15475 DisplayError(_("It is your turn"), 0);
15480 case MachinePlaysBlack:
15481 if (WhiteOnMove(forwardMostMove)) {
15482 DisplayError(_("It is your turn"), 0);
15487 case TwoMachinesPlay:
15488 if (WhiteOnMove(forwardMostMove) ==
15489 (first.twoMachinesColor[0] == 'w')) {
15495 case BeginningOfGame:
15499 SendToProgram("?\n", cps);
15503 TruncateGameEvent ()
15506 if (gameMode != EditGame) return;
15513 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15514 if (forwardMostMove > currentMove) {
15515 if (gameInfo.resultDetails != NULL) {
15516 free(gameInfo.resultDetails);
15517 gameInfo.resultDetails = NULL;
15518 gameInfo.result = GameUnfinished;
15520 forwardMostMove = currentMove;
15521 HistorySet(parseList, backwardMostMove, forwardMostMove,
15529 if (appData.noChessProgram) return;
15530 switch (gameMode) {
15531 case MachinePlaysWhite:
15532 if (WhiteOnMove(forwardMostMove)) {
15533 DisplayError(_("Wait until your turn."), 0);
15537 case BeginningOfGame:
15538 case MachinePlaysBlack:
15539 if (!WhiteOnMove(forwardMostMove)) {
15540 DisplayError(_("Wait until your turn."), 0);
15545 DisplayError(_("No hint available"), 0);
15548 SendToProgram("hint\n", &first);
15549 hintRequested = TRUE;
15555 ListGame * lg = (ListGame *) gameList.head;
15558 static int secondTime = FALSE;
15560 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15561 DisplayError(_("Game list not loaded or empty"), 0);
15565 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15568 DisplayNote(_("Book file exists! Try again for overwrite."));
15572 creatingBook = TRUE;
15573 secondTime = FALSE;
15575 /* Get list size */
15576 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15577 LoadGame(f, nItem, "", TRUE);
15578 AddGameToBook(TRUE);
15579 lg = (ListGame *) lg->node.succ;
15582 creatingBook = FALSE;
15589 if (appData.noChessProgram) return;
15590 switch (gameMode) {
15591 case MachinePlaysWhite:
15592 if (WhiteOnMove(forwardMostMove)) {
15593 DisplayError(_("Wait until your turn."), 0);
15597 case BeginningOfGame:
15598 case MachinePlaysBlack:
15599 if (!WhiteOnMove(forwardMostMove)) {
15600 DisplayError(_("Wait until your turn."), 0);
15605 EditPositionDone(TRUE);
15607 case TwoMachinesPlay:
15612 SendToProgram("bk\n", &first);
15613 bookOutput[0] = NULLCHAR;
15614 bookRequested = TRUE;
15620 char *tags = PGNTags(&gameInfo);
15621 TagsPopUp(tags, CmailMsg());
15625 /* end button procedures */
15628 PrintPosition (FILE *fp, int move)
15632 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15633 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15634 char c = PieceToChar(boards[move][i][j]);
15635 fputc(c == 'x' ? '.' : c, fp);
15636 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15639 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15640 fprintf(fp, "white to play\n");
15642 fprintf(fp, "black to play\n");
15646 PrintOpponents (FILE *fp)
15648 if (gameInfo.white != NULL) {
15649 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15655 /* Find last component of program's own name, using some heuristics */
15657 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15660 int local = (strcmp(host, "localhost") == 0);
15661 while (!local && (p = strchr(prog, ';')) != NULL) {
15663 while (*p == ' ') p++;
15666 if (*prog == '"' || *prog == '\'') {
15667 q = strchr(prog + 1, *prog);
15669 q = strchr(prog, ' ');
15671 if (q == NULL) q = prog + strlen(prog);
15673 while (p >= prog && *p != '/' && *p != '\\') p--;
15675 if(p == prog && *p == '"') p++;
15677 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15678 memcpy(buf, p, q - p);
15679 buf[q - p] = NULLCHAR;
15687 TimeControlTagValue ()
15690 if (!appData.clockMode) {
15691 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15692 } else if (movesPerSession > 0) {
15693 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15694 } else if (timeIncrement == 0) {
15695 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15697 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15699 return StrSave(buf);
15705 /* This routine is used only for certain modes */
15706 VariantClass v = gameInfo.variant;
15707 ChessMove r = GameUnfinished;
15710 if(keepInfo) return;
15712 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15713 r = gameInfo.result;
15714 p = gameInfo.resultDetails;
15715 gameInfo.resultDetails = NULL;
15717 ClearGameInfo(&gameInfo);
15718 gameInfo.variant = v;
15720 switch (gameMode) {
15721 case MachinePlaysWhite:
15722 gameInfo.event = StrSave( appData.pgnEventHeader );
15723 gameInfo.site = StrSave(HostName());
15724 gameInfo.date = PGNDate();
15725 gameInfo.round = StrSave("-");
15726 gameInfo.white = StrSave(first.tidy);
15727 gameInfo.black = StrSave(UserName());
15728 gameInfo.timeControl = TimeControlTagValue();
15731 case MachinePlaysBlack:
15732 gameInfo.event = StrSave( appData.pgnEventHeader );
15733 gameInfo.site = StrSave(HostName());
15734 gameInfo.date = PGNDate();
15735 gameInfo.round = StrSave("-");
15736 gameInfo.white = StrSave(UserName());
15737 gameInfo.black = StrSave(first.tidy);
15738 gameInfo.timeControl = TimeControlTagValue();
15741 case TwoMachinesPlay:
15742 gameInfo.event = StrSave( appData.pgnEventHeader );
15743 gameInfo.site = StrSave(HostName());
15744 gameInfo.date = PGNDate();
15747 snprintf(buf, MSG_SIZ, "%d", roundNr);
15748 gameInfo.round = StrSave(buf);
15750 gameInfo.round = StrSave("-");
15752 if (first.twoMachinesColor[0] == 'w') {
15753 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15754 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15756 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15757 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15759 gameInfo.timeControl = TimeControlTagValue();
15763 gameInfo.event = StrSave("Edited game");
15764 gameInfo.site = StrSave(HostName());
15765 gameInfo.date = PGNDate();
15766 gameInfo.round = StrSave("-");
15767 gameInfo.white = StrSave("-");
15768 gameInfo.black = StrSave("-");
15769 gameInfo.result = r;
15770 gameInfo.resultDetails = p;
15774 gameInfo.event = StrSave("Edited position");
15775 gameInfo.site = StrSave(HostName());
15776 gameInfo.date = PGNDate();
15777 gameInfo.round = StrSave("-");
15778 gameInfo.white = StrSave("-");
15779 gameInfo.black = StrSave("-");
15782 case IcsPlayingWhite:
15783 case IcsPlayingBlack:
15788 case PlayFromGameFile:
15789 gameInfo.event = StrSave("Game from non-PGN file");
15790 gameInfo.site = StrSave(HostName());
15791 gameInfo.date = PGNDate();
15792 gameInfo.round = StrSave("-");
15793 gameInfo.white = StrSave("?");
15794 gameInfo.black = StrSave("?");
15803 ReplaceComment (int index, char *text)
15809 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15810 pvInfoList[index-1].depth == len &&
15811 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15812 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15813 while (*text == '\n') text++;
15814 len = strlen(text);
15815 while (len > 0 && text[len - 1] == '\n') len--;
15817 if (commentList[index] != NULL)
15818 free(commentList[index]);
15821 commentList[index] = NULL;
15824 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15825 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15826 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15827 commentList[index] = (char *) malloc(len + 2);
15828 strncpy(commentList[index], text, len);
15829 commentList[index][len] = '\n';
15830 commentList[index][len + 1] = NULLCHAR;
15832 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15834 commentList[index] = (char *) malloc(len + 7);
15835 safeStrCpy(commentList[index], "{\n", 3);
15836 safeStrCpy(commentList[index]+2, text, len+1);
15837 commentList[index][len+2] = NULLCHAR;
15838 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15839 strcat(commentList[index], "\n}\n");
15844 CrushCRs (char *text)
15852 if (ch == '\r') continue;
15854 } while (ch != '\0');
15858 AppendComment (int index, char *text, Boolean addBraces)
15859 /* addBraces tells if we should add {} */
15864 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15865 if(addBraces == 3) addBraces = 0; else // force appending literally
15866 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15869 while (*text == '\n') text++;
15870 len = strlen(text);
15871 while (len > 0 && text[len - 1] == '\n') len--;
15872 text[len] = NULLCHAR;
15874 if (len == 0) return;
15876 if (commentList[index] != NULL) {
15877 Boolean addClosingBrace = addBraces;
15878 old = commentList[index];
15879 oldlen = strlen(old);
15880 while(commentList[index][oldlen-1] == '\n')
15881 commentList[index][--oldlen] = NULLCHAR;
15882 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15883 safeStrCpy(commentList[index], old, oldlen + len + 6);
15885 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15886 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15887 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15888 while (*text == '\n') { text++; len--; }
15889 commentList[index][--oldlen] = NULLCHAR;
15891 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15892 else strcat(commentList[index], "\n");
15893 strcat(commentList[index], text);
15894 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15895 else strcat(commentList[index], "\n");
15897 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15899 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15900 else commentList[index][0] = NULLCHAR;
15901 strcat(commentList[index], text);
15902 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15903 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15908 FindStr (char * text, char * sub_text)
15910 char * result = strstr( text, sub_text );
15912 if( result != NULL ) {
15913 result += strlen( sub_text );
15919 /* [AS] Try to extract PV info from PGN comment */
15920 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15922 GetInfoFromComment (int index, char * text)
15924 char * sep = text, *p;
15926 if( text != NULL && index > 0 ) {
15929 int time = -1, sec = 0, deci;
15930 char * s_eval = FindStr( text, "[%eval " );
15931 char * s_emt = FindStr( text, "[%emt " );
15933 if( s_eval != NULL || s_emt != NULL ) {
15935 if(0) { // [HGM] this code is not finished, and could actually be detrimental
15940 if( s_eval != NULL ) {
15941 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15945 if( delim != ']' ) {
15950 if( s_emt != NULL ) {
15955 /* We expect something like: [+|-]nnn.nn/dd */
15958 if(*text != '{') return text; // [HGM] braces: must be normal comment
15960 sep = strchr( text, '/' );
15961 if( sep == NULL || sep < (text+4) ) {
15966 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15967 if(p[1] == '(') { // comment starts with PV
15968 p = strchr(p, ')'); // locate end of PV
15969 if(p == NULL || sep < p+5) return text;
15970 // at this point we have something like "{(.*) +0.23/6 ..."
15971 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15972 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15973 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15975 time = -1; sec = -1; deci = -1;
15976 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15977 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15978 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15979 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15983 if( score_lo < 0 || score_lo >= 100 ) {
15987 if(sec >= 0) time = 600*time + 10*sec; else
15988 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15990 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15992 /* [HGM] PV time: now locate end of PV info */
15993 while( *++sep >= '0' && *sep <= '9'); // strip depth
15995 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15997 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15999 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16000 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16011 pvInfoList[index-1].depth = depth;
16012 pvInfoList[index-1].score = score;
16013 pvInfoList[index-1].time = 10*time; // centi-sec
16014 if(*sep == '}') *sep = 0; else *--sep = '{';
16015 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16021 SendToProgram (char *message, ChessProgramState *cps)
16023 int count, outCount, error;
16026 if (cps->pr == NoProc) return;
16029 if (appData.debugMode) {
16032 fprintf(debugFP, "%ld >%-6s: %s",
16033 SubtractTimeMarks(&now, &programStartTime),
16034 cps->which, message);
16036 fprintf(serverFP, "%ld >%-6s: %s",
16037 SubtractTimeMarks(&now, &programStartTime),
16038 cps->which, message), fflush(serverFP);
16041 count = strlen(message);
16042 outCount = OutputToProcess(cps->pr, message, count, &error);
16043 if (outCount < count && !exiting
16044 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16045 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16046 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16047 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16048 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16049 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16050 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16051 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16053 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16054 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16055 gameInfo.result = res;
16057 gameInfo.resultDetails = StrSave(buf);
16059 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16060 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16065 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16069 ChessProgramState *cps = (ChessProgramState *)closure;
16071 if (isr != cps->isr) return; /* Killed intentionally */
16074 RemoveInputSource(cps->isr);
16075 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16076 _(cps->which), cps->program);
16077 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16078 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16079 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16080 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16081 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16082 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16084 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16085 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16086 gameInfo.result = res;
16088 gameInfo.resultDetails = StrSave(buf);
16090 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16091 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16093 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16094 _(cps->which), cps->program);
16095 RemoveInputSource(cps->isr);
16097 /* [AS] Program is misbehaving badly... kill it */
16098 if( count == -2 ) {
16099 DestroyChildProcess( cps->pr, 9 );
16103 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16108 if ((end_str = strchr(message, '\r')) != NULL)
16109 *end_str = NULLCHAR;
16110 if ((end_str = strchr(message, '\n')) != NULL)
16111 *end_str = NULLCHAR;
16113 if (appData.debugMode) {
16114 TimeMark now; int print = 1;
16115 char *quote = ""; char c; int i;
16117 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16118 char start = message[0];
16119 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16120 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16121 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16122 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16123 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16124 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16125 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16126 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16127 sscanf(message, "hint: %c", &c)!=1 &&
16128 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16129 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16130 print = (appData.engineComments >= 2);
16132 message[0] = start; // restore original message
16136 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16137 SubtractTimeMarks(&now, &programStartTime), cps->which,
16141 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16142 SubtractTimeMarks(&now, &programStartTime), cps->which,
16144 message), fflush(serverFP);
16148 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16149 if (appData.icsEngineAnalyze) {
16150 if (strstr(message, "whisper") != NULL ||
16151 strstr(message, "kibitz") != NULL ||
16152 strstr(message, "tellics") != NULL) return;
16155 HandleMachineMove(message, cps);
16160 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16165 if( timeControl_2 > 0 ) {
16166 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16167 tc = timeControl_2;
16170 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16171 inc /= cps->timeOdds;
16172 st /= cps->timeOdds;
16174 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16177 /* Set exact time per move, normally using st command */
16178 if (cps->stKludge) {
16179 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16181 if (seconds == 0) {
16182 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16184 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16187 snprintf(buf, MSG_SIZ, "st %d\n", st);
16190 /* Set conventional or incremental time control, using level command */
16191 if (seconds == 0) {
16192 /* Note old gnuchess bug -- minutes:seconds used to not work.
16193 Fixed in later versions, but still avoid :seconds
16194 when seconds is 0. */
16195 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16197 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16198 seconds, inc/1000.);
16201 SendToProgram(buf, cps);
16203 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16204 /* Orthogonally, limit search to given depth */
16206 if (cps->sdKludge) {
16207 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16209 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16211 SendToProgram(buf, cps);
16214 if(cps->nps >= 0) { /* [HGM] nps */
16215 if(cps->supportsNPS == FALSE)
16216 cps->nps = -1; // don't use if engine explicitly says not supported!
16218 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16219 SendToProgram(buf, cps);
16224 ChessProgramState *
16226 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16228 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16229 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16235 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16237 char message[MSG_SIZ];
16240 /* Note: this routine must be called when the clocks are stopped
16241 or when they have *just* been set or switched; otherwise
16242 it will be off by the time since the current tick started.
16244 if (machineWhite) {
16245 time = whiteTimeRemaining / 10;
16246 otime = blackTimeRemaining / 10;
16248 time = blackTimeRemaining / 10;
16249 otime = whiteTimeRemaining / 10;
16251 /* [HGM] translate opponent's time by time-odds factor */
16252 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16254 if (time <= 0) time = 1;
16255 if (otime <= 0) otime = 1;
16257 snprintf(message, MSG_SIZ, "time %ld\n", time);
16258 SendToProgram(message, cps);
16260 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16261 SendToProgram(message, cps);
16265 EngineDefinedVariant (ChessProgramState *cps, int n)
16266 { // return name of n-th unknown variant that engine supports
16267 static char buf[MSG_SIZ];
16268 char *p, *s = cps->variants;
16269 if(!s) return NULL;
16270 do { // parse string from variants feature
16272 p = strchr(s, ',');
16273 if(p) *p = NULLCHAR;
16274 v = StringToVariant(s);
16275 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16276 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16277 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16280 if(n < 0) return buf;
16286 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16289 int len = strlen(name);
16292 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16294 sscanf(*p, "%d", &val);
16296 while (**p && **p != ' ')
16298 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16299 SendToProgram(buf, cps);
16306 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16309 int len = strlen(name);
16310 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16312 sscanf(*p, "%d", loc);
16313 while (**p && **p != ' ') (*p)++;
16314 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16315 SendToProgram(buf, cps);
16322 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16325 int len = strlen(name);
16326 if (strncmp((*p), name, len) == 0
16327 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16329 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16330 sscanf(*p, "%[^\"]", *loc);
16331 while (**p && **p != '\"') (*p)++;
16332 if (**p == '\"') (*p)++;
16333 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16334 SendToProgram(buf, cps);
16341 ParseOption (Option *opt, ChessProgramState *cps)
16342 // [HGM] options: process the string that defines an engine option, and determine
16343 // name, type, default value, and allowed value range
16345 char *p, *q, buf[MSG_SIZ];
16346 int n, min = (-1)<<31, max = 1<<31, def;
16348 if(p = strstr(opt->name, " -spin ")) {
16349 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16350 if(max < min) max = min; // enforce consistency
16351 if(def < min) def = min;
16352 if(def > max) def = max;
16357 } else if((p = strstr(opt->name, " -slider "))) {
16358 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16359 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16360 if(max < min) max = min; // enforce consistency
16361 if(def < min) def = min;
16362 if(def > max) def = max;
16366 opt->type = Spin; // Slider;
16367 } else if((p = strstr(opt->name, " -string "))) {
16368 opt->textValue = p+9;
16369 opt->type = TextBox;
16370 } else if((p = strstr(opt->name, " -file "))) {
16371 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16372 opt->textValue = p+7;
16373 opt->type = FileName; // FileName;
16374 } else if((p = strstr(opt->name, " -path "))) {
16375 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16376 opt->textValue = p+7;
16377 opt->type = PathName; // PathName;
16378 } else if(p = strstr(opt->name, " -check ")) {
16379 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16380 opt->value = (def != 0);
16381 opt->type = CheckBox;
16382 } else if(p = strstr(opt->name, " -combo ")) {
16383 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16384 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16385 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16386 opt->value = n = 0;
16387 while(q = StrStr(q, " /// ")) {
16388 n++; *q = 0; // count choices, and null-terminate each of them
16390 if(*q == '*') { // remember default, which is marked with * prefix
16394 cps->comboList[cps->comboCnt++] = q;
16396 cps->comboList[cps->comboCnt++] = NULL;
16398 opt->type = ComboBox;
16399 } else if(p = strstr(opt->name, " -button")) {
16400 opt->type = Button;
16401 } else if(p = strstr(opt->name, " -save")) {
16402 opt->type = SaveButton;
16403 } else return FALSE;
16404 *p = 0; // terminate option name
16405 // now look if the command-line options define a setting for this engine option.
16406 if(cps->optionSettings && cps->optionSettings[0])
16407 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16408 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16409 snprintf(buf, MSG_SIZ, "option %s", p);
16410 if(p = strstr(buf, ",")) *p = 0;
16411 if(q = strchr(buf, '=')) switch(opt->type) {
16413 for(n=0; n<opt->max; n++)
16414 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16417 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16421 opt->value = atoi(q+1);
16426 SendToProgram(buf, cps);
16432 FeatureDone (ChessProgramState *cps, int val)
16434 DelayedEventCallback cb = GetDelayedEvent();
16435 if ((cb == InitBackEnd3 && cps == &first) ||
16436 (cb == SettingsMenuIfReady && cps == &second) ||
16437 (cb == LoadEngine) ||
16438 (cb == TwoMachinesEventIfReady)) {
16439 CancelDelayedEvent();
16440 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16442 cps->initDone = val;
16443 if(val) cps->reload = FALSE;
16446 /* Parse feature command from engine */
16448 ParseFeatures (char *args, ChessProgramState *cps)
16456 while (*p == ' ') p++;
16457 if (*p == NULLCHAR) return;
16459 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16460 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16461 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16462 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16463 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16464 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16465 if (BoolFeature(&p, "reuse", &val, cps)) {
16466 /* Engine can disable reuse, but can't enable it if user said no */
16467 if (!val) cps->reuse = FALSE;
16470 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16471 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16472 if (gameMode == TwoMachinesPlay) {
16473 DisplayTwoMachinesTitle();
16479 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16480 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16481 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16482 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16483 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16484 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16485 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16486 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16487 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16488 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16489 if (IntFeature(&p, "done", &val, cps)) {
16490 FeatureDone(cps, val);
16493 /* Added by Tord: */
16494 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16495 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16496 /* End of additions by Tord */
16498 /* [HGM] added features: */
16499 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16500 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16501 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16502 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16503 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16504 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16505 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16506 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16507 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16508 FREE(cps->option[cps->nrOptions].name);
16509 cps->option[cps->nrOptions].name = q; q = NULL;
16510 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16511 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16512 SendToProgram(buf, cps);
16515 if(cps->nrOptions >= MAX_OPTIONS) {
16517 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16518 DisplayError(buf, 0);
16522 /* End of additions by HGM */
16524 /* unknown feature: complain and skip */
16526 while (*q && *q != '=') q++;
16527 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16528 SendToProgram(buf, cps);
16534 while (*p && *p != '\"') p++;
16535 if (*p == '\"') p++;
16537 while (*p && *p != ' ') p++;
16545 PeriodicUpdatesEvent (int newState)
16547 if (newState == appData.periodicUpdates)
16550 appData.periodicUpdates=newState;
16552 /* Display type changes, so update it now */
16553 // DisplayAnalysis();
16555 /* Get the ball rolling again... */
16557 AnalysisPeriodicEvent(1);
16558 StartAnalysisClock();
16563 PonderNextMoveEvent (int newState)
16565 if (newState == appData.ponderNextMove) return;
16566 if (gameMode == EditPosition) EditPositionDone(TRUE);
16568 SendToProgram("hard\n", &first);
16569 if (gameMode == TwoMachinesPlay) {
16570 SendToProgram("hard\n", &second);
16573 SendToProgram("easy\n", &first);
16574 thinkOutput[0] = NULLCHAR;
16575 if (gameMode == TwoMachinesPlay) {
16576 SendToProgram("easy\n", &second);
16579 appData.ponderNextMove = newState;
16583 NewSettingEvent (int option, int *feature, char *command, int value)
16587 if (gameMode == EditPosition) EditPositionDone(TRUE);
16588 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16589 if(feature == NULL || *feature) SendToProgram(buf, &first);
16590 if (gameMode == TwoMachinesPlay) {
16591 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16596 ShowThinkingEvent ()
16597 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16599 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16600 int newState = appData.showThinking
16601 // [HGM] thinking: other features now need thinking output as well
16602 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16604 if (oldState == newState) return;
16605 oldState = newState;
16606 if (gameMode == EditPosition) EditPositionDone(TRUE);
16608 SendToProgram("post\n", &first);
16609 if (gameMode == TwoMachinesPlay) {
16610 SendToProgram("post\n", &second);
16613 SendToProgram("nopost\n", &first);
16614 thinkOutput[0] = NULLCHAR;
16615 if (gameMode == TwoMachinesPlay) {
16616 SendToProgram("nopost\n", &second);
16619 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16623 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16625 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16626 if (pr == NoProc) return;
16627 AskQuestion(title, question, replyPrefix, pr);
16631 TypeInEvent (char firstChar)
16633 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16634 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16635 gameMode == AnalyzeMode || gameMode == EditGame ||
16636 gameMode == EditPosition || gameMode == IcsExamining ||
16637 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16638 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16639 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16640 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16641 gameMode == Training) PopUpMoveDialog(firstChar);
16645 TypeInDoneEvent (char *move)
16648 int n, fromX, fromY, toX, toY;
16650 ChessMove moveType;
16653 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16654 EditPositionPasteFEN(move);
16657 // [HGM] movenum: allow move number to be typed in any mode
16658 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16662 // undocumented kludge: allow command-line option to be typed in!
16663 // (potentially fatal, and does not implement the effect of the option.)
16664 // should only be used for options that are values on which future decisions will be made,
16665 // and definitely not on options that would be used during initialization.
16666 if(strstr(move, "!!! -") == move) {
16667 ParseArgsFromString(move+4);
16671 if (gameMode != EditGame && currentMove != forwardMostMove &&
16672 gameMode != Training) {
16673 DisplayMoveError(_("Displayed move is not current"));
16675 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16676 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16677 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16678 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16679 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16680 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16682 DisplayMoveError(_("Could not parse move"));
16688 DisplayMove (int moveNumber)
16690 char message[MSG_SIZ];
16692 char cpThinkOutput[MSG_SIZ];
16694 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16696 if (moveNumber == forwardMostMove - 1 ||
16697 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16699 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16701 if (strchr(cpThinkOutput, '\n')) {
16702 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16705 *cpThinkOutput = NULLCHAR;
16708 /* [AS] Hide thinking from human user */
16709 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16710 *cpThinkOutput = NULLCHAR;
16711 if( thinkOutput[0] != NULLCHAR ) {
16714 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16715 cpThinkOutput[i] = '.';
16717 cpThinkOutput[i] = NULLCHAR;
16718 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16722 if (moveNumber == forwardMostMove - 1 &&
16723 gameInfo.resultDetails != NULL) {
16724 if (gameInfo.resultDetails[0] == NULLCHAR) {
16725 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16727 snprintf(res, MSG_SIZ, " {%s} %s",
16728 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16734 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16735 DisplayMessage(res, cpThinkOutput);
16737 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16738 WhiteOnMove(moveNumber) ? " " : ".. ",
16739 parseList[moveNumber], res);
16740 DisplayMessage(message, cpThinkOutput);
16745 DisplayComment (int moveNumber, char *text)
16747 char title[MSG_SIZ];
16749 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16750 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16752 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16753 WhiteOnMove(moveNumber) ? " " : ".. ",
16754 parseList[moveNumber]);
16756 if (text != NULL && (appData.autoDisplayComment || commentUp))
16757 CommentPopUp(title, text);
16760 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16761 * might be busy thinking or pondering. It can be omitted if your
16762 * gnuchess is configured to stop thinking immediately on any user
16763 * input. However, that gnuchess feature depends on the FIONREAD
16764 * ioctl, which does not work properly on some flavors of Unix.
16767 Attention (ChessProgramState *cps)
16770 if (!cps->useSigint) return;
16771 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16772 switch (gameMode) {
16773 case MachinePlaysWhite:
16774 case MachinePlaysBlack:
16775 case TwoMachinesPlay:
16776 case IcsPlayingWhite:
16777 case IcsPlayingBlack:
16780 /* Skip if we know it isn't thinking */
16781 if (!cps->maybeThinking) return;
16782 if (appData.debugMode)
16783 fprintf(debugFP, "Interrupting %s\n", cps->which);
16784 InterruptChildProcess(cps->pr);
16785 cps->maybeThinking = FALSE;
16790 #endif /*ATTENTION*/
16796 if (whiteTimeRemaining <= 0) {
16799 if (appData.icsActive) {
16800 if (appData.autoCallFlag &&
16801 gameMode == IcsPlayingBlack && !blackFlag) {
16802 SendToICS(ics_prefix);
16803 SendToICS("flag\n");
16807 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16809 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16810 if (appData.autoCallFlag) {
16811 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16818 if (blackTimeRemaining <= 0) {
16821 if (appData.icsActive) {
16822 if (appData.autoCallFlag &&
16823 gameMode == IcsPlayingWhite && !whiteFlag) {
16824 SendToICS(ics_prefix);
16825 SendToICS("flag\n");
16829 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16831 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16832 if (appData.autoCallFlag) {
16833 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16844 CheckTimeControl ()
16846 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16847 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16850 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16852 if ( !WhiteOnMove(forwardMostMove) ) {
16853 /* White made time control */
16854 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16855 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16856 /* [HGM] time odds: correct new time quota for time odds! */
16857 / WhitePlayer()->timeOdds;
16858 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16860 lastBlack -= blackTimeRemaining;
16861 /* Black made time control */
16862 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16863 / WhitePlayer()->other->timeOdds;
16864 lastWhite = whiteTimeRemaining;
16869 DisplayBothClocks ()
16871 int wom = gameMode == EditPosition ?
16872 !blackPlaysFirst : WhiteOnMove(currentMove);
16873 DisplayWhiteClock(whiteTimeRemaining, wom);
16874 DisplayBlackClock(blackTimeRemaining, !wom);
16878 /* Timekeeping seems to be a portability nightmare. I think everyone
16879 has ftime(), but I'm really not sure, so I'm including some ifdefs
16880 to use other calls if you don't. Clocks will be less accurate if
16881 you have neither ftime nor gettimeofday.
16884 /* VS 2008 requires the #include outside of the function */
16885 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16886 #include <sys/timeb.h>
16889 /* Get the current time as a TimeMark */
16891 GetTimeMark (TimeMark *tm)
16893 #if HAVE_GETTIMEOFDAY
16895 struct timeval timeVal;
16896 struct timezone timeZone;
16898 gettimeofday(&timeVal, &timeZone);
16899 tm->sec = (long) timeVal.tv_sec;
16900 tm->ms = (int) (timeVal.tv_usec / 1000L);
16902 #else /*!HAVE_GETTIMEOFDAY*/
16905 // include <sys/timeb.h> / moved to just above start of function
16906 struct timeb timeB;
16909 tm->sec = (long) timeB.time;
16910 tm->ms = (int) timeB.millitm;
16912 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16913 tm->sec = (long) time(NULL);
16919 /* Return the difference in milliseconds between two
16920 time marks. We assume the difference will fit in a long!
16923 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16925 return 1000L*(tm2->sec - tm1->sec) +
16926 (long) (tm2->ms - tm1->ms);
16931 * Code to manage the game clocks.
16933 * In tournament play, black starts the clock and then white makes a move.
16934 * We give the human user a slight advantage if he is playing white---the
16935 * clocks don't run until he makes his first move, so it takes zero time.
16936 * Also, we don't account for network lag, so we could get out of sync
16937 * with GNU Chess's clock -- but then, referees are always right.
16940 static TimeMark tickStartTM;
16941 static long intendedTickLength;
16944 NextTickLength (long timeRemaining)
16946 long nominalTickLength, nextTickLength;
16948 if (timeRemaining > 0L && timeRemaining <= 10000L)
16949 nominalTickLength = 100L;
16951 nominalTickLength = 1000L;
16952 nextTickLength = timeRemaining % nominalTickLength;
16953 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16955 return nextTickLength;
16958 /* Adjust clock one minute up or down */
16960 AdjustClock (Boolean which, int dir)
16962 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16963 if(which) blackTimeRemaining += 60000*dir;
16964 else whiteTimeRemaining += 60000*dir;
16965 DisplayBothClocks();
16966 adjustedClock = TRUE;
16969 /* Stop clocks and reset to a fresh time control */
16973 (void) StopClockTimer();
16974 if (appData.icsActive) {
16975 whiteTimeRemaining = blackTimeRemaining = 0;
16976 } else if (searchTime) {
16977 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16978 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16979 } else { /* [HGM] correct new time quote for time odds */
16980 whiteTC = blackTC = fullTimeControlString;
16981 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16982 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16984 if (whiteFlag || blackFlag) {
16986 whiteFlag = blackFlag = FALSE;
16988 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16989 DisplayBothClocks();
16990 adjustedClock = FALSE;
16993 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16995 /* Decrement running clock by amount of time that has passed */
16999 long timeRemaining;
17000 long lastTickLength, fudge;
17003 if (!appData.clockMode) return;
17004 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17008 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17010 /* Fudge if we woke up a little too soon */
17011 fudge = intendedTickLength - lastTickLength;
17012 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17014 if (WhiteOnMove(forwardMostMove)) {
17015 if(whiteNPS >= 0) lastTickLength = 0;
17016 timeRemaining = whiteTimeRemaining -= lastTickLength;
17017 if(timeRemaining < 0 && !appData.icsActive) {
17018 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17019 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17020 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17021 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17024 DisplayWhiteClock(whiteTimeRemaining - fudge,
17025 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17027 if(blackNPS >= 0) lastTickLength = 0;
17028 timeRemaining = blackTimeRemaining -= lastTickLength;
17029 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17030 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17032 blackStartMove = forwardMostMove;
17033 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17036 DisplayBlackClock(blackTimeRemaining - fudge,
17037 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17039 if (CheckFlags()) return;
17041 if(twoBoards) { // count down secondary board's clocks as well
17042 activePartnerTime -= lastTickLength;
17044 if(activePartner == 'W')
17045 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17047 DisplayBlackClock(activePartnerTime, TRUE);
17052 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17053 StartClockTimer(intendedTickLength);
17055 /* if the time remaining has fallen below the alarm threshold, sound the
17056 * alarm. if the alarm has sounded and (due to a takeback or time control
17057 * with increment) the time remaining has increased to a level above the
17058 * threshold, reset the alarm so it can sound again.
17061 if (appData.icsActive && appData.icsAlarm) {
17063 /* make sure we are dealing with the user's clock */
17064 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17065 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17068 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17069 alarmSounded = FALSE;
17070 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17072 alarmSounded = TRUE;
17078 /* A player has just moved, so stop the previously running
17079 clock and (if in clock mode) start the other one.
17080 We redisplay both clocks in case we're in ICS mode, because
17081 ICS gives us an update to both clocks after every move.
17082 Note that this routine is called *after* forwardMostMove
17083 is updated, so the last fractional tick must be subtracted
17084 from the color that is *not* on move now.
17087 SwitchClocks (int newMoveNr)
17089 long lastTickLength;
17091 int flagged = FALSE;
17095 if (StopClockTimer() && appData.clockMode) {
17096 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17097 if (!WhiteOnMove(forwardMostMove)) {
17098 if(blackNPS >= 0) lastTickLength = 0;
17099 blackTimeRemaining -= lastTickLength;
17100 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17101 // if(pvInfoList[forwardMostMove].time == -1)
17102 pvInfoList[forwardMostMove].time = // use GUI time
17103 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17105 if(whiteNPS >= 0) lastTickLength = 0;
17106 whiteTimeRemaining -= lastTickLength;
17107 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17108 // if(pvInfoList[forwardMostMove].time == -1)
17109 pvInfoList[forwardMostMove].time =
17110 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17112 flagged = CheckFlags();
17114 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17115 CheckTimeControl();
17117 if (flagged || !appData.clockMode) return;
17119 switch (gameMode) {
17120 case MachinePlaysBlack:
17121 case MachinePlaysWhite:
17122 case BeginningOfGame:
17123 if (pausing) return;
17127 case PlayFromGameFile:
17135 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17136 if(WhiteOnMove(forwardMostMove))
17137 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17138 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17142 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17143 whiteTimeRemaining : blackTimeRemaining);
17144 StartClockTimer(intendedTickLength);
17148 /* Stop both clocks */
17152 long lastTickLength;
17155 if (!StopClockTimer()) return;
17156 if (!appData.clockMode) return;
17160 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17161 if (WhiteOnMove(forwardMostMove)) {
17162 if(whiteNPS >= 0) lastTickLength = 0;
17163 whiteTimeRemaining -= lastTickLength;
17164 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17166 if(blackNPS >= 0) lastTickLength = 0;
17167 blackTimeRemaining -= lastTickLength;
17168 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17173 /* Start clock of player on move. Time may have been reset, so
17174 if clock is already running, stop and restart it. */
17178 (void) StopClockTimer(); /* in case it was running already */
17179 DisplayBothClocks();
17180 if (CheckFlags()) return;
17182 if (!appData.clockMode) return;
17183 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17185 GetTimeMark(&tickStartTM);
17186 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17187 whiteTimeRemaining : blackTimeRemaining);
17189 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17190 whiteNPS = blackNPS = -1;
17191 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17192 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17193 whiteNPS = first.nps;
17194 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17195 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17196 blackNPS = first.nps;
17197 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17198 whiteNPS = second.nps;
17199 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17200 blackNPS = second.nps;
17201 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17203 StartClockTimer(intendedTickLength);
17207 TimeString (long ms)
17209 long second, minute, hour, day;
17211 static char buf[32];
17213 if (ms > 0 && ms <= 9900) {
17214 /* convert milliseconds to tenths, rounding up */
17215 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17217 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17221 /* convert milliseconds to seconds, rounding up */
17222 /* use floating point to avoid strangeness of integer division
17223 with negative dividends on many machines */
17224 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17231 day = second / (60 * 60 * 24);
17232 second = second % (60 * 60 * 24);
17233 hour = second / (60 * 60);
17234 second = second % (60 * 60);
17235 minute = second / 60;
17236 second = second % 60;
17239 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17240 sign, day, hour, minute, second);
17242 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17244 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17251 * This is necessary because some C libraries aren't ANSI C compliant yet.
17254 StrStr (char *string, char *match)
17258 length = strlen(match);
17260 for (i = strlen(string) - length; i >= 0; i--, string++)
17261 if (!strncmp(match, string, length))
17268 StrCaseStr (char *string, char *match)
17272 length = strlen(match);
17274 for (i = strlen(string) - length; i >= 0; i--, string++) {
17275 for (j = 0; j < length; j++) {
17276 if (ToLower(match[j]) != ToLower(string[j]))
17279 if (j == length) return string;
17287 StrCaseCmp (char *s1, char *s2)
17292 c1 = ToLower(*s1++);
17293 c2 = ToLower(*s2++);
17294 if (c1 > c2) return 1;
17295 if (c1 < c2) return -1;
17296 if (c1 == NULLCHAR) return 0;
17304 return isupper(c) ? tolower(c) : c;
17311 return islower(c) ? toupper(c) : c;
17313 #endif /* !_amigados */
17320 if ((ret = (char *) malloc(strlen(s) + 1)))
17322 safeStrCpy(ret, s, strlen(s)+1);
17328 StrSavePtr (char *s, char **savePtr)
17333 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17334 safeStrCpy(*savePtr, s, strlen(s)+1);
17346 clock = time((time_t *)NULL);
17347 tm = localtime(&clock);
17348 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17349 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17350 return StrSave(buf);
17355 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17357 int i, j, fromX, fromY, toX, toY;
17364 whiteToPlay = (gameMode == EditPosition) ?
17365 !blackPlaysFirst : (move % 2 == 0);
17368 /* Piece placement data */
17369 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17370 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17372 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17373 if (boards[move][i][j] == EmptySquare) {
17375 } else { ChessSquare piece = boards[move][i][j];
17376 if (emptycount > 0) {
17377 if(emptycount<10) /* [HGM] can be >= 10 */
17378 *p++ = '0' + emptycount;
17379 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17382 if(PieceToChar(piece) == '+') {
17383 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17385 piece = (ChessSquare)(DEMOTED piece);
17387 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17389 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17390 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17395 if (emptycount > 0) {
17396 if(emptycount<10) /* [HGM] can be >= 10 */
17397 *p++ = '0' + emptycount;
17398 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17405 /* [HGM] print Crazyhouse or Shogi holdings */
17406 if( gameInfo.holdingsWidth ) {
17407 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17409 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17410 piece = boards[move][i][BOARD_WIDTH-1];
17411 if( piece != EmptySquare )
17412 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17413 *p++ = PieceToChar(piece);
17415 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17416 piece = boards[move][BOARD_HEIGHT-i-1][0];
17417 if( piece != EmptySquare )
17418 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17419 *p++ = PieceToChar(piece);
17422 if( q == p ) *p++ = '-';
17428 *p++ = whiteToPlay ? 'w' : 'b';
17431 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17432 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17434 if(nrCastlingRights) {
17436 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17437 /* [HGM] write directly from rights */
17438 if(boards[move][CASTLING][2] != NoRights &&
17439 boards[move][CASTLING][0] != NoRights )
17440 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17441 if(boards[move][CASTLING][2] != NoRights &&
17442 boards[move][CASTLING][1] != NoRights )
17443 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17444 if(boards[move][CASTLING][5] != NoRights &&
17445 boards[move][CASTLING][3] != NoRights )
17446 *p++ = boards[move][CASTLING][3] + AAA;
17447 if(boards[move][CASTLING][5] != NoRights &&
17448 boards[move][CASTLING][4] != NoRights )
17449 *p++ = boards[move][CASTLING][4] + AAA;
17452 /* [HGM] write true castling rights */
17453 if( nrCastlingRights == 6 ) {
17455 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17456 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17457 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17458 boards[move][CASTLING][2] != NoRights );
17459 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17460 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17461 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17462 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17463 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17467 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17468 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17469 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17470 boards[move][CASTLING][5] != NoRights );
17471 if(gameInfo.variant == VariantSChess) {
17472 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17473 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17474 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17475 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17480 if (q == p) *p++ = '-'; /* No castling rights */
17484 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17485 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17486 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17487 /* En passant target square */
17488 if (move > backwardMostMove) {
17489 fromX = moveList[move - 1][0] - AAA;
17490 fromY = moveList[move - 1][1] - ONE;
17491 toX = moveList[move - 1][2] - AAA;
17492 toY = moveList[move - 1][3] - ONE;
17493 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17494 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17495 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17497 /* 2-square pawn move just happened */
17499 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17503 } else if(move == backwardMostMove) {
17504 // [HGM] perhaps we should always do it like this, and forget the above?
17505 if((signed char)boards[move][EP_STATUS] >= 0) {
17506 *p++ = boards[move][EP_STATUS] + AAA;
17507 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17519 { int i = 0, j=move;
17521 /* [HGM] find reversible plies */
17522 if (appData.debugMode) { int k;
17523 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17524 for(k=backwardMostMove; k<=forwardMostMove; k++)
17525 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17529 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17530 if( j == backwardMostMove ) i += initialRulePlies;
17531 sprintf(p, "%d ", i);
17532 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17534 /* Fullmove number */
17535 sprintf(p, "%d", (move / 2) + 1);
17536 } else *--p = NULLCHAR;
17538 return StrSave(buf);
17542 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17546 int emptycount, virgin[BOARD_FILES];
17551 /* Piece placement data */
17552 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17555 if (*p == '/' || *p == ' ' || *p == '[' ) {
17557 emptycount = gameInfo.boardWidth - j;
17558 while (emptycount--)
17559 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17560 if (*p == '/') p++;
17561 else if(autoSize) { // we stumbled unexpectedly into end of board
17562 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17563 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17565 appData.NrRanks = gameInfo.boardHeight - i; i=0;
17568 #if(BOARD_FILES >= 10)
17569 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17570 p++; emptycount=10;
17571 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17572 while (emptycount--)
17573 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17575 } else if (*p == '*') {
17576 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17577 } else if (isdigit(*p)) {
17578 emptycount = *p++ - '0';
17579 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17580 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17581 while (emptycount--)
17582 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17583 } else if (*p == '+' || isalpha(*p)) {
17584 if (j >= gameInfo.boardWidth) return FALSE;
17586 piece = CharToPiece(*++p);
17587 if(piece == EmptySquare) return FALSE; /* unknown piece */
17588 piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17589 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17590 } else piece = CharToPiece(*p++);
17592 if(piece==EmptySquare) return FALSE; /* unknown piece */
17593 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17594 piece = (ChessSquare) (PROMOTED piece);
17595 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17598 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17604 while (*p == '/' || *p == ' ') p++;
17606 if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17608 /* [HGM] by default clear Crazyhouse holdings, if present */
17609 if(gameInfo.holdingsWidth) {
17610 for(i=0; i<BOARD_HEIGHT; i++) {
17611 board[i][0] = EmptySquare; /* black holdings */
17612 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17613 board[i][1] = (ChessSquare) 0; /* black counts */
17614 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17618 /* [HGM] look for Crazyhouse holdings here */
17619 while(*p==' ') p++;
17620 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17622 if(*p == '-' ) p++; /* empty holdings */ else {
17623 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17624 /* if we would allow FEN reading to set board size, we would */
17625 /* have to add holdings and shift the board read so far here */
17626 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17628 if((int) piece >= (int) BlackPawn ) {
17629 i = (int)piece - (int)BlackPawn;
17630 i = PieceToNumber((ChessSquare)i);
17631 if( i >= gameInfo.holdingsSize ) return FALSE;
17632 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17633 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17635 i = (int)piece - (int)WhitePawn;
17636 i = PieceToNumber((ChessSquare)i);
17637 if( i >= gameInfo.holdingsSize ) return FALSE;
17638 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17639 board[i][BOARD_WIDTH-2]++; /* black holdings */
17646 while(*p == ' ') p++;
17650 if(appData.colorNickNames) {
17651 if( c == appData.colorNickNames[0] ) c = 'w'; else
17652 if( c == appData.colorNickNames[1] ) c = 'b';
17656 *blackPlaysFirst = FALSE;
17659 *blackPlaysFirst = TRUE;
17665 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17666 /* return the extra info in global variiables */
17668 /* set defaults in case FEN is incomplete */
17669 board[EP_STATUS] = EP_UNKNOWN;
17670 for(i=0; i<nrCastlingRights; i++ ) {
17671 board[CASTLING][i] =
17672 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17673 } /* assume possible unless obviously impossible */
17674 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17675 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17676 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17677 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17678 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17679 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17680 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17681 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17684 while(*p==' ') p++;
17685 if(nrCastlingRights) {
17686 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17687 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17688 /* castling indicator present, so default becomes no castlings */
17689 for(i=0; i<nrCastlingRights; i++ ) {
17690 board[CASTLING][i] = NoRights;
17693 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17694 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17695 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17696 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17697 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17699 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17700 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17701 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17703 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17704 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17705 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17706 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17707 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17708 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17711 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17712 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17713 board[CASTLING][2] = whiteKingFile;
17714 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17715 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17718 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17719 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17720 board[CASTLING][2] = whiteKingFile;
17721 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17722 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17725 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17726 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17727 board[CASTLING][5] = blackKingFile;
17728 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17729 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17732 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17733 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17734 board[CASTLING][5] = blackKingFile;
17735 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17736 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17739 default: /* FRC castlings */
17740 if(c >= 'a') { /* black rights */
17741 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17742 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17743 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17744 if(i == BOARD_RGHT) break;
17745 board[CASTLING][5] = i;
17747 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17748 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17750 board[CASTLING][3] = c;
17752 board[CASTLING][4] = c;
17753 } else { /* white rights */
17754 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17755 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17756 if(board[0][i] == WhiteKing) break;
17757 if(i == BOARD_RGHT) break;
17758 board[CASTLING][2] = i;
17759 c -= AAA - 'a' + 'A';
17760 if(board[0][c] >= WhiteKing) break;
17762 board[CASTLING][0] = c;
17764 board[CASTLING][1] = c;
17768 for(i=0; i<nrCastlingRights; i++)
17769 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17770 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17771 if (appData.debugMode) {
17772 fprintf(debugFP, "FEN castling rights:");
17773 for(i=0; i<nrCastlingRights; i++)
17774 fprintf(debugFP, " %d", board[CASTLING][i]);
17775 fprintf(debugFP, "\n");
17778 while(*p==' ') p++;
17781 /* read e.p. field in games that know e.p. capture */
17782 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17783 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17784 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17786 p++; board[EP_STATUS] = EP_NONE;
17788 char c = *p++ - AAA;
17790 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17791 if(*p >= '0' && *p <='9') p++;
17792 board[EP_STATUS] = c;
17797 if(sscanf(p, "%d", &i) == 1) {
17798 FENrulePlies = i; /* 50-move ply counter */
17799 /* (The move number is still ignored) */
17806 EditPositionPasteFEN (char *fen)
17809 Board initial_position;
17811 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17812 DisplayError(_("Bad FEN position in clipboard"), 0);
17815 int savedBlackPlaysFirst = blackPlaysFirst;
17816 EditPositionEvent();
17817 blackPlaysFirst = savedBlackPlaysFirst;
17818 CopyBoard(boards[0], initial_position);
17819 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17820 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17821 DisplayBothClocks();
17822 DrawPosition(FALSE, boards[currentMove]);
17827 static char cseq[12] = "\\ ";
17830 set_cont_sequence (char *new_seq)
17835 // handle bad attempts to set the sequence
17837 return 0; // acceptable error - no debug
17839 len = strlen(new_seq);
17840 ret = (len > 0) && (len < sizeof(cseq));
17842 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17843 else if (appData.debugMode)
17844 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17849 reformat a source message so words don't cross the width boundary. internal
17850 newlines are not removed. returns the wrapped size (no null character unless
17851 included in source message). If dest is NULL, only calculate the size required
17852 for the dest buffer. lp argument indicats line position upon entry, and it's
17853 passed back upon exit.
17856 wrap (char *dest, char *src, int count, int width, int *lp)
17858 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17860 cseq_len = strlen(cseq);
17861 old_line = line = *lp;
17862 ansi = len = clen = 0;
17864 for (i=0; i < count; i++)
17866 if (src[i] == '\033')
17869 // if we hit the width, back up
17870 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17872 // store i & len in case the word is too long
17873 old_i = i, old_len = len;
17875 // find the end of the last word
17876 while (i && src[i] != ' ' && src[i] != '\n')
17882 // word too long? restore i & len before splitting it
17883 if ((old_i-i+clen) >= width)
17890 if (i && src[i-1] == ' ')
17893 if (src[i] != ' ' && src[i] != '\n')
17900 // now append the newline and continuation sequence
17905 strncpy(dest+len, cseq, cseq_len);
17913 dest[len] = src[i];
17917 if (src[i] == '\n')
17922 if (dest && appData.debugMode)
17924 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17925 count, width, line, len, *lp);
17926 show_bytes(debugFP, src, count);
17927 fprintf(debugFP, "\ndest: ");
17928 show_bytes(debugFP, dest, len);
17929 fprintf(debugFP, "\n");
17931 *lp = dest ? line : old_line;
17936 // [HGM] vari: routines for shelving variations
17937 Boolean modeRestore = FALSE;
17940 PushInner (int firstMove, int lastMove)
17942 int i, j, nrMoves = lastMove - firstMove;
17944 // push current tail of game on stack
17945 savedResult[storedGames] = gameInfo.result;
17946 savedDetails[storedGames] = gameInfo.resultDetails;
17947 gameInfo.resultDetails = NULL;
17948 savedFirst[storedGames] = firstMove;
17949 savedLast [storedGames] = lastMove;
17950 savedFramePtr[storedGames] = framePtr;
17951 framePtr -= nrMoves; // reserve space for the boards
17952 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17953 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17954 for(j=0; j<MOVE_LEN; j++)
17955 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17956 for(j=0; j<2*MOVE_LEN; j++)
17957 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17958 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17959 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17960 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17961 pvInfoList[firstMove+i-1].depth = 0;
17962 commentList[framePtr+i] = commentList[firstMove+i];
17963 commentList[firstMove+i] = NULL;
17967 forwardMostMove = firstMove; // truncate game so we can start variation
17971 PushTail (int firstMove, int lastMove)
17973 if(appData.icsActive) { // only in local mode
17974 forwardMostMove = currentMove; // mimic old ICS behavior
17977 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17979 PushInner(firstMove, lastMove);
17980 if(storedGames == 1) GreyRevert(FALSE);
17981 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17985 PopInner (Boolean annotate)
17988 char buf[8000], moveBuf[20];
17990 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17991 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17992 nrMoves = savedLast[storedGames] - currentMove;
17995 if(!WhiteOnMove(currentMove))
17996 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17997 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17998 for(i=currentMove; i<forwardMostMove; i++) {
18000 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18001 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18002 strcat(buf, moveBuf);
18003 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18004 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18008 for(i=1; i<=nrMoves; i++) { // copy last variation back
18009 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18010 for(j=0; j<MOVE_LEN; j++)
18011 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18012 for(j=0; j<2*MOVE_LEN; j++)
18013 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18014 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18015 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18016 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18017 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18018 commentList[currentMove+i] = commentList[framePtr+i];
18019 commentList[framePtr+i] = NULL;
18021 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18022 framePtr = savedFramePtr[storedGames];
18023 gameInfo.result = savedResult[storedGames];
18024 if(gameInfo.resultDetails != NULL) {
18025 free(gameInfo.resultDetails);
18027 gameInfo.resultDetails = savedDetails[storedGames];
18028 forwardMostMove = currentMove + nrMoves;
18032 PopTail (Boolean annotate)
18034 if(appData.icsActive) return FALSE; // only in local mode
18035 if(!storedGames) return FALSE; // sanity
18036 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18038 PopInner(annotate);
18039 if(currentMove < forwardMostMove) ForwardEvent(); else
18040 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18042 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18048 { // remove all shelved variations
18050 for(i=0; i<storedGames; i++) {
18051 if(savedDetails[i])
18052 free(savedDetails[i]);
18053 savedDetails[i] = NULL;
18055 for(i=framePtr; i<MAX_MOVES; i++) {
18056 if(commentList[i]) free(commentList[i]);
18057 commentList[i] = NULL;
18059 framePtr = MAX_MOVES-1;
18064 LoadVariation (int index, char *text)
18065 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18066 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18067 int level = 0, move;
18069 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18070 // first find outermost bracketing variation
18071 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18072 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18073 if(*p == '{') wait = '}'; else
18074 if(*p == '[') wait = ']'; else
18075 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18076 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18078 if(*p == wait) wait = NULLCHAR; // closing ]} found
18081 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18082 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18083 end[1] = NULLCHAR; // clip off comment beyond variation
18084 ToNrEvent(currentMove-1);
18085 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18086 // kludge: use ParsePV() to append variation to game
18087 move = currentMove;
18088 ParsePV(start, TRUE, TRUE);
18089 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18090 ClearPremoveHighlights();
18092 ToNrEvent(currentMove+1);
18098 char *p, *q, buf[MSG_SIZ];
18099 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18100 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18101 ParseArgsFromString(buf);
18102 ActivateTheme(TRUE); // also redo colors
18106 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18109 q = appData.themeNames;
18110 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18111 if(appData.useBitmaps) {
18112 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18113 appData.liteBackTextureFile, appData.darkBackTextureFile,
18114 appData.liteBackTextureMode,
18115 appData.darkBackTextureMode );
18117 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18118 Col2Text(2), // lightSquareColor
18119 Col2Text(3) ); // darkSquareColor
18121 if(appData.useBorder) {
18122 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18125 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18127 if(appData.useFont) {
18128 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18129 appData.renderPiecesWithFont,
18130 appData.fontToPieceTable,
18131 Col2Text(9), // appData.fontBackColorWhite
18132 Col2Text(10) ); // appData.fontForeColorBlack
18134 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18135 appData.pieceDirectory);
18136 if(!appData.pieceDirectory[0])
18137 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18138 Col2Text(0), // whitePieceColor
18139 Col2Text(1) ); // blackPieceColor
18141 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18142 Col2Text(4), // highlightSquareColor
18143 Col2Text(5) ); // premoveHighlightColor
18144 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18145 if(insert != q) insert[-1] = NULLCHAR;
18146 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18149 ActivateTheme(FALSE);