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, 2014, 2015, 2016 Free
9 * Software Foundation, Inc.
11 * Enhancements Copyright 2005 Alessandro Scotti
13 * The following terms apply to Digital Equipment Corporation's copyright
15 * ------------------------------------------------------------------------
18 * Permission to use, copy, modify, and distribute this software and its
19 * documentation for any purpose and without fee is hereby granted,
20 * provided that the above copyright notice appear in all copies and that
21 * both that copyright notice and this permission notice appear in
22 * supporting documentation, and that the name of Digital not be
23 * used in advertising or publicity pertaining to distribution of the
24 * software without specific, written prior permission.
26 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
33 * ------------------------------------------------------------------------
35 * The following terms apply to the enhanced version of XBoard
36 * distributed by the Free Software Foundation:
37 * ------------------------------------------------------------------------
39 * GNU XBoard is free software: you can redistribute it and/or modify
40 * it under the terms of the GNU General Public License as published by
41 * the Free Software Foundation, either version 3 of the License, or (at
42 * your option) any later version.
44 * GNU XBoard is distributed in the hope that it will be useful, but
45 * WITHOUT ANY WARRANTY; without even the implied warranty of
46 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47 * General Public License for more details.
49 * You should have received a copy of the GNU General Public License
50 * along with this program. If not, see http://www.gnu.org/licenses/. *
52 *------------------------------------------------------------------------
53 ** See the file ChangeLog for a revision history. */
55 /* [AS] Also useful here for debugging */
59 int flock(int f, int code);
64 # define EGBB_NAME "egbbdll64.dll"
66 # define EGBB_NAME "egbbdll.dll"
71 # include <sys/file.h>
76 # define EGBB_NAME "egbbso64.so"
78 # define EGBB_NAME "egbbso.so"
80 // kludge to allow Windows code in back-end by converting it to corresponding Linux code
82 # define HMODULE void *
83 # define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 # define GetProcAddress dlsym
94 #include <sys/types.h>
103 #else /* not STDC_HEADERS */
106 # else /* not HAVE_STRING_H */
107 # include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
124 # include <sys/time.h>
130 #if defined(_amigados) && !defined(__GNUC__)
135 extern int gettimeofday(struct timeval *, struct timezone *);
143 #include "frontend.h"
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173 char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175 char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188 /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199 char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201 int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
250 extern void ConsoleCreate();
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
272 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border; /* [HGM] width of board rim, needed to size seek graph */
299 char bestMove[MSG_SIZ];
300 int solvingTime, totalTime;
302 /* States for ics_getting_history */
304 #define H_REQUESTED 1
305 #define H_GOT_REQ_HEADER 2
306 #define H_GOT_UNREQ_HEADER 3
307 #define H_GETTING_MOVES 4
308 #define H_GOT_UNWANTED_HEADER 5
310 /* whosays values for GameEnds */
319 /* Maximum number of games in a cmail message */
320 #define CMAIL_MAX_GAMES 20
322 /* Different types of move when calling RegisterMove */
324 #define CMAIL_RESIGN 1
326 #define CMAIL_ACCEPT 3
328 /* Different types of result to remember for each game */
329 #define CMAIL_NOT_RESULT 0
330 #define CMAIL_OLD_RESULT 1
331 #define CMAIL_NEW_RESULT 2
333 /* Telnet protocol constants */
344 safeStrCpy (char *dst, const char *src, size_t count)
347 assert( dst != NULL );
348 assert( src != NULL );
351 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
352 if( i == count && dst[count-1] != NULLCHAR)
354 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
355 if(appData.debugMode)
356 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
362 /* Some compiler can't cast u64 to double
363 * This function do the job for us:
365 * We use the highest bit for cast, this only
366 * works if the highest bit is not
367 * in use (This should not happen)
369 * We used this for all compiler
372 u64ToDouble (u64 value)
375 u64 tmp = value & u64Const(0x7fffffffffffffff);
376 r = (double)(s64)tmp;
377 if (value & u64Const(0x8000000000000000))
378 r += 9.2233720368547758080e18; /* 2^63 */
382 /* Fake up flags for now, as we aren't keeping track of castling
383 availability yet. [HGM] Change of logic: the flag now only
384 indicates the type of castlings allowed by the rule of the game.
385 The actual rights themselves are maintained in the array
386 castlingRights, as part of the game history, and are not probed
392 int flags = F_ALL_CASTLE_OK;
393 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
394 switch (gameInfo.variant) {
396 flags &= ~F_ALL_CASTLE_OK;
397 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
398 flags |= F_IGNORE_CHECK;
400 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
403 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
405 case VariantKriegspiel:
406 flags |= F_KRIEGSPIEL_CAPTURE;
408 case VariantCapaRandom:
409 case VariantFischeRandom:
410 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
411 case VariantNoCastle:
412 case VariantShatranj:
417 flags &= ~F_ALL_CASTLE_OK;
420 case VariantChuChess:
422 flags |= F_NULL_MOVE;
427 if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
431 FILE *gameFileFP, *debugFP, *serverFP;
432 char *currentDebugFile; // [HGM] debug split: to remember name
435 [AS] Note: sometimes, the sscanf() function is used to parse the input
436 into a fixed-size buffer. Because of this, we must be prepared to
437 receive strings as long as the size of the input buffer, which is currently
438 set to 4K for Windows and 8K for the rest.
439 So, we must either allocate sufficiently large buffers here, or
440 reduce the size of the input buffer in the input reading part.
443 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
444 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
445 char thinkOutput1[MSG_SIZ*10];
446 char promoRestrict[MSG_SIZ];
448 ChessProgramState first, second, pairing;
450 /* premove variables */
453 int premoveFromX = 0;
454 int premoveFromY = 0;
455 int premovePromoChar = 0;
457 Boolean alarmSounded;
458 /* end premove variables */
460 char *ics_prefix = "$";
461 enum ICS_TYPE ics_type = ICS_GENERIC;
463 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
464 int pauseExamForwardMostMove = 0;
465 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
466 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
467 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
468 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
469 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
470 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
471 int whiteFlag = FALSE, blackFlag = FALSE;
472 int userOfferedDraw = FALSE;
473 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
474 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
475 int cmailMoveType[CMAIL_MAX_GAMES];
476 long ics_clock_paused = 0;
477 ProcRef icsPR = NoProc, cmailPR = NoProc;
478 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
479 GameMode gameMode = BeginningOfGame;
480 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
481 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
482 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
483 int hiddenThinkOutputState = 0; /* [AS] */
484 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
485 int adjudicateLossPlies = 6;
486 char white_holding[64], black_holding[64];
487 TimeMark lastNodeCountTime;
488 long lastNodeCount=0;
489 int shiftKey, controlKey; // [HGM] set by mouse handler
491 int have_sent_ICS_logon = 0;
493 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
494 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
495 Boolean adjustedClock;
496 long timeControl_2; /* [AS] Allow separate time controls */
497 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
498 long timeRemaining[2][MAX_MOVES];
499 int matchGame = 0, nextGame = 0, roundNr = 0;
500 Boolean waitingForGame = FALSE, startingEngine = FALSE;
501 TimeMark programStartTime, pauseStart;
502 char ics_handle[MSG_SIZ];
503 int have_set_title = 0;
505 /* animateTraining preserves the state of appData.animate
506 * when Training mode is activated. This allows the
507 * response to be animated when appData.animate == TRUE and
508 * appData.animateDragging == TRUE.
510 Boolean animateTraining;
516 Board boards[MAX_MOVES];
517 /* [HGM] Following 7 needed for accurate legality tests: */
518 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
519 unsigned char initialRights[BOARD_FILES];
520 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
521 int initialRulePlies, FENrulePlies;
522 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
524 Boolean shuffleOpenings;
525 int mute; // mute all sounds
527 // [HGM] vari: next 12 to save and restore variations
528 #define MAX_VARIATIONS 10
529 int framePtr = MAX_MOVES-1; // points to free stack entry
531 int savedFirst[MAX_VARIATIONS];
532 int savedLast[MAX_VARIATIONS];
533 int savedFramePtr[MAX_VARIATIONS];
534 char *savedDetails[MAX_VARIATIONS];
535 ChessMove savedResult[MAX_VARIATIONS];
537 void PushTail P((int firstMove, int lastMove));
538 Boolean PopTail P((Boolean annotate));
539 void PushInner P((int firstMove, int lastMove));
540 void PopInner P((Boolean annotate));
541 void CleanupTail P((void));
543 ChessSquare FIDEArray[2][BOARD_FILES] = {
544 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
547 BlackKing, BlackBishop, BlackKnight, BlackRook }
550 ChessSquare twoKingsArray[2][BOARD_FILES] = {
551 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
552 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
553 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
554 BlackKing, BlackKing, BlackKnight, BlackRook }
557 ChessSquare KnightmateArray[2][BOARD_FILES] = {
558 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
559 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
560 { BlackRook, BlackMan, BlackBishop, BlackQueen,
561 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
564 ChessSquare SpartanArray[2][BOARD_FILES] = {
565 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
566 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
567 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
568 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
571 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
572 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
573 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
574 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
575 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
578 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
579 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
580 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
582 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
585 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
586 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
587 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
588 { BlackRook, BlackKnight, BlackMan, BlackFerz,
589 BlackKing, BlackMan, BlackKnight, BlackRook }
592 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
593 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
594 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
595 { BlackRook, BlackKnight, BlackMan, BlackFerz,
596 BlackKing, BlackMan, BlackKnight, BlackRook }
599 ChessSquare lionArray[2][BOARD_FILES] = {
600 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
601 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
602 { BlackRook, BlackLion, BlackBishop, BlackQueen,
603 BlackKing, BlackBishop, BlackKnight, BlackRook }
607 #if (BOARD_FILES>=10)
608 ChessSquare ShogiArray[2][BOARD_FILES] = {
609 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
610 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
611 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
612 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
615 ChessSquare XiangqiArray[2][BOARD_FILES] = {
616 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
617 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
618 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
619 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
622 ChessSquare CapablancaArray[2][BOARD_FILES] = {
623 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
624 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
625 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
626 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
629 ChessSquare GreatArray[2][BOARD_FILES] = {
630 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
631 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
632 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
633 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
636 ChessSquare JanusArray[2][BOARD_FILES] = {
637 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
638 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
639 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
640 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
643 ChessSquare GrandArray[2][BOARD_FILES] = {
644 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
645 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
646 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
647 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
650 ChessSquare ChuChessArray[2][BOARD_FILES] = {
651 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
652 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
653 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
654 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
658 ChessSquare GothicArray[2][BOARD_FILES] = {
659 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
660 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
661 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
662 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
665 #define GothicArray CapablancaArray
669 ChessSquare FalconArray[2][BOARD_FILES] = {
670 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
671 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
672 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
673 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
676 #define FalconArray CapablancaArray
679 #else // !(BOARD_FILES>=10)
680 #define XiangqiPosition FIDEArray
681 #define CapablancaArray FIDEArray
682 #define GothicArray FIDEArray
683 #define GreatArray FIDEArray
684 #endif // !(BOARD_FILES>=10)
686 #if (BOARD_FILES>=12)
687 ChessSquare CourierArray[2][BOARD_FILES] = {
688 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
689 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
690 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
691 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
693 ChessSquare ChuArray[6][BOARD_FILES] = {
694 { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
695 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
696 { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
697 BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
698 { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
699 WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
700 { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
701 BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
702 { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
703 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
704 { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
705 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
707 #else // !(BOARD_FILES>=12)
708 #define CourierArray CapablancaArray
709 #define ChuArray CapablancaArray
710 #endif // !(BOARD_FILES>=12)
713 Board initialPosition;
716 /* Convert str to a rating. Checks for special cases of "----",
718 "++++", etc. Also strips ()'s */
720 string_to_rating (char *str)
722 while(*str && !isdigit(*str)) ++str;
724 return 0; /* One of the special "no rating" cases */
732 /* Init programStats */
733 programStats.movelist[0] = 0;
734 programStats.depth = 0;
735 programStats.nr_moves = 0;
736 programStats.moves_left = 0;
737 programStats.nodes = 0;
738 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
739 programStats.score = 0;
740 programStats.got_only_move = 0;
741 programStats.got_fail = 0;
742 programStats.line_is_book = 0;
747 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
748 if (appData.firstPlaysBlack) {
749 first.twoMachinesColor = "black\n";
750 second.twoMachinesColor = "white\n";
752 first.twoMachinesColor = "white\n";
753 second.twoMachinesColor = "black\n";
756 first.other = &second;
757 second.other = &first;
760 if(appData.timeOddsMode) {
761 norm = appData.timeOdds[0];
762 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
764 first.timeOdds = appData.timeOdds[0]/norm;
765 second.timeOdds = appData.timeOdds[1]/norm;
768 if(programVersion) free(programVersion);
769 if (appData.noChessProgram) {
770 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
771 sprintf(programVersion, "%s", PACKAGE_STRING);
773 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
774 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
775 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
780 UnloadEngine (ChessProgramState *cps)
782 /* Kill off first chess program */
783 if (cps->isr != NULL)
784 RemoveInputSource(cps->isr);
787 if (cps->pr != NoProc) {
789 DoSleep( appData.delayBeforeQuit );
790 SendToProgram("quit\n", cps);
791 DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
794 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
798 ClearOptions (ChessProgramState *cps)
801 cps->nrOptions = cps->comboCnt = 0;
802 for(i=0; i<MAX_OPTIONS; i++) {
803 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
804 cps->option[i].textValue = 0;
808 char *engineNames[] = {
809 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
810 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
812 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
813 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
818 InitEngine (ChessProgramState *cps, int n)
819 { // [HGM] all engine initialiation put in a function that does one engine
823 cps->which = engineNames[n];
824 cps->maybeThinking = FALSE;
828 cps->sendDrawOffers = 1;
830 cps->program = appData.chessProgram[n];
831 cps->host = appData.host[n];
832 cps->dir = appData.directory[n];
833 cps->initString = appData.engInitString[n];
834 cps->computerString = appData.computerString[n];
835 cps->useSigint = TRUE;
836 cps->useSigterm = TRUE;
837 cps->reuse = appData.reuse[n];
838 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
839 cps->useSetboard = FALSE;
841 cps->usePing = FALSE;
844 cps->usePlayother = FALSE;
845 cps->useColors = TRUE;
846 cps->useUsermove = FALSE;
847 cps->sendICS = FALSE;
848 cps->sendName = appData.icsActive;
849 cps->sdKludge = FALSE;
850 cps->stKludge = FALSE;
851 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
852 TidyProgramName(cps->program, cps->host, cps->tidy);
854 ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
855 cps->analysisSupport = 2; /* detect */
856 cps->analyzing = FALSE;
857 cps->initDone = FALSE;
859 cps->pseudo = appData.pseudo[n];
861 /* New features added by Tord: */
862 cps->useFEN960 = FALSE;
863 cps->useOOCastle = TRUE;
864 /* End of new features added by Tord. */
865 cps->fenOverride = appData.fenOverride[n];
867 /* [HGM] time odds: set factor for each machine */
868 cps->timeOdds = appData.timeOdds[n];
870 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
871 cps->accumulateTC = appData.accumulateTC[n];
872 cps->maxNrOfSessions = 1;
877 cps->drawDepth = appData.drawDepth[n];
878 cps->supportsNPS = UNKNOWN;
879 cps->memSize = FALSE;
880 cps->maxCores = FALSE;
881 ASSIGN(cps->egtFormats, "");
884 cps->optionSettings = appData.engOptions[n];
886 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
887 cps->isUCI = appData.isUCI[n]; /* [AS] */
888 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
891 if (appData.protocolVersion[n] > PROTOVER
892 || appData.protocolVersion[n] < 1)
897 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
898 appData.protocolVersion[n]);
899 if( (len >= MSG_SIZ) && appData.debugMode )
900 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
902 DisplayFatalError(buf, 0, 2);
906 cps->protocolVersion = appData.protocolVersion[n];
909 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
910 ParseFeatures(appData.featureDefaults, cps);
913 ChessProgramState *savCps;
921 if(WaitForEngine(savCps, LoadEngine)) return;
922 CommonEngineInit(); // recalculate time odds
923 if(gameInfo.variant != StringToVariant(appData.variant)) {
924 // we changed variant when loading the engine; this forces us to reset
925 Reset(TRUE, savCps != &first);
926 oldMode = BeginningOfGame; // to prevent restoring old mode
928 InitChessProgram(savCps, FALSE);
929 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
930 DisplayMessage("", "");
931 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
932 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
935 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
939 ReplaceEngine (ChessProgramState *cps, int n)
941 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
943 if(oldMode != BeginningOfGame) EditGameEvent();
946 appData.noChessProgram = FALSE;
947 appData.clockMode = TRUE;
950 if(n) return; // only startup first engine immediately; second can wait
951 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
955 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
956 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
958 static char resetOptions[] =
959 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
960 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
961 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
962 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
965 FloatToFront(char **list, char *engineLine)
967 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
969 if(appData.recentEngines <= 0) return;
970 TidyProgramName(engineLine, "localhost", tidy+1);
971 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
972 strncpy(buf+1, *list, MSG_SIZ-50);
973 if(p = strstr(buf, tidy)) { // tidy name appears in list
974 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
975 while(*p++ = *++q); // squeeze out
977 strcat(tidy, buf+1); // put list behind tidy name
978 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
979 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
980 ASSIGN(*list, tidy+1);
983 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
986 Load (ChessProgramState *cps, int i)
988 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
989 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
990 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
991 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
992 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
993 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
994 appData.firstProtocolVersion = PROTOVER;
995 ParseArgsFromString(buf);
997 ReplaceEngine(cps, i);
998 FloatToFront(&appData.recentEngineList, engineLine);
1002 while(q = strchr(p, SLASH)) p = q+1;
1003 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1004 if(engineDir[0] != NULLCHAR) {
1005 ASSIGN(appData.directory[i], engineDir); p = engineName;
1006 } else if(p != engineName) { // derive directory from engine path, when not given
1008 ASSIGN(appData.directory[i], engineName);
1010 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1011 } else { ASSIGN(appData.directory[i], "."); }
1012 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1014 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1015 snprintf(command, MSG_SIZ, "%s %s", p, params);
1018 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1019 ASSIGN(appData.chessProgram[i], p);
1020 appData.isUCI[i] = isUCI;
1021 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1022 appData.hasOwnBookUCI[i] = hasBook;
1023 if(!nickName[0]) useNick = FALSE;
1024 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1028 q = firstChessProgramNames;
1029 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1030 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1031 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1032 quote, p, quote, appData.directory[i],
1033 useNick ? " -fn \"" : "",
1034 useNick ? nickName : "",
1035 useNick ? "\"" : "",
1036 v1 ? " -firstProtocolVersion 1" : "",
1037 hasBook ? "" : " -fNoOwnBookUCI",
1038 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1039 storeVariant ? " -variant " : "",
1040 storeVariant ? VariantName(gameInfo.variant) : "");
1041 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1042 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1043 if(insert != q) insert[-1] = NULLCHAR;
1044 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1046 FloatToFront(&appData.recentEngineList, buf);
1048 ReplaceEngine(cps, i);
1054 int matched, min, sec;
1056 * Parse timeControl resource
1058 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1059 appData.movesPerSession)) {
1061 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1062 DisplayFatalError(buf, 0, 2);
1066 * Parse searchTime resource
1068 if (*appData.searchTime != NULLCHAR) {
1069 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1071 searchTime = min * 60;
1072 } else if (matched == 2) {
1073 searchTime = min * 60 + sec;
1076 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1077 DisplayFatalError(buf, 0, 2);
1086 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1087 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1089 GetTimeMark(&programStartTime);
1090 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1091 appData.seedBase = random() + (random()<<15);
1092 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1094 ClearProgramStats();
1095 programStats.ok_to_send = 1;
1096 programStats.seen_stat = 0;
1099 * Initialize game list
1105 * Internet chess server status
1107 if (appData.icsActive) {
1108 appData.matchMode = FALSE;
1109 appData.matchGames = 0;
1111 appData.noChessProgram = !appData.zippyPlay;
1113 appData.zippyPlay = FALSE;
1114 appData.zippyTalk = FALSE;
1115 appData.noChessProgram = TRUE;
1117 if (*appData.icsHelper != NULLCHAR) {
1118 appData.useTelnet = TRUE;
1119 appData.telnetProgram = appData.icsHelper;
1122 appData.zippyTalk = appData.zippyPlay = FALSE;
1125 /* [AS] Initialize pv info list [HGM] and game state */
1129 for( i=0; i<=framePtr; i++ ) {
1130 pvInfoList[i].depth = -1;
1131 boards[i][EP_STATUS] = EP_NONE;
1132 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1138 /* [AS] Adjudication threshold */
1139 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1141 InitEngine(&first, 0);
1142 InitEngine(&second, 1);
1145 pairing.which = "pairing"; // pairing engine
1146 pairing.pr = NoProc;
1148 pairing.program = appData.pairingEngine;
1149 pairing.host = "localhost";
1152 if (appData.icsActive) {
1153 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1154 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1155 appData.clockMode = FALSE;
1156 first.sendTime = second.sendTime = 0;
1160 /* Override some settings from environment variables, for backward
1161 compatibility. Unfortunately it's not feasible to have the env
1162 vars just set defaults, at least in xboard. Ugh.
1164 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1169 if (!appData.icsActive) {
1173 /* Check for variants that are supported only in ICS mode,
1174 or not at all. Some that are accepted here nevertheless
1175 have bugs; see comments below.
1177 VariantClass variant = StringToVariant(appData.variant);
1179 case VariantBughouse: /* need four players and two boards */
1180 case VariantKriegspiel: /* need to hide pieces and move details */
1181 /* case VariantFischeRandom: (Fabien: moved below) */
1182 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1183 if( (len >= MSG_SIZ) && appData.debugMode )
1184 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1186 DisplayFatalError(buf, 0, 2);
1189 case VariantUnknown:
1190 case VariantLoadable:
1200 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1201 if( (len >= MSG_SIZ) && appData.debugMode )
1202 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1204 DisplayFatalError(buf, 0, 2);
1207 case VariantNormal: /* definitely works! */
1208 if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1209 safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1212 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1213 case VariantFairy: /* [HGM] TestLegality definitely off! */
1214 case VariantGothic: /* [HGM] should work */
1215 case VariantCapablanca: /* [HGM] should work */
1216 case VariantCourier: /* [HGM] initial forced moves not implemented */
1217 case VariantShogi: /* [HGM] could still mate with pawn drop */
1218 case VariantChu: /* [HGM] experimental */
1219 case VariantKnightmate: /* [HGM] should work */
1220 case VariantCylinder: /* [HGM] untested */
1221 case VariantFalcon: /* [HGM] untested */
1222 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1223 offboard interposition not understood */
1224 case VariantWildCastle: /* pieces not automatically shuffled */
1225 case VariantNoCastle: /* pieces not automatically shuffled */
1226 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1227 case VariantLosers: /* should work except for win condition,
1228 and doesn't know captures are mandatory */
1229 case VariantSuicide: /* should work except for win condition,
1230 and doesn't know captures are mandatory */
1231 case VariantGiveaway: /* should work except for win condition,
1232 and doesn't know captures are mandatory */
1233 case VariantTwoKings: /* should work */
1234 case VariantAtomic: /* should work except for win condition */
1235 case Variant3Check: /* should work except for win condition */
1236 case VariantShatranj: /* should work except for all win conditions */
1237 case VariantMakruk: /* should work except for draw countdown */
1238 case VariantASEAN : /* should work except for draw countdown */
1239 case VariantBerolina: /* might work if TestLegality is off */
1240 case VariantCapaRandom: /* should work */
1241 case VariantJanus: /* should work */
1242 case VariantSuper: /* experimental */
1243 case VariantGreat: /* experimental, requires legality testing to be off */
1244 case VariantSChess: /* S-Chess, should work */
1245 case VariantGrand: /* should work */
1246 case VariantSpartan: /* should work */
1247 case VariantLion: /* should work */
1248 case VariantChuChess: /* should work */
1256 NextIntegerFromString (char ** str, long * value)
1261 while( *s == ' ' || *s == '\t' ) {
1267 if( *s >= '0' && *s <= '9' ) {
1268 while( *s >= '0' && *s <= '9' ) {
1269 *value = *value * 10 + (*s - '0');
1282 NextTimeControlFromString (char ** str, long * value)
1285 int result = NextIntegerFromString( str, &temp );
1288 *value = temp * 60; /* Minutes */
1289 if( **str == ':' ) {
1291 result = NextIntegerFromString( str, &temp );
1292 *value += temp; /* Seconds */
1300 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1301 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1302 int result = -1, type = 0; long temp, temp2;
1304 if(**str != ':') return -1; // old params remain in force!
1306 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1307 if( NextIntegerFromString( str, &temp ) ) return -1;
1308 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1311 /* time only: incremental or sudden-death time control */
1312 if(**str == '+') { /* increment follows; read it */
1314 if(**str == '!') type = *(*str)++; // Bronstein TC
1315 if(result = NextIntegerFromString( str, &temp2)) return -1;
1316 *inc = temp2 * 1000;
1317 if(**str == '.') { // read fraction of increment
1318 char *start = ++(*str);
1319 if(result = NextIntegerFromString( str, &temp2)) return -1;
1321 while(start++ < *str) temp2 /= 10;
1325 *moves = 0; *tc = temp * 1000; *incType = type;
1329 (*str)++; /* classical time control */
1330 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1342 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1343 { /* [HGM] get time to add from the multi-session time-control string */
1344 int incType, moves=1; /* kludge to force reading of first session */
1345 long time, increment;
1348 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1350 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1351 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1352 if(movenr == -1) return time; /* last move before new session */
1353 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1354 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1355 if(!moves) return increment; /* current session is incremental */
1356 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1357 } while(movenr >= -1); /* try again for next session */
1359 return 0; // no new time quota on this move
1363 ParseTimeControl (char *tc, float ti, int mps)
1367 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1370 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1371 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1372 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1376 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1378 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1381 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1383 snprintf(buf, MSG_SIZ, ":%s", mytc);
1385 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1387 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1392 /* Parse second time control */
1395 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1403 timeControl_2 = tc2 * 1000;
1413 timeControl = tc1 * 1000;
1416 timeIncrement = ti * 1000; /* convert to ms */
1417 movesPerSession = 0;
1420 movesPerSession = mps;
1428 if (appData.debugMode) {
1429 # ifdef __GIT_VERSION
1430 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1432 fprintf(debugFP, "Version: %s\n", programVersion);
1435 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1437 set_cont_sequence(appData.wrapContSeq);
1438 if (appData.matchGames > 0) {
1439 appData.matchMode = TRUE;
1440 } else if (appData.matchMode) {
1441 appData.matchGames = 1;
1443 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1444 appData.matchGames = appData.sameColorGames;
1445 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1446 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1447 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1450 if (appData.noChessProgram || first.protocolVersion == 1) {
1453 /* kludge: allow timeout for initial "feature" commands */
1455 DisplayMessage("", _("Starting chess program"));
1456 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1461 CalculateIndex (int index, int gameNr)
1462 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1464 if(index > 0) return index; // fixed nmber
1465 if(index == 0) return 1;
1466 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1467 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1472 LoadGameOrPosition (int gameNr)
1473 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1474 if (*appData.loadGameFile != NULLCHAR) {
1475 if (!LoadGameFromFile(appData.loadGameFile,
1476 CalculateIndex(appData.loadGameIndex, gameNr),
1477 appData.loadGameFile, FALSE)) {
1478 DisplayFatalError(_("Bad game file"), 0, 1);
1481 } else if (*appData.loadPositionFile != NULLCHAR) {
1482 if (!LoadPositionFromFile(appData.loadPositionFile,
1483 CalculateIndex(appData.loadPositionIndex, gameNr),
1484 appData.loadPositionFile)) {
1485 DisplayFatalError(_("Bad position file"), 0, 1);
1493 ReserveGame (int gameNr, char resChar)
1495 FILE *tf = fopen(appData.tourneyFile, "r+");
1496 char *p, *q, c, buf[MSG_SIZ];
1497 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1498 safeStrCpy(buf, lastMsg, MSG_SIZ);
1499 DisplayMessage(_("Pick new game"), "");
1500 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1501 ParseArgsFromFile(tf);
1502 p = q = appData.results;
1503 if(appData.debugMode) {
1504 char *r = appData.participants;
1505 fprintf(debugFP, "results = '%s'\n", p);
1506 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1507 fprintf(debugFP, "\n");
1509 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1511 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1512 safeStrCpy(q, p, strlen(p) + 2);
1513 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1514 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1515 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1516 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1519 fseek(tf, -(strlen(p)+4), SEEK_END);
1521 if(c != '"') // depending on DOS or Unix line endings we can be one off
1522 fseek(tf, -(strlen(p)+2), SEEK_END);
1523 else fseek(tf, -(strlen(p)+3), SEEK_END);
1524 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1525 DisplayMessage(buf, "");
1526 free(p); appData.results = q;
1527 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1528 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1529 int round = appData.defaultMatchGames * appData.tourneyType;
1530 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1531 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1532 UnloadEngine(&first); // next game belongs to other pairing;
1533 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1535 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1539 MatchEvent (int mode)
1540 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1542 if(matchMode) { // already in match mode: switch it off
1544 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1547 // if(gameMode != BeginningOfGame) {
1548 // DisplayError(_("You can only start a match from the initial position."), 0);
1552 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1553 /* Set up machine vs. machine match */
1555 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1556 if(appData.tourneyFile[0]) {
1558 if(nextGame > appData.matchGames) {
1560 if(strchr(appData.results, '*') == NULL) {
1562 appData.tourneyCycles++;
1563 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1565 NextTourneyGame(-1, &dummy);
1567 if(nextGame <= appData.matchGames) {
1568 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1570 ScheduleDelayedEvent(NextMatchGame, 10000);
1575 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1576 DisplayError(buf, 0);
1577 appData.tourneyFile[0] = 0;
1581 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1582 DisplayFatalError(_("Can't have a match with no chess programs"),
1587 matchGame = roundNr = 1;
1588 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1592 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1595 InitBackEnd3 P((void))
1597 GameMode initialMode;
1601 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1602 !strcmp(appData.variant, "normal") && // no explicit variant request
1603 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1604 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1605 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1606 char c, *q = first.variants, *p = strchr(q, ',');
1607 if(p) *p = NULLCHAR;
1608 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1610 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1611 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1612 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1613 Reset(TRUE, FALSE); // and re-initialize
1618 InitChessProgram(&first, startedFromSetupPosition);
1620 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1621 free(programVersion);
1622 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1623 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1624 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1627 if (appData.icsActive) {
1629 /* [DM] Make a console window if needed [HGM] merged ifs */
1635 if (*appData.icsCommPort != NULLCHAR)
1636 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1637 appData.icsCommPort);
1639 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1640 appData.icsHost, appData.icsPort);
1642 if( (len >= MSG_SIZ) && appData.debugMode )
1643 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1645 DisplayFatalError(buf, err, 1);
1650 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1652 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1653 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1654 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1655 } else if (appData.noChessProgram) {
1661 if (*appData.cmailGameName != NULLCHAR) {
1663 OpenLoopback(&cmailPR);
1665 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1669 DisplayMessage("", "");
1670 if (StrCaseCmp(appData.initialMode, "") == 0) {
1671 initialMode = BeginningOfGame;
1672 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1673 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1674 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1675 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1678 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1679 initialMode = TwoMachinesPlay;
1680 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1681 initialMode = AnalyzeFile;
1682 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1683 initialMode = AnalyzeMode;
1684 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1685 initialMode = MachinePlaysWhite;
1686 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1687 initialMode = MachinePlaysBlack;
1688 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1689 initialMode = EditGame;
1690 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1691 initialMode = EditPosition;
1692 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1693 initialMode = Training;
1695 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1696 if( (len >= MSG_SIZ) && appData.debugMode )
1697 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1699 DisplayFatalError(buf, 0, 2);
1703 if (appData.matchMode) {
1704 if(appData.tourneyFile[0]) { // start tourney from command line
1706 if(f = fopen(appData.tourneyFile, "r")) {
1707 ParseArgsFromFile(f); // make sure tourney parmeters re known
1709 appData.clockMode = TRUE;
1711 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1714 } else if (*appData.cmailGameName != NULLCHAR) {
1715 /* Set up cmail mode */
1716 ReloadCmailMsgEvent(TRUE);
1718 /* Set up other modes */
1719 if (initialMode == AnalyzeFile) {
1720 if (*appData.loadGameFile == NULLCHAR) {
1721 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1725 if (*appData.loadGameFile != NULLCHAR) {
1726 (void) LoadGameFromFile(appData.loadGameFile,
1727 appData.loadGameIndex,
1728 appData.loadGameFile, TRUE);
1729 } else if (*appData.loadPositionFile != NULLCHAR) {
1730 (void) LoadPositionFromFile(appData.loadPositionFile,
1731 appData.loadPositionIndex,
1732 appData.loadPositionFile);
1733 /* [HGM] try to make self-starting even after FEN load */
1734 /* to allow automatic setup of fairy variants with wtm */
1735 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1736 gameMode = BeginningOfGame;
1737 setboardSpoiledMachineBlack = 1;
1739 /* [HGM] loadPos: make that every new game uses the setup */
1740 /* from file as long as we do not switch variant */
1741 if(!blackPlaysFirst) {
1742 startedFromPositionFile = TRUE;
1743 CopyBoard(filePosition, boards[0]);
1744 CopyBoard(initialPosition, boards[0]);
1746 } else if(*appData.fen != NULLCHAR) {
1747 if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1748 startedFromPositionFile = TRUE;
1752 if (initialMode == AnalyzeMode) {
1753 if (appData.noChessProgram) {
1754 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1757 if (appData.icsActive) {
1758 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1762 } else if (initialMode == AnalyzeFile) {
1763 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1764 ShowThinkingEvent();
1766 AnalysisPeriodicEvent(1);
1767 } else if (initialMode == MachinePlaysWhite) {
1768 if (appData.noChessProgram) {
1769 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1773 if (appData.icsActive) {
1774 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1778 MachineWhiteEvent();
1779 } else if (initialMode == MachinePlaysBlack) {
1780 if (appData.noChessProgram) {
1781 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1785 if (appData.icsActive) {
1786 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1790 MachineBlackEvent();
1791 } else if (initialMode == TwoMachinesPlay) {
1792 if (appData.noChessProgram) {
1793 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1797 if (appData.icsActive) {
1798 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1803 } else if (initialMode == EditGame) {
1805 } else if (initialMode == EditPosition) {
1806 EditPositionEvent();
1807 } else if (initialMode == Training) {
1808 if (*appData.loadGameFile == NULLCHAR) {
1809 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1818 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1820 DisplayBook(current+1);
1822 MoveHistorySet( movelist, first, last, current, pvInfoList );
1824 EvalGraphSet( first, last, current, pvInfoList );
1826 MakeEngineOutputTitle();
1830 * Establish will establish a contact to a remote host.port.
1831 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1832 * used to talk to the host.
1833 * Returns 0 if okay, error code if not.
1840 if (*appData.icsCommPort != NULLCHAR) {
1841 /* Talk to the host through a serial comm port */
1842 return OpenCommPort(appData.icsCommPort, &icsPR);
1844 } else if (*appData.gateway != NULLCHAR) {
1845 if (*appData.remoteShell == NULLCHAR) {
1846 /* Use the rcmd protocol to run telnet program on a gateway host */
1847 snprintf(buf, sizeof(buf), "%s %s %s",
1848 appData.telnetProgram, appData.icsHost, appData.icsPort);
1849 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1852 /* Use the rsh program to run telnet program on a gateway host */
1853 if (*appData.remoteUser == NULLCHAR) {
1854 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1855 appData.gateway, appData.telnetProgram,
1856 appData.icsHost, appData.icsPort);
1858 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1859 appData.remoteShell, appData.gateway,
1860 appData.remoteUser, appData.telnetProgram,
1861 appData.icsHost, appData.icsPort);
1863 return StartChildProcess(buf, "", &icsPR);
1866 } else if (appData.useTelnet) {
1867 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1870 /* TCP socket interface differs somewhat between
1871 Unix and NT; handle details in the front end.
1873 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1878 EscapeExpand (char *p, char *q)
1879 { // [HGM] initstring: routine to shape up string arguments
1880 while(*p++ = *q++) if(p[-1] == '\\')
1882 case 'n': p[-1] = '\n'; break;
1883 case 'r': p[-1] = '\r'; break;
1884 case 't': p[-1] = '\t'; break;
1885 case '\\': p[-1] = '\\'; break;
1886 case 0: *p = 0; return;
1887 default: p[-1] = q[-1]; break;
1892 show_bytes (FILE *fp, char *buf, int count)
1895 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1896 fprintf(fp, "\\%03o", *buf & 0xff);
1905 /* Returns an errno value */
1907 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1909 char buf[8192], *p, *q, *buflim;
1910 int left, newcount, outcount;
1912 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1913 *appData.gateway != NULLCHAR) {
1914 if (appData.debugMode) {
1915 fprintf(debugFP, ">ICS: ");
1916 show_bytes(debugFP, message, count);
1917 fprintf(debugFP, "\n");
1919 return OutputToProcess(pr, message, count, outError);
1922 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1929 if (appData.debugMode) {
1930 fprintf(debugFP, ">ICS: ");
1931 show_bytes(debugFP, buf, newcount);
1932 fprintf(debugFP, "\n");
1934 outcount = OutputToProcess(pr, buf, newcount, outError);
1935 if (outcount < newcount) return -1; /* to be sure */
1942 } else if (((unsigned char) *p) == TN_IAC) {
1943 *q++ = (char) TN_IAC;
1950 if (appData.debugMode) {
1951 fprintf(debugFP, ">ICS: ");
1952 show_bytes(debugFP, buf, newcount);
1953 fprintf(debugFP, "\n");
1955 outcount = OutputToProcess(pr, buf, newcount, outError);
1956 if (outcount < newcount) return -1; /* to be sure */
1961 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1963 int outError, outCount;
1964 static int gotEof = 0;
1967 /* Pass data read from player on to ICS */
1970 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1971 if (outCount < count) {
1972 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1974 if(have_sent_ICS_logon == 2) {
1975 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1976 fprintf(ini, "%s", message);
1977 have_sent_ICS_logon = 3;
1979 have_sent_ICS_logon = 1;
1980 } else if(have_sent_ICS_logon == 3) {
1981 fprintf(ini, "%s", message);
1983 have_sent_ICS_logon = 1;
1985 } else if (count < 0) {
1986 RemoveInputSource(isr);
1987 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1988 } else if (gotEof++ > 0) {
1989 RemoveInputSource(isr);
1990 DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1996 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1997 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1998 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1999 SendToICS("date\n");
2000 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2003 /* added routine for printf style output to ics */
2005 ics_printf (char *format, ...)
2007 char buffer[MSG_SIZ];
2010 va_start(args, format);
2011 vsnprintf(buffer, sizeof(buffer), format, args);
2012 buffer[sizeof(buffer)-1] = '\0';
2020 int count, outCount, outError;
2022 if (icsPR == NoProc) return;
2025 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2026 if (outCount < count) {
2027 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2031 /* This is used for sending logon scripts to the ICS. Sending
2032 without a delay causes problems when using timestamp on ICC
2033 (at least on my machine). */
2035 SendToICSDelayed (char *s, long msdelay)
2037 int count, outCount, outError;
2039 if (icsPR == NoProc) return;
2042 if (appData.debugMode) {
2043 fprintf(debugFP, ">ICS: ");
2044 show_bytes(debugFP, s, count);
2045 fprintf(debugFP, "\n");
2047 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2049 if (outCount < count) {
2050 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2055 /* Remove all highlighting escape sequences in s
2056 Also deletes any suffix starting with '('
2059 StripHighlightAndTitle (char *s)
2061 static char retbuf[MSG_SIZ];
2064 while (*s != NULLCHAR) {
2065 while (*s == '\033') {
2066 while (*s != NULLCHAR && !isalpha(*s)) s++;
2067 if (*s != NULLCHAR) s++;
2069 while (*s != NULLCHAR && *s != '\033') {
2070 if (*s == '(' || *s == '[') {
2081 /* Remove all highlighting escape sequences in s */
2083 StripHighlight (char *s)
2085 static char retbuf[MSG_SIZ];
2088 while (*s != NULLCHAR) {
2089 while (*s == '\033') {
2090 while (*s != NULLCHAR && !isalpha(*s)) s++;
2091 if (*s != NULLCHAR) s++;
2093 while (*s != NULLCHAR && *s != '\033') {
2101 char engineVariant[MSG_SIZ];
2102 char *variantNames[] = VARIANT_NAMES;
2104 VariantName (VariantClass v)
2106 if(v == VariantUnknown || *engineVariant) return engineVariant;
2107 return variantNames[v];
2111 /* Identify a variant from the strings the chess servers use or the
2112 PGN Variant tag names we use. */
2114 StringToVariant (char *e)
2118 VariantClass v = VariantNormal;
2119 int i, found = FALSE;
2120 char buf[MSG_SIZ], c;
2125 /* [HGM] skip over optional board-size prefixes */
2126 if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2127 sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2128 while( *e++ != '_');
2131 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2135 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2136 if (p = StrCaseStr(e, variantNames[i])) {
2137 if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2138 v = (VariantClass) i;
2145 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2146 || StrCaseStr(e, "wild/fr")
2147 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2148 v = VariantFischeRandom;
2149 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2150 (i = 1, p = StrCaseStr(e, "w"))) {
2152 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2159 case 0: /* FICS only, actually */
2161 /* Castling legal even if K starts on d-file */
2162 v = VariantWildCastle;
2167 /* Castling illegal even if K & R happen to start in
2168 normal positions. */
2169 v = VariantNoCastle;
2182 /* Castling legal iff K & R start in normal positions */
2188 /* Special wilds for position setup; unclear what to do here */
2189 v = VariantLoadable;
2192 /* Bizarre ICC game */
2193 v = VariantTwoKings;
2196 v = VariantKriegspiel;
2202 v = VariantFischeRandom;
2205 v = VariantCrazyhouse;
2208 v = VariantBughouse;
2214 /* Not quite the same as FICS suicide! */
2215 v = VariantGiveaway;
2221 v = VariantShatranj;
2224 /* Temporary names for future ICC types. The name *will* change in
2225 the next xboard/WinBoard release after ICC defines it. */
2263 v = VariantCapablanca;
2266 v = VariantKnightmate;
2272 v = VariantCylinder;
2278 v = VariantCapaRandom;
2281 v = VariantBerolina;
2293 /* Found "wild" or "w" in the string but no number;
2294 must assume it's normal chess. */
2298 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2299 if( (len >= MSG_SIZ) && appData.debugMode )
2300 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2302 DisplayError(buf, 0);
2308 if (appData.debugMode) {
2309 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2310 e, wnum, VariantName(v));
2315 static int leftover_start = 0, leftover_len = 0;
2316 char star_match[STAR_MATCH_N][MSG_SIZ];
2318 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2319 advance *index beyond it, and set leftover_start to the new value of
2320 *index; else return FALSE. If pattern contains the character '*', it
2321 matches any sequence of characters not containing '\r', '\n', or the
2322 character following the '*' (if any), and the matched sequence(s) are
2323 copied into star_match.
2326 looking_at ( char *buf, int *index, char *pattern)
2328 char *bufp = &buf[*index], *patternp = pattern;
2330 char *matchp = star_match[0];
2333 if (*patternp == NULLCHAR) {
2334 *index = leftover_start = bufp - buf;
2338 if (*bufp == NULLCHAR) return FALSE;
2339 if (*patternp == '*') {
2340 if (*bufp == *(patternp + 1)) {
2342 matchp = star_match[++star_count];
2346 } else if (*bufp == '\n' || *bufp == '\r') {
2348 if (*patternp == NULLCHAR)
2353 *matchp++ = *bufp++;
2357 if (*patternp != *bufp) return FALSE;
2364 SendToPlayer (char *data, int length)
2366 int error, outCount;
2367 outCount = OutputToProcess(NoProc, data, length, &error);
2368 if (outCount < length) {
2369 DisplayFatalError(_("Error writing to display"), error, 1);
2374 PackHolding (char packed[], char *holding)
2384 switch (runlength) {
2395 sprintf(q, "%d", runlength);
2407 /* Telnet protocol requests from the front end */
2409 TelnetRequest (unsigned char ddww, unsigned char option)
2411 unsigned char msg[3];
2412 int outCount, outError;
2414 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2416 if (appData.debugMode) {
2417 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2433 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2442 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2445 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2450 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2452 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2459 if (!appData.icsActive) return;
2460 TelnetRequest(TN_DO, TN_ECHO);
2466 if (!appData.icsActive) return;
2467 TelnetRequest(TN_DONT, TN_ECHO);
2471 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2473 /* put the holdings sent to us by the server on the board holdings area */
2474 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2478 if(gameInfo.holdingsWidth < 2) return;
2479 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2480 return; // prevent overwriting by pre-board holdings
2482 if( (int)lowestPiece >= BlackPawn ) {
2485 holdingsStartRow = BOARD_HEIGHT-1;
2488 holdingsColumn = BOARD_WIDTH-1;
2489 countsColumn = BOARD_WIDTH-2;
2490 holdingsStartRow = 0;
2494 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2495 board[i][holdingsColumn] = EmptySquare;
2496 board[i][countsColumn] = (ChessSquare) 0;
2498 while( (p=*holdings++) != NULLCHAR ) {
2499 piece = CharToPiece( ToUpper(p) );
2500 if(piece == EmptySquare) continue;
2501 /*j = (int) piece - (int) WhitePawn;*/
2502 j = PieceToNumber(piece);
2503 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2504 if(j < 0) continue; /* should not happen */
2505 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2506 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2507 board[holdingsStartRow+j*direction][countsColumn]++;
2513 VariantSwitch (Board board, VariantClass newVariant)
2515 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2516 static Board oldBoard;
2518 startedFromPositionFile = FALSE;
2519 if(gameInfo.variant == newVariant) return;
2521 /* [HGM] This routine is called each time an assignment is made to
2522 * gameInfo.variant during a game, to make sure the board sizes
2523 * are set to match the new variant. If that means adding or deleting
2524 * holdings, we shift the playing board accordingly
2525 * This kludge is needed because in ICS observe mode, we get boards
2526 * of an ongoing game without knowing the variant, and learn about the
2527 * latter only later. This can be because of the move list we requested,
2528 * in which case the game history is refilled from the beginning anyway,
2529 * but also when receiving holdings of a crazyhouse game. In the latter
2530 * case we want to add those holdings to the already received position.
2534 if (appData.debugMode) {
2535 fprintf(debugFP, "Switch board from %s to %s\n",
2536 VariantName(gameInfo.variant), VariantName(newVariant));
2537 setbuf(debugFP, NULL);
2539 shuffleOpenings = 0; /* [HGM] shuffle */
2540 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2544 newWidth = 9; newHeight = 9;
2545 gameInfo.holdingsSize = 7;
2546 case VariantBughouse:
2547 case VariantCrazyhouse:
2548 newHoldingsWidth = 2; break;
2552 newHoldingsWidth = 2;
2553 gameInfo.holdingsSize = 8;
2556 case VariantCapablanca:
2557 case VariantCapaRandom:
2560 newHoldingsWidth = gameInfo.holdingsSize = 0;
2563 if(newWidth != gameInfo.boardWidth ||
2564 newHeight != gameInfo.boardHeight ||
2565 newHoldingsWidth != gameInfo.holdingsWidth ) {
2567 /* shift position to new playing area, if needed */
2568 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2569 for(i=0; i<BOARD_HEIGHT; i++)
2570 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2571 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2573 for(i=0; i<newHeight; i++) {
2574 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2575 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2577 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2578 for(i=0; i<BOARD_HEIGHT; i++)
2579 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2580 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2583 board[HOLDINGS_SET] = 0;
2584 gameInfo.boardWidth = newWidth;
2585 gameInfo.boardHeight = newHeight;
2586 gameInfo.holdingsWidth = newHoldingsWidth;
2587 gameInfo.variant = newVariant;
2588 InitDrawingSizes(-2, 0);
2589 } else gameInfo.variant = newVariant;
2590 CopyBoard(oldBoard, board); // remember correctly formatted board
2591 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2592 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2595 static int loggedOn = FALSE;
2597 /*-- Game start info cache: --*/
2599 char gs_kind[MSG_SIZ];
2600 static char player1Name[128] = "";
2601 static char player2Name[128] = "";
2602 static char cont_seq[] = "\n\\ ";
2603 static int player1Rating = -1;
2604 static int player2Rating = -1;
2605 /*----------------------------*/
2607 ColorClass curColor = ColorNormal;
2608 int suppressKibitz = 0;
2611 Boolean soughtPending = FALSE;
2612 Boolean seekGraphUp;
2613 #define MAX_SEEK_ADS 200
2615 char *seekAdList[MAX_SEEK_ADS];
2616 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2617 float tcList[MAX_SEEK_ADS];
2618 char colorList[MAX_SEEK_ADS];
2619 int nrOfSeekAds = 0;
2620 int minRating = 1010, maxRating = 2800;
2621 int hMargin = 10, vMargin = 20, h, w;
2622 extern int squareSize, lineGap;
2627 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2628 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2629 if(r < minRating+100 && r >=0 ) r = minRating+100;
2630 if(r > maxRating) r = maxRating;
2631 if(tc < 1.f) tc = 1.f;
2632 if(tc > 95.f) tc = 95.f;
2633 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2634 y = ((double)r - minRating)/(maxRating - minRating)
2635 * (h-vMargin-squareSize/8-1) + vMargin;
2636 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2637 if(strstr(seekAdList[i], " u ")) color = 1;
2638 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2639 !strstr(seekAdList[i], "bullet") &&
2640 !strstr(seekAdList[i], "blitz") &&
2641 !strstr(seekAdList[i], "standard") ) color = 2;
2642 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2643 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2647 PlotSingleSeekAd (int i)
2653 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2655 char buf[MSG_SIZ], *ext = "";
2656 VariantClass v = StringToVariant(type);
2657 if(strstr(type, "wild")) {
2658 ext = type + 4; // append wild number
2659 if(v == VariantFischeRandom) type = "chess960"; else
2660 if(v == VariantLoadable) type = "setup"; else
2661 type = VariantName(v);
2663 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2664 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2665 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2666 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2667 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2668 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2669 seekNrList[nrOfSeekAds] = nr;
2670 zList[nrOfSeekAds] = 0;
2671 seekAdList[nrOfSeekAds++] = StrSave(buf);
2672 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2677 EraseSeekDot (int i)
2679 int x = xList[i], y = yList[i], d=squareSize/4, k;
2680 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2681 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2682 // now replot every dot that overlapped
2683 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2684 int xx = xList[k], yy = yList[k];
2685 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2686 DrawSeekDot(xx, yy, colorList[k]);
2691 RemoveSeekAd (int nr)
2694 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2696 if(seekAdList[i]) free(seekAdList[i]);
2697 seekAdList[i] = seekAdList[--nrOfSeekAds];
2698 seekNrList[i] = seekNrList[nrOfSeekAds];
2699 ratingList[i] = ratingList[nrOfSeekAds];
2700 colorList[i] = colorList[nrOfSeekAds];
2701 tcList[i] = tcList[nrOfSeekAds];
2702 xList[i] = xList[nrOfSeekAds];
2703 yList[i] = yList[nrOfSeekAds];
2704 zList[i] = zList[nrOfSeekAds];
2705 seekAdList[nrOfSeekAds] = NULL;
2711 MatchSoughtLine (char *line)
2713 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2714 int nr, base, inc, u=0; char dummy;
2716 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2717 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2719 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2720 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2721 // match: compact and save the line
2722 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2732 if(!seekGraphUp) return FALSE;
2733 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2734 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap + 2*border;
2736 DrawSeekBackground(0, 0, w, h);
2737 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2738 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2739 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2740 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2742 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2745 snprintf(buf, MSG_SIZ, "%d", i);
2746 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2749 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2750 for(i=1; i<100; i+=(i<10?1:5)) {
2751 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2752 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2753 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2755 snprintf(buf, MSG_SIZ, "%d", i);
2756 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2759 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2764 SeekGraphClick (ClickType click, int x, int y, int moving)
2766 static int lastDown = 0, displayed = 0, lastSecond;
2767 if(y < 0) return FALSE;
2768 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2769 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2770 if(!seekGraphUp) return FALSE;
2771 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2772 DrawPosition(TRUE, NULL);
2775 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2776 if(click == Release || moving) return FALSE;
2778 soughtPending = TRUE;
2779 SendToICS(ics_prefix);
2780 SendToICS("sought\n"); // should this be "sought all"?
2781 } else { // issue challenge based on clicked ad
2782 int dist = 10000; int i, closest = 0, second = 0;
2783 for(i=0; i<nrOfSeekAds; i++) {
2784 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2785 if(d < dist) { dist = d; closest = i; }
2786 second += (d - zList[i] < 120); // count in-range ads
2787 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2791 second = (second > 1);
2792 if(displayed != closest || second != lastSecond) {
2793 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2794 lastSecond = second; displayed = closest;
2796 if(click == Press) {
2797 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2800 } // on press 'hit', only show info
2801 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2802 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2803 SendToICS(ics_prefix);
2805 return TRUE; // let incoming board of started game pop down the graph
2806 } else if(click == Release) { // release 'miss' is ignored
2807 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2808 if(moving == 2) { // right up-click
2809 nrOfSeekAds = 0; // refresh graph
2810 soughtPending = TRUE;
2811 SendToICS(ics_prefix);
2812 SendToICS("sought\n"); // should this be "sought all"?
2815 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2816 // press miss or release hit 'pop down' seek graph
2817 seekGraphUp = FALSE;
2818 DrawPosition(TRUE, NULL);
2824 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2826 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2827 #define STARTED_NONE 0
2828 #define STARTED_MOVES 1
2829 #define STARTED_BOARD 2
2830 #define STARTED_OBSERVE 3
2831 #define STARTED_HOLDINGS 4
2832 #define STARTED_CHATTER 5
2833 #define STARTED_COMMENT 6
2834 #define STARTED_MOVES_NOHIDE 7
2836 static int started = STARTED_NONE;
2837 static char parse[20000];
2838 static int parse_pos = 0;
2839 static char buf[BUF_SIZE + 1];
2840 static int firstTime = TRUE, intfSet = FALSE;
2841 static ColorClass prevColor = ColorNormal;
2842 static int savingComment = FALSE;
2843 static int cmatch = 0; // continuation sequence match
2850 int backup; /* [DM] For zippy color lines */
2852 char talker[MSG_SIZ]; // [HGM] chat
2853 int channel, collective=0;
2855 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2857 if (appData.debugMode) {
2859 fprintf(debugFP, "<ICS: ");
2860 show_bytes(debugFP, data, count);
2861 fprintf(debugFP, "\n");
2865 if (appData.debugMode) { int f = forwardMostMove;
2866 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2867 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2868 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2871 /* If last read ended with a partial line that we couldn't parse,
2872 prepend it to the new read and try again. */
2873 if (leftover_len > 0) {
2874 for (i=0; i<leftover_len; i++)
2875 buf[i] = buf[leftover_start + i];
2878 /* copy new characters into the buffer */
2879 bp = buf + leftover_len;
2880 buf_len=leftover_len;
2881 for (i=0; i<count; i++)
2884 if (data[i] == '\r')
2887 // join lines split by ICS?
2888 if (!appData.noJoin)
2891 Joining just consists of finding matches against the
2892 continuation sequence, and discarding that sequence
2893 if found instead of copying it. So, until a match
2894 fails, there's nothing to do since it might be the
2895 complete sequence, and thus, something we don't want
2898 if (data[i] == cont_seq[cmatch])
2901 if (cmatch == strlen(cont_seq))
2903 cmatch = 0; // complete match. just reset the counter
2906 it's possible for the ICS to not include the space
2907 at the end of the last word, making our [correct]
2908 join operation fuse two separate words. the server
2909 does this when the space occurs at the width setting.
2911 if (!buf_len || buf[buf_len-1] != ' ')
2922 match failed, so we have to copy what matched before
2923 falling through and copying this character. In reality,
2924 this will only ever be just the newline character, but
2925 it doesn't hurt to be precise.
2927 strncpy(bp, cont_seq, cmatch);
2939 buf[buf_len] = NULLCHAR;
2940 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2945 while (i < buf_len) {
2946 /* Deal with part of the TELNET option negotiation
2947 protocol. We refuse to do anything beyond the
2948 defaults, except that we allow the WILL ECHO option,
2949 which ICS uses to turn off password echoing when we are
2950 directly connected to it. We reject this option
2951 if localLineEditing mode is on (always on in xboard)
2952 and we are talking to port 23, which might be a real
2953 telnet server that will try to keep WILL ECHO on permanently.
2955 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2956 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2957 unsigned char option;
2959 switch ((unsigned char) buf[++i]) {
2961 if (appData.debugMode)
2962 fprintf(debugFP, "\n<WILL ");
2963 switch (option = (unsigned char) buf[++i]) {
2965 if (appData.debugMode)
2966 fprintf(debugFP, "ECHO ");
2967 /* Reply only if this is a change, according
2968 to the protocol rules. */
2969 if (remoteEchoOption) break;
2970 if (appData.localLineEditing &&
2971 atoi(appData.icsPort) == TN_PORT) {
2972 TelnetRequest(TN_DONT, TN_ECHO);
2975 TelnetRequest(TN_DO, TN_ECHO);
2976 remoteEchoOption = TRUE;
2980 if (appData.debugMode)
2981 fprintf(debugFP, "%d ", option);
2982 /* Whatever this is, we don't want it. */
2983 TelnetRequest(TN_DONT, option);
2988 if (appData.debugMode)
2989 fprintf(debugFP, "\n<WONT ");
2990 switch (option = (unsigned char) buf[++i]) {
2992 if (appData.debugMode)
2993 fprintf(debugFP, "ECHO ");
2994 /* Reply only if this is a change, according
2995 to the protocol rules. */
2996 if (!remoteEchoOption) break;
2998 TelnetRequest(TN_DONT, TN_ECHO);
2999 remoteEchoOption = FALSE;
3002 if (appData.debugMode)
3003 fprintf(debugFP, "%d ", (unsigned char) option);
3004 /* Whatever this is, it must already be turned
3005 off, because we never agree to turn on
3006 anything non-default, so according to the
3007 protocol rules, we don't reply. */
3012 if (appData.debugMode)
3013 fprintf(debugFP, "\n<DO ");
3014 switch (option = (unsigned char) buf[++i]) {
3016 /* Whatever this is, we refuse to do it. */
3017 if (appData.debugMode)
3018 fprintf(debugFP, "%d ", option);
3019 TelnetRequest(TN_WONT, option);
3024 if (appData.debugMode)
3025 fprintf(debugFP, "\n<DONT ");
3026 switch (option = (unsigned char) buf[++i]) {
3028 if (appData.debugMode)
3029 fprintf(debugFP, "%d ", option);
3030 /* Whatever this is, we are already not doing
3031 it, because we never agree to do anything
3032 non-default, so according to the protocol
3033 rules, we don't reply. */
3038 if (appData.debugMode)
3039 fprintf(debugFP, "\n<IAC ");
3040 /* Doubled IAC; pass it through */
3044 if (appData.debugMode)
3045 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3046 /* Drop all other telnet commands on the floor */
3049 if (oldi > next_out)
3050 SendToPlayer(&buf[next_out], oldi - next_out);
3056 /* OK, this at least will *usually* work */
3057 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3061 if (loggedOn && !intfSet) {
3062 if (ics_type == ICS_ICC) {
3063 snprintf(str, MSG_SIZ,
3064 "/set-quietly interface %s\n/set-quietly style 12\n",
3066 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3067 strcat(str, "/set-2 51 1\n/set seek 1\n");
3068 } else if (ics_type == ICS_CHESSNET) {
3069 snprintf(str, MSG_SIZ, "/style 12\n");
3071 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3072 strcat(str, programVersion);
3073 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3074 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3075 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3077 strcat(str, "$iset nohighlight 1\n");
3079 strcat(str, "$iset lock 1\n$style 12\n");
3082 NotifyFrontendLogin();
3086 if (started == STARTED_COMMENT) {
3087 /* Accumulate characters in comment */
3088 parse[parse_pos++] = buf[i];
3089 if (buf[i] == '\n') {
3090 parse[parse_pos] = NULLCHAR;
3091 if(chattingPartner>=0) {
3093 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3094 OutputChatMessage(chattingPartner, mess);
3095 if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3097 talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3098 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3099 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3100 OutputChatMessage(p, mess);
3104 chattingPartner = -1;
3105 if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3108 if(!suppressKibitz) // [HGM] kibitz
3109 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3110 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3111 int nrDigit = 0, nrAlph = 0, j;
3112 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3113 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3114 parse[parse_pos] = NULLCHAR;
3115 // try to be smart: if it does not look like search info, it should go to
3116 // ICS interaction window after all, not to engine-output window.
3117 for(j=0; j<parse_pos; j++) { // count letters and digits
3118 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3119 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3120 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3122 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3123 int depth=0; float score;
3124 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3125 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3126 pvInfoList[forwardMostMove-1].depth = depth;
3127 pvInfoList[forwardMostMove-1].score = 100*score;
3129 OutputKibitz(suppressKibitz, parse);
3132 if(gameMode == IcsObserving) // restore original ICS messages
3133 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3134 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3136 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3137 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3138 SendToPlayer(tmp, strlen(tmp));
3140 next_out = i+1; // [HGM] suppress printing in ICS window
3142 started = STARTED_NONE;
3144 /* Don't match patterns against characters in comment */
3149 if (started == STARTED_CHATTER) {
3150 if (buf[i] != '\n') {
3151 /* Don't match patterns against characters in chatter */
3155 started = STARTED_NONE;
3156 if(suppressKibitz) next_out = i+1;
3159 /* Kludge to deal with rcmd protocol */
3160 if (firstTime && looking_at(buf, &i, "\001*")) {
3161 DisplayFatalError(&buf[1], 0, 1);
3167 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3170 if (appData.debugMode)
3171 fprintf(debugFP, "ics_type %d\n", ics_type);
3174 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3175 ics_type = ICS_FICS;
3177 if (appData.debugMode)
3178 fprintf(debugFP, "ics_type %d\n", ics_type);
3181 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3182 ics_type = ICS_CHESSNET;
3184 if (appData.debugMode)
3185 fprintf(debugFP, "ics_type %d\n", ics_type);
3190 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3191 looking_at(buf, &i, "Logging you in as \"*\"") ||
3192 looking_at(buf, &i, "will be \"*\""))) {
3193 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3197 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3199 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3200 DisplayIcsInteractionTitle(buf);
3201 have_set_title = TRUE;
3204 /* skip finger notes */
3205 if (started == STARTED_NONE &&
3206 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3207 (buf[i] == '1' && buf[i+1] == '0')) &&
3208 buf[i+2] == ':' && buf[i+3] == ' ') {
3209 started = STARTED_CHATTER;
3215 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3216 if(appData.seekGraph) {
3217 if(soughtPending && MatchSoughtLine(buf+i)) {
3218 i = strstr(buf+i, "rated") - buf;
3219 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220 next_out = leftover_start = i;
3221 started = STARTED_CHATTER;
3222 suppressKibitz = TRUE;
3225 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3226 && looking_at(buf, &i, "* ads displayed")) {
3227 soughtPending = FALSE;
3232 if(appData.autoRefresh) {
3233 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3234 int s = (ics_type == ICS_ICC); // ICC format differs
3236 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3237 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3238 looking_at(buf, &i, "*% "); // eat prompt
3239 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3240 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3241 next_out = i; // suppress
3244 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3245 char *p = star_match[0];
3247 if(seekGraphUp) RemoveSeekAd(atoi(p));
3248 while(*p && *p++ != ' '); // next
3250 looking_at(buf, &i, "*% "); // eat prompt
3251 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3258 /* skip formula vars */
3259 if (started == STARTED_NONE &&
3260 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3261 started = STARTED_CHATTER;
3266 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3267 if (appData.autoKibitz && started == STARTED_NONE &&
3268 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3269 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3270 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3271 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3272 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3273 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3274 suppressKibitz = TRUE;
3275 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3277 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3278 && (gameMode == IcsPlayingWhite)) ||
3279 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3280 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3281 started = STARTED_CHATTER; // own kibitz we simply discard
3283 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3284 parse_pos = 0; parse[0] = NULLCHAR;
3285 savingComment = TRUE;
3286 suppressKibitz = gameMode != IcsObserving ? 2 :
3287 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3291 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3292 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3293 && atoi(star_match[0])) {
3294 // suppress the acknowledgements of our own autoKibitz
3296 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3297 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3298 SendToPlayer(star_match[0], strlen(star_match[0]));
3299 if(looking_at(buf, &i, "*% ")) // eat prompt
3300 suppressKibitz = FALSE;
3304 } // [HGM] kibitz: end of patch
3306 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3308 // [HGM] chat: intercept tells by users for which we have an open chat window
3310 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3311 looking_at(buf, &i, "* whispers:") ||
3312 looking_at(buf, &i, "* kibitzes:") ||
3313 looking_at(buf, &i, "* shouts:") ||
3314 looking_at(buf, &i, "* c-shouts:") ||
3315 looking_at(buf, &i, "--> * ") ||
3316 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3317 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3318 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3319 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3321 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3322 chattingPartner = -1; collective = 0;
3324 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3325 for(p=0; p<MAX_CHAT; p++) {
3327 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3328 talker[0] = '['; strcat(talker, "] ");
3329 Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3330 chattingPartner = p; break;
3333 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3334 for(p=0; p<MAX_CHAT; p++) {
3336 if(!strcmp("kibitzes", chatPartner[p])) {
3337 talker[0] = '['; strcat(talker, "] ");
3338 chattingPartner = p; break;
3341 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3342 for(p=0; p<MAX_CHAT; p++) {
3344 if(!strcmp("whispers", chatPartner[p])) {
3345 talker[0] = '['; strcat(talker, "] ");
3346 chattingPartner = p; break;
3349 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3350 if(buf[i-8] == '-' && buf[i-3] == 't')
3351 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3353 if(!strcmp("c-shouts", chatPartner[p])) {
3354 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3355 chattingPartner = p; break;
3358 if(chattingPartner < 0)
3359 for(p=0; p<MAX_CHAT; p++) {
3361 if(!strcmp("shouts", chatPartner[p])) {
3362 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3363 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3364 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3365 chattingPartner = p; break;
3369 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3370 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3372 Colorize(ColorTell, FALSE);
3373 if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3375 chattingPartner = p; break;
3377 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3378 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3379 started = STARTED_COMMENT;
3380 parse_pos = 0; parse[0] = NULLCHAR;
3381 savingComment = 3 + chattingPartner; // counts as TRUE
3382 if(collective == 3) i = oldi; else {
3383 suppressKibitz = TRUE;
3384 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3385 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3389 } // [HGM] chat: end of patch
3392 if (appData.zippyTalk || appData.zippyPlay) {
3393 /* [DM] Backup address for color zippy lines */
3395 if (loggedOn == TRUE)
3396 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3397 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3399 } // [DM] 'else { ' deleted
3401 /* Regular tells and says */
3402 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3403 looking_at(buf, &i, "* (your partner) tells you: ") ||
3404 looking_at(buf, &i, "* says: ") ||
3405 /* Don't color "message" or "messages" output */
3406 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3407 looking_at(buf, &i, "*. * at *:*: ") ||
3408 looking_at(buf, &i, "--* (*:*): ") ||
3409 /* Message notifications (same color as tells) */
3410 looking_at(buf, &i, "* has left a message ") ||
3411 looking_at(buf, &i, "* just sent you a message:\n") ||
3412 /* Whispers and kibitzes */
3413 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3414 looking_at(buf, &i, "* kibitzes: ") ||
3416 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3418 if (tkind == 1 && strchr(star_match[0], ':')) {
3419 /* Avoid "tells you:" spoofs in channels */
3422 if (star_match[0][0] == NULLCHAR ||
3423 strchr(star_match[0], ' ') ||
3424 (tkind == 3 && strchr(star_match[1], ' '))) {
3425 /* Reject bogus matches */
3428 if (appData.colorize) {
3429 if (oldi > next_out) {
3430 SendToPlayer(&buf[next_out], oldi - next_out);
3435 Colorize(ColorTell, FALSE);
3436 curColor = ColorTell;
3439 Colorize(ColorKibitz, FALSE);
3440 curColor = ColorKibitz;
3443 p = strrchr(star_match[1], '(');
3450 Colorize(ColorChannel1, FALSE);
3451 curColor = ColorChannel1;
3453 Colorize(ColorChannel, FALSE);
3454 curColor = ColorChannel;
3458 curColor = ColorNormal;
3462 if (started == STARTED_NONE && appData.autoComment &&
3463 (gameMode == IcsObserving ||
3464 gameMode == IcsPlayingWhite ||
3465 gameMode == IcsPlayingBlack)) {
3466 parse_pos = i - oldi;
3467 memcpy(parse, &buf[oldi], parse_pos);
3468 parse[parse_pos] = NULLCHAR;
3469 started = STARTED_COMMENT;
3470 savingComment = TRUE;
3471 } else if(collective != 3) {
3472 started = STARTED_CHATTER;
3473 savingComment = FALSE;
3480 if (looking_at(buf, &i, "* s-shouts: ") ||
3481 looking_at(buf, &i, "* c-shouts: ")) {
3482 if (appData.colorize) {
3483 if (oldi > next_out) {
3484 SendToPlayer(&buf[next_out], oldi - next_out);
3487 Colorize(ColorSShout, FALSE);
3488 curColor = ColorSShout;
3491 started = STARTED_CHATTER;
3495 if (looking_at(buf, &i, "--->")) {
3500 if (looking_at(buf, &i, "* shouts: ") ||
3501 looking_at(buf, &i, "--> ")) {
3502 if (appData.colorize) {
3503 if (oldi > next_out) {
3504 SendToPlayer(&buf[next_out], oldi - next_out);
3507 Colorize(ColorShout, FALSE);
3508 curColor = ColorShout;
3511 started = STARTED_CHATTER;
3515 if (looking_at( buf, &i, "Challenge:")) {
3516 if (appData.colorize) {
3517 if (oldi > next_out) {
3518 SendToPlayer(&buf[next_out], oldi - next_out);
3521 Colorize(ColorChallenge, FALSE);
3522 curColor = ColorChallenge;
3528 if (looking_at(buf, &i, "* offers you") ||
3529 looking_at(buf, &i, "* offers to be") ||
3530 looking_at(buf, &i, "* would like to") ||
3531 looking_at(buf, &i, "* requests to") ||
3532 looking_at(buf, &i, "Your opponent offers") ||
3533 looking_at(buf, &i, "Your opponent requests")) {
3535 if (appData.colorize) {
3536 if (oldi > next_out) {
3537 SendToPlayer(&buf[next_out], oldi - next_out);
3540 Colorize(ColorRequest, FALSE);
3541 curColor = ColorRequest;
3546 if (looking_at(buf, &i, "* (*) seeking")) {
3547 if (appData.colorize) {
3548 if (oldi > next_out) {
3549 SendToPlayer(&buf[next_out], oldi - next_out);
3552 Colorize(ColorSeek, FALSE);
3553 curColor = ColorSeek;
3558 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3560 if (looking_at(buf, &i, "\\ ")) {
3561 if (prevColor != ColorNormal) {
3562 if (oldi > next_out) {
3563 SendToPlayer(&buf[next_out], oldi - next_out);
3566 Colorize(prevColor, TRUE);
3567 curColor = prevColor;
3569 if (savingComment) {
3570 parse_pos = i - oldi;
3571 memcpy(parse, &buf[oldi], parse_pos);
3572 parse[parse_pos] = NULLCHAR;
3573 started = STARTED_COMMENT;
3574 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3575 chattingPartner = savingComment - 3; // kludge to remember the box
3577 started = STARTED_CHATTER;
3582 if (looking_at(buf, &i, "Black Strength :") ||
3583 looking_at(buf, &i, "<<< style 10 board >>>") ||
3584 looking_at(buf, &i, "<10>") ||
3585 looking_at(buf, &i, "#@#")) {
3586 /* Wrong board style */
3588 SendToICS(ics_prefix);
3589 SendToICS("set style 12\n");
3590 SendToICS(ics_prefix);
3591 SendToICS("refresh\n");
3595 if (looking_at(buf, &i, "login:")) {
3596 if (!have_sent_ICS_logon) {
3598 have_sent_ICS_logon = 1;
3599 else // no init script was found
3600 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3601 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3602 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3607 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3608 (looking_at(buf, &i, "\n<12> ") ||
3609 looking_at(buf, &i, "<12> "))) {
3611 if (oldi > next_out) {
3612 SendToPlayer(&buf[next_out], oldi - next_out);
3615 started = STARTED_BOARD;
3620 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3621 looking_at(buf, &i, "<b1> ")) {
3622 if (oldi > next_out) {
3623 SendToPlayer(&buf[next_out], oldi - next_out);
3626 started = STARTED_HOLDINGS;
3631 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3633 /* Header for a move list -- first line */
3635 switch (ics_getting_history) {
3639 case BeginningOfGame:
3640 /* User typed "moves" or "oldmoves" while we
3641 were idle. Pretend we asked for these
3642 moves and soak them up so user can step
3643 through them and/or save them.
3646 gameMode = IcsObserving;
3649 ics_getting_history = H_GOT_UNREQ_HEADER;
3651 case EditGame: /*?*/
3652 case EditPosition: /*?*/
3653 /* Should above feature work in these modes too? */
3654 /* For now it doesn't */
3655 ics_getting_history = H_GOT_UNWANTED_HEADER;
3658 ics_getting_history = H_GOT_UNWANTED_HEADER;
3663 /* Is this the right one? */
3664 if (gameInfo.white && gameInfo.black &&
3665 strcmp(gameInfo.white, star_match[0]) == 0 &&
3666 strcmp(gameInfo.black, star_match[2]) == 0) {
3668 ics_getting_history = H_GOT_REQ_HEADER;
3671 case H_GOT_REQ_HEADER:
3672 case H_GOT_UNREQ_HEADER:
3673 case H_GOT_UNWANTED_HEADER:
3674 case H_GETTING_MOVES:
3675 /* Should not happen */
3676 DisplayError(_("Error gathering move list: two headers"), 0);
3677 ics_getting_history = H_FALSE;
3681 /* Save player ratings into gameInfo if needed */
3682 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3683 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3684 (gameInfo.whiteRating == -1 ||
3685 gameInfo.blackRating == -1)) {
3687 gameInfo.whiteRating = string_to_rating(star_match[1]);
3688 gameInfo.blackRating = string_to_rating(star_match[3]);
3689 if (appData.debugMode)
3690 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3691 gameInfo.whiteRating, gameInfo.blackRating);
3696 if (looking_at(buf, &i,
3697 "* * match, initial time: * minute*, increment: * second")) {
3698 /* Header for a move list -- second line */
3699 /* Initial board will follow if this is a wild game */
3700 if (gameInfo.event != NULL) free(gameInfo.event);
3701 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3702 gameInfo.event = StrSave(str);
3703 /* [HGM] we switched variant. Translate boards if needed. */
3704 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3708 if (looking_at(buf, &i, "Move ")) {
3709 /* Beginning of a move list */
3710 switch (ics_getting_history) {
3712 /* Normally should not happen */
3713 /* Maybe user hit reset while we were parsing */
3716 /* Happens if we are ignoring a move list that is not
3717 * the one we just requested. Common if the user
3718 * tries to observe two games without turning off
3721 case H_GETTING_MOVES:
3722 /* Should not happen */
3723 DisplayError(_("Error gathering move list: nested"), 0);
3724 ics_getting_history = H_FALSE;
3726 case H_GOT_REQ_HEADER:
3727 ics_getting_history = H_GETTING_MOVES;
3728 started = STARTED_MOVES;
3730 if (oldi > next_out) {
3731 SendToPlayer(&buf[next_out], oldi - next_out);
3734 case H_GOT_UNREQ_HEADER:
3735 ics_getting_history = H_GETTING_MOVES;
3736 started = STARTED_MOVES_NOHIDE;
3739 case H_GOT_UNWANTED_HEADER:
3740 ics_getting_history = H_FALSE;
3746 if (looking_at(buf, &i, "% ") ||
3747 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3748 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3749 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3750 soughtPending = FALSE;
3754 if(suppressKibitz) next_out = i;
3755 savingComment = FALSE;
3759 case STARTED_MOVES_NOHIDE:
3760 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3761 parse[parse_pos + i - oldi] = NULLCHAR;
3762 ParseGameHistory(parse);
3764 if (appData.zippyPlay && first.initDone) {
3765 FeedMovesToProgram(&first, forwardMostMove);
3766 if (gameMode == IcsPlayingWhite) {
3767 if (WhiteOnMove(forwardMostMove)) {
3768 if (first.sendTime) {
3769 if (first.useColors) {
3770 SendToProgram("black\n", &first);
3772 SendTimeRemaining(&first, TRUE);
3774 if (first.useColors) {
3775 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3777 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3778 first.maybeThinking = TRUE;
3780 if (first.usePlayother) {
3781 if (first.sendTime) {
3782 SendTimeRemaining(&first, TRUE);
3784 SendToProgram("playother\n", &first);
3790 } else if (gameMode == IcsPlayingBlack) {
3791 if (!WhiteOnMove(forwardMostMove)) {
3792 if (first.sendTime) {
3793 if (first.useColors) {
3794 SendToProgram("white\n", &first);
3796 SendTimeRemaining(&first, FALSE);
3798 if (first.useColors) {
3799 SendToProgram("black\n", &first);
3801 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3802 first.maybeThinking = TRUE;
3804 if (first.usePlayother) {
3805 if (first.sendTime) {
3806 SendTimeRemaining(&first, FALSE);
3808 SendToProgram("playother\n", &first);
3817 if (gameMode == IcsObserving && ics_gamenum == -1) {
3818 /* Moves came from oldmoves or moves command
3819 while we weren't doing anything else.
3821 currentMove = forwardMostMove;
3822 ClearHighlights();/*!!could figure this out*/
3823 flipView = appData.flipView;
3824 DrawPosition(TRUE, boards[currentMove]);
3825 DisplayBothClocks();
3826 snprintf(str, MSG_SIZ, "%s %s %s",
3827 gameInfo.white, _("vs."), gameInfo.black);
3831 /* Moves were history of an active game */
3832 if (gameInfo.resultDetails != NULL) {
3833 free(gameInfo.resultDetails);
3834 gameInfo.resultDetails = NULL;
3837 HistorySet(parseList, backwardMostMove,
3838 forwardMostMove, currentMove-1);
3839 DisplayMove(currentMove - 1);
3840 if (started == STARTED_MOVES) next_out = i;
3841 started = STARTED_NONE;
3842 ics_getting_history = H_FALSE;
3845 case STARTED_OBSERVE:
3846 started = STARTED_NONE;
3847 SendToICS(ics_prefix);
3848 SendToICS("refresh\n");
3854 if(bookHit) { // [HGM] book: simulate book reply
3855 static char bookMove[MSG_SIZ]; // a bit generous?
3857 programStats.nodes = programStats.depth = programStats.time =
3858 programStats.score = programStats.got_only_move = 0;
3859 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3861 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3862 strcat(bookMove, bookHit);
3863 HandleMachineMove(bookMove, &first);
3868 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3869 started == STARTED_HOLDINGS ||
3870 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3871 /* Accumulate characters in move list or board */
3872 parse[parse_pos++] = buf[i];
3875 /* Start of game messages. Mostly we detect start of game
3876 when the first board image arrives. On some versions
3877 of the ICS, though, we need to do a "refresh" after starting
3878 to observe in order to get the current board right away. */
3879 if (looking_at(buf, &i, "Adding game * to observation list")) {
3880 started = STARTED_OBSERVE;
3884 /* Handle auto-observe */
3885 if (appData.autoObserve &&
3886 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3887 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3889 /* Choose the player that was highlighted, if any. */
3890 if (star_match[0][0] == '\033' ||
3891 star_match[1][0] != '\033') {
3892 player = star_match[0];
3894 player = star_match[2];
3896 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3897 ics_prefix, StripHighlightAndTitle(player));
3900 /* Save ratings from notify string */
3901 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3902 player1Rating = string_to_rating(star_match[1]);
3903 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3904 player2Rating = string_to_rating(star_match[3]);
3906 if (appData.debugMode)
3908 "Ratings from 'Game notification:' %s %d, %s %d\n",
3909 player1Name, player1Rating,
3910 player2Name, player2Rating);
3915 /* Deal with automatic examine mode after a game,
3916 and with IcsObserving -> IcsExamining transition */
3917 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3918 looking_at(buf, &i, "has made you an examiner of game *")) {
3920 int gamenum = atoi(star_match[0]);
3921 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3922 gamenum == ics_gamenum) {
3923 /* We were already playing or observing this game;
3924 no need to refetch history */
3925 gameMode = IcsExamining;
3927 pauseExamForwardMostMove = forwardMostMove;
3928 } else if (currentMove < forwardMostMove) {
3929 ForwardInner(forwardMostMove);
3932 /* I don't think this case really can happen */
3933 SendToICS(ics_prefix);
3934 SendToICS("refresh\n");
3939 /* Error messages */
3940 // if (ics_user_moved) {
3941 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3942 if (looking_at(buf, &i, "Illegal move") ||
3943 looking_at(buf, &i, "Not a legal move") ||
3944 looking_at(buf, &i, "Your king is in check") ||
3945 looking_at(buf, &i, "It isn't your turn") ||
3946 looking_at(buf, &i, "It is not your move")) {
3948 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3949 currentMove = forwardMostMove-1;
3950 DisplayMove(currentMove - 1); /* before DMError */
3951 DrawPosition(FALSE, boards[currentMove]);
3952 SwitchClocks(forwardMostMove-1); // [HGM] race
3953 DisplayBothClocks();
3955 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3961 if (looking_at(buf, &i, "still have time") ||
3962 looking_at(buf, &i, "not out of time") ||
3963 looking_at(buf, &i, "either player is out of time") ||
3964 looking_at(buf, &i, "has timeseal; checking")) {
3965 /* We must have called his flag a little too soon */
3966 whiteFlag = blackFlag = FALSE;
3970 if (looking_at(buf, &i, "added * seconds to") ||
3971 looking_at(buf, &i, "seconds were added to")) {
3972 /* Update the clocks */
3973 SendToICS(ics_prefix);
3974 SendToICS("refresh\n");
3978 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3979 ics_clock_paused = TRUE;
3984 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3985 ics_clock_paused = FALSE;
3990 /* Grab player ratings from the Creating: message.
3991 Note we have to check for the special case when
3992 the ICS inserts things like [white] or [black]. */
3993 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3994 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3996 0 player 1 name (not necessarily white)
3998 2 empty, white, or black (IGNORED)
3999 3 player 2 name (not necessarily black)
4002 The names/ratings are sorted out when the game
4003 actually starts (below).
4005 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4006 player1Rating = string_to_rating(star_match[1]);
4007 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4008 player2Rating = string_to_rating(star_match[4]);
4010 if (appData.debugMode)
4012 "Ratings from 'Creating:' %s %d, %s %d\n",
4013 player1Name, player1Rating,
4014 player2Name, player2Rating);
4019 /* Improved generic start/end-of-game messages */
4020 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4021 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4022 /* If tkind == 0: */
4023 /* star_match[0] is the game number */
4024 /* [1] is the white player's name */
4025 /* [2] is the black player's name */
4026 /* For end-of-game: */
4027 /* [3] is the reason for the game end */
4028 /* [4] is a PGN end game-token, preceded by " " */
4029 /* For start-of-game: */
4030 /* [3] begins with "Creating" or "Continuing" */
4031 /* [4] is " *" or empty (don't care). */
4032 int gamenum = atoi(star_match[0]);
4033 char *whitename, *blackname, *why, *endtoken;
4034 ChessMove endtype = EndOfFile;
4037 whitename = star_match[1];
4038 blackname = star_match[2];
4039 why = star_match[3];
4040 endtoken = star_match[4];
4042 whitename = star_match[1];
4043 blackname = star_match[3];
4044 why = star_match[5];
4045 endtoken = star_match[6];
4048 /* Game start messages */
4049 if (strncmp(why, "Creating ", 9) == 0 ||
4050 strncmp(why, "Continuing ", 11) == 0) {
4051 gs_gamenum = gamenum;
4052 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4053 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4054 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4056 if (appData.zippyPlay) {
4057 ZippyGameStart(whitename, blackname);
4060 partnerBoardValid = FALSE; // [HGM] bughouse
4064 /* Game end messages */
4065 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4066 ics_gamenum != gamenum) {
4069 while (endtoken[0] == ' ') endtoken++;
4070 switch (endtoken[0]) {
4073 endtype = GameUnfinished;
4076 endtype = BlackWins;
4079 if (endtoken[1] == '/')
4080 endtype = GameIsDrawn;
4082 endtype = WhiteWins;
4085 GameEnds(endtype, why, GE_ICS);
4087 if (appData.zippyPlay && first.initDone) {
4088 ZippyGameEnd(endtype, why);
4089 if (first.pr == NoProc) {
4090 /* Start the next process early so that we'll
4091 be ready for the next challenge */
4092 StartChessProgram(&first);
4094 /* Send "new" early, in case this command takes
4095 a long time to finish, so that we'll be ready
4096 for the next challenge. */
4097 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4101 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4105 if (looking_at(buf, &i, "Removing game * from observation") ||
4106 looking_at(buf, &i, "no longer observing game *") ||
4107 looking_at(buf, &i, "Game * (*) has no examiners")) {
4108 if (gameMode == IcsObserving &&
4109 atoi(star_match[0]) == ics_gamenum)
4111 /* icsEngineAnalyze */
4112 if (appData.icsEngineAnalyze) {
4119 ics_user_moved = FALSE;
4124 if (looking_at(buf, &i, "no longer examining game *")) {
4125 if (gameMode == IcsExamining &&
4126 atoi(star_match[0]) == ics_gamenum)
4130 ics_user_moved = FALSE;
4135 /* Advance leftover_start past any newlines we find,
4136 so only partial lines can get reparsed */
4137 if (looking_at(buf, &i, "\n")) {
4138 prevColor = curColor;
4139 if (curColor != ColorNormal) {
4140 if (oldi > next_out) {
4141 SendToPlayer(&buf[next_out], oldi - next_out);
4144 Colorize(ColorNormal, FALSE);
4145 curColor = ColorNormal;
4147 if (started == STARTED_BOARD) {
4148 started = STARTED_NONE;
4149 parse[parse_pos] = NULLCHAR;
4150 ParseBoard12(parse);
4153 /* Send premove here */
4154 if (appData.premove) {
4156 if (currentMove == 0 &&
4157 gameMode == IcsPlayingWhite &&
4158 appData.premoveWhite) {
4159 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4160 if (appData.debugMode)
4161 fprintf(debugFP, "Sending premove:\n");
4163 } else if (currentMove == 1 &&
4164 gameMode == IcsPlayingBlack &&
4165 appData.premoveBlack) {
4166 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4167 if (appData.debugMode)
4168 fprintf(debugFP, "Sending premove:\n");
4170 } else if (gotPremove) {
4171 int oldFMM = forwardMostMove;
4173 ClearPremoveHighlights();
4174 if (appData.debugMode)
4175 fprintf(debugFP, "Sending premove:\n");
4176 UserMoveEvent(premoveFromX, premoveFromY,
4177 premoveToX, premoveToY,
4179 if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4180 if(moveList[oldFMM-1][1] != '@')
4181 SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4182 moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4184 SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4189 /* Usually suppress following prompt */
4190 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4191 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4192 if (looking_at(buf, &i, "*% ")) {
4193 savingComment = FALSE;
4198 } else if (started == STARTED_HOLDINGS) {
4200 char new_piece[MSG_SIZ];
4201 started = STARTED_NONE;
4202 parse[parse_pos] = NULLCHAR;
4203 if (appData.debugMode)
4204 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4205 parse, currentMove);
4206 if (sscanf(parse, " game %d", &gamenum) == 1) {
4207 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4208 if (gameInfo.variant == VariantNormal) {
4209 /* [HGM] We seem to switch variant during a game!
4210 * Presumably no holdings were displayed, so we have
4211 * to move the position two files to the right to
4212 * create room for them!
4214 VariantClass newVariant;
4215 switch(gameInfo.boardWidth) { // base guess on board width
4216 case 9: newVariant = VariantShogi; break;
4217 case 10: newVariant = VariantGreat; break;
4218 default: newVariant = VariantCrazyhouse; break;
4220 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4221 /* Get a move list just to see the header, which
4222 will tell us whether this is really bug or zh */
4223 if (ics_getting_history == H_FALSE) {
4224 ics_getting_history = H_REQUESTED;
4225 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4229 new_piece[0] = NULLCHAR;
4230 sscanf(parse, "game %d white [%s black [%s <- %s",
4231 &gamenum, white_holding, black_holding,
4233 white_holding[strlen(white_holding)-1] = NULLCHAR;
4234 black_holding[strlen(black_holding)-1] = NULLCHAR;
4235 /* [HGM] copy holdings to board holdings area */
4236 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4237 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4238 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4240 if (appData.zippyPlay && first.initDone) {
4241 ZippyHoldings(white_holding, black_holding,
4245 if (tinyLayout || smallLayout) {
4246 char wh[16], bh[16];
4247 PackHolding(wh, white_holding);
4248 PackHolding(bh, black_holding);
4249 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4250 gameInfo.white, gameInfo.black);
4252 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4253 gameInfo.white, white_holding, _("vs."),
4254 gameInfo.black, black_holding);
4256 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4257 DrawPosition(FALSE, boards[currentMove]);
4259 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4260 sscanf(parse, "game %d white [%s black [%s <- %s",
4261 &gamenum, white_holding, black_holding,
4263 white_holding[strlen(white_holding)-1] = NULLCHAR;
4264 black_holding[strlen(black_holding)-1] = NULLCHAR;
4265 /* [HGM] copy holdings to partner-board holdings area */
4266 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4267 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4268 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4269 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4270 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4273 /* Suppress following prompt */
4274 if (looking_at(buf, &i, "*% ")) {
4275 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4276 savingComment = FALSE;
4284 i++; /* skip unparsed character and loop back */
4287 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4288 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4289 // SendToPlayer(&buf[next_out], i - next_out);
4290 started != STARTED_HOLDINGS && leftover_start > next_out) {
4291 SendToPlayer(&buf[next_out], leftover_start - next_out);
4295 leftover_len = buf_len - leftover_start;
4296 /* if buffer ends with something we couldn't parse,
4297 reparse it after appending the next read */
4299 } else if (count == 0) {
4300 RemoveInputSource(isr);
4301 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4303 DisplayFatalError(_("Error reading from ICS"), error, 1);
4308 /* Board style 12 looks like this:
4310 <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
4312 * The "<12> " is stripped before it gets to this routine. The two
4313 * trailing 0's (flip state and clock ticking) are later addition, and
4314 * some chess servers may not have them, or may have only the first.
4315 * Additional trailing fields may be added in the future.
4318 #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"
4320 #define RELATION_OBSERVING_PLAYED 0
4321 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4322 #define RELATION_PLAYING_MYMOVE 1
4323 #define RELATION_PLAYING_NOTMYMOVE -1
4324 #define RELATION_EXAMINING 2
4325 #define RELATION_ISOLATED_BOARD -3
4326 #define RELATION_STARTING_POSITION -4 /* FICS only */
4329 ParseBoard12 (char *string)
4333 char *bookHit = NULL; // [HGM] book
4335 GameMode newGameMode;
4336 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4337 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4338 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4339 char to_play, board_chars[200];
4340 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4341 char black[32], white[32];
4343 int prevMove = currentMove;
4346 int fromX, fromY, toX, toY;
4348 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4349 Boolean weird = FALSE, reqFlag = FALSE;
4351 fromX = fromY = toX = toY = -1;
4355 if (appData.debugMode)
4356 fprintf(debugFP, "Parsing board: %s\n", string);
4358 move_str[0] = NULLCHAR;
4359 elapsed_time[0] = NULLCHAR;
4360 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4362 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4363 if(string[i] == ' ') { ranks++; files = 0; }
4365 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4368 for(j = 0; j <i; j++) board_chars[j] = string[j];
4369 board_chars[i] = '\0';
4372 n = sscanf(string, PATTERN, &to_play, &double_push,
4373 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4374 &gamenum, white, black, &relation, &basetime, &increment,
4375 &white_stren, &black_stren, &white_time, &black_time,
4376 &moveNum, str, elapsed_time, move_str, &ics_flip,
4380 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4381 DisplayError(str, 0);
4385 /* Convert the move number to internal form */
4386 moveNum = (moveNum - 1) * 2;
4387 if (to_play == 'B') moveNum++;
4388 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4389 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4395 case RELATION_OBSERVING_PLAYED:
4396 case RELATION_OBSERVING_STATIC:
4397 if (gamenum == -1) {
4398 /* Old ICC buglet */
4399 relation = RELATION_OBSERVING_STATIC;
4401 newGameMode = IcsObserving;
4403 case RELATION_PLAYING_MYMOVE:
4404 case RELATION_PLAYING_NOTMYMOVE:
4406 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4407 IcsPlayingWhite : IcsPlayingBlack;
4408 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4410 case RELATION_EXAMINING:
4411 newGameMode = IcsExamining;
4413 case RELATION_ISOLATED_BOARD:
4415 /* Just display this board. If user was doing something else,
4416 we will forget about it until the next board comes. */
4417 newGameMode = IcsIdle;
4419 case RELATION_STARTING_POSITION:
4420 newGameMode = gameMode;
4424 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4425 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4426 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4427 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4428 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4429 static int lastBgGame = -1;
4431 for (k = 0; k < ranks; k++) {
4432 for (j = 0; j < files; j++)
4433 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4434 if(gameInfo.holdingsWidth > 1) {
4435 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4436 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4439 CopyBoard(partnerBoard, board);
4440 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4441 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4442 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4443 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4444 if(toSqr = strchr(str, '-')) {
4445 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4446 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4447 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4448 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4449 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4450 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4452 DisplayWhiteClock(white_time*fac, to_play == 'W');
4453 DisplayBlackClock(black_time*fac, to_play != 'W');
4454 activePartner = to_play;
4455 if(gamenum != lastBgGame) {
4457 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4460 lastBgGame = gamenum;
4461 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4462 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4463 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4464 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4465 if(!twoBoards) DisplayMessage(partnerStatus, "");
4466 partnerBoardValid = TRUE;
4470 if(appData.dualBoard && appData.bgObserve) {
4471 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4472 SendToICS(ics_prefix), SendToICS("pobserve\n");
4473 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4475 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4480 /* Modify behavior for initial board display on move listing
4483 switch (ics_getting_history) {
4487 case H_GOT_REQ_HEADER:
4488 case H_GOT_UNREQ_HEADER:
4489 /* This is the initial position of the current game */
4490 gamenum = ics_gamenum;
4491 moveNum = 0; /* old ICS bug workaround */
4492 if (to_play == 'B') {
4493 startedFromSetupPosition = TRUE;
4494 blackPlaysFirst = TRUE;
4496 if (forwardMostMove == 0) forwardMostMove = 1;
4497 if (backwardMostMove == 0) backwardMostMove = 1;
4498 if (currentMove == 0) currentMove = 1;
4500 newGameMode = gameMode;
4501 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4503 case H_GOT_UNWANTED_HEADER:
4504 /* This is an initial board that we don't want */
4506 case H_GETTING_MOVES:
4507 /* Should not happen */
4508 DisplayError(_("Error gathering move list: extra board"), 0);
4509 ics_getting_history = H_FALSE;
4513 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4514 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4515 weird && (int)gameInfo.variant < (int)VariantShogi) {
4516 /* [HGM] We seem to have switched variant unexpectedly
4517 * Try to guess new variant from board size
4519 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4520 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4521 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4522 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4523 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4524 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4525 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4526 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4527 /* Get a move list just to see the header, which
4528 will tell us whether this is really bug or zh */
4529 if (ics_getting_history == H_FALSE) {
4530 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4531 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4536 /* Take action if this is the first board of a new game, or of a
4537 different game than is currently being displayed. */
4538 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4539 relation == RELATION_ISOLATED_BOARD) {
4541 /* Forget the old game and get the history (if any) of the new one */
4542 if (gameMode != BeginningOfGame) {
4546 if (appData.autoRaiseBoard) BoardToTop();
4548 if (gamenum == -1) {
4549 newGameMode = IcsIdle;
4550 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4551 appData.getMoveList && !reqFlag) {
4552 /* Need to get game history */
4553 ics_getting_history = H_REQUESTED;
4554 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4558 /* Initially flip the board to have black on the bottom if playing
4559 black or if the ICS flip flag is set, but let the user change
4560 it with the Flip View button. */
4561 flipView = appData.autoFlipView ?
4562 (newGameMode == IcsPlayingBlack) || ics_flip :
4565 /* Done with values from previous mode; copy in new ones */
4566 gameMode = newGameMode;
4568 ics_gamenum = gamenum;
4569 if (gamenum == gs_gamenum) {
4570 int klen = strlen(gs_kind);
4571 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4572 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4573 gameInfo.event = StrSave(str);
4575 gameInfo.event = StrSave("ICS game");
4577 gameInfo.site = StrSave(appData.icsHost);
4578 gameInfo.date = PGNDate();
4579 gameInfo.round = StrSave("-");
4580 gameInfo.white = StrSave(white);
4581 gameInfo.black = StrSave(black);
4582 timeControl = basetime * 60 * 1000;
4584 timeIncrement = increment * 1000;
4585 movesPerSession = 0;
4586 gameInfo.timeControl = TimeControlTagValue();
4587 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4588 if (appData.debugMode) {
4589 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4590 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4591 setbuf(debugFP, NULL);
4594 gameInfo.outOfBook = NULL;
4596 /* Do we have the ratings? */
4597 if (strcmp(player1Name, white) == 0 &&
4598 strcmp(player2Name, black) == 0) {
4599 if (appData.debugMode)
4600 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4601 player1Rating, player2Rating);
4602 gameInfo.whiteRating = player1Rating;
4603 gameInfo.blackRating = player2Rating;
4604 } else if (strcmp(player2Name, white) == 0 &&
4605 strcmp(player1Name, black) == 0) {
4606 if (appData.debugMode)
4607 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4608 player2Rating, player1Rating);
4609 gameInfo.whiteRating = player2Rating;
4610 gameInfo.blackRating = player1Rating;
4612 player1Name[0] = player2Name[0] = NULLCHAR;
4614 /* Silence shouts if requested */
4615 if (appData.quietPlay &&
4616 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4617 SendToICS(ics_prefix);
4618 SendToICS("set shout 0\n");
4622 /* Deal with midgame name changes */
4624 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4625 if (gameInfo.white) free(gameInfo.white);
4626 gameInfo.white = StrSave(white);
4628 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4629 if (gameInfo.black) free(gameInfo.black);
4630 gameInfo.black = StrSave(black);
4634 /* Throw away game result if anything actually changes in examine mode */
4635 if (gameMode == IcsExamining && !newGame) {
4636 gameInfo.result = GameUnfinished;
4637 if (gameInfo.resultDetails != NULL) {
4638 free(gameInfo.resultDetails);
4639 gameInfo.resultDetails = NULL;
4643 /* In pausing && IcsExamining mode, we ignore boards coming
4644 in if they are in a different variation than we are. */
4645 if (pauseExamInvalid) return;
4646 if (pausing && gameMode == IcsExamining) {
4647 if (moveNum <= pauseExamForwardMostMove) {
4648 pauseExamInvalid = TRUE;
4649 forwardMostMove = pauseExamForwardMostMove;
4654 if (appData.debugMode) {
4655 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4657 /* Parse the board */
4658 for (k = 0; k < ranks; k++) {
4659 for (j = 0; j < files; j++)
4660 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4661 if(gameInfo.holdingsWidth > 1) {
4662 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4663 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4666 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4667 board[5][BOARD_RGHT+1] = WhiteAngel;
4668 board[6][BOARD_RGHT+1] = WhiteMarshall;
4669 board[1][0] = BlackMarshall;
4670 board[2][0] = BlackAngel;
4671 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4673 CopyBoard(boards[moveNum], board);
4674 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4676 startedFromSetupPosition =
4677 !CompareBoards(board, initialPosition);
4678 if(startedFromSetupPosition)
4679 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4682 /* [HGM] Set castling rights. Take the outermost Rooks,
4683 to make it also work for FRC opening positions. Note that board12
4684 is really defective for later FRC positions, as it has no way to
4685 indicate which Rook can castle if they are on the same side of King.
4686 For the initial position we grant rights to the outermost Rooks,
4687 and remember thos rights, and we then copy them on positions
4688 later in an FRC game. This means WB might not recognize castlings with
4689 Rooks that have moved back to their original position as illegal,
4690 but in ICS mode that is not its job anyway.
4692 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4693 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4695 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4696 if(board[0][i] == WhiteRook) j = i;
4697 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4698 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4699 if(board[0][i] == WhiteRook) j = i;
4700 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4701 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4702 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4703 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4704 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4705 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4706 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4708 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4709 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4710 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4711 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4712 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4713 if(board[BOARD_HEIGHT-1][k] == bKing)
4714 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4715 if(gameInfo.variant == VariantTwoKings) {
4716 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4717 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4718 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4721 r = boards[moveNum][CASTLING][0] = initialRights[0];
4722 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4723 r = boards[moveNum][CASTLING][1] = initialRights[1];
4724 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4725 r = boards[moveNum][CASTLING][3] = initialRights[3];
4726 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4727 r = boards[moveNum][CASTLING][4] = initialRights[4];
4728 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4729 /* wildcastle kludge: always assume King has rights */
4730 r = boards[moveNum][CASTLING][2] = initialRights[2];
4731 r = boards[moveNum][CASTLING][5] = initialRights[5];
4733 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4734 boards[moveNum][EP_STATUS] = EP_NONE;
4735 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4736 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4737 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4740 if (ics_getting_history == H_GOT_REQ_HEADER ||
4741 ics_getting_history == H_GOT_UNREQ_HEADER) {
4742 /* This was an initial position from a move list, not
4743 the current position */
4747 /* Update currentMove and known move number limits */
4748 newMove = newGame || moveNum > forwardMostMove;
4751 forwardMostMove = backwardMostMove = currentMove = moveNum;
4752 if (gameMode == IcsExamining && moveNum == 0) {
4753 /* Workaround for ICS limitation: we are not told the wild
4754 type when starting to examine a game. But if we ask for
4755 the move list, the move list header will tell us */
4756 ics_getting_history = H_REQUESTED;
4757 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4760 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4761 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4763 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4764 /* [HGM] applied this also to an engine that is silently watching */
4765 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4766 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4767 gameInfo.variant == currentlyInitializedVariant) {
4768 takeback = forwardMostMove - moveNum;
4769 for (i = 0; i < takeback; i++) {
4770 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4771 SendToProgram("undo\n", &first);
4776 forwardMostMove = moveNum;
4777 if (!pausing || currentMove > forwardMostMove)
4778 currentMove = forwardMostMove;
4780 /* New part of history that is not contiguous with old part */
4781 if (pausing && gameMode == IcsExamining) {
4782 pauseExamInvalid = TRUE;
4783 forwardMostMove = pauseExamForwardMostMove;
4786 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4788 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4789 // [HGM] when we will receive the move list we now request, it will be
4790 // fed to the engine from the first move on. So if the engine is not
4791 // in the initial position now, bring it there.
4792 InitChessProgram(&first, 0);
4795 ics_getting_history = H_REQUESTED;
4796 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4799 forwardMostMove = backwardMostMove = currentMove = moveNum;
4802 /* Update the clocks */
4803 if (strchr(elapsed_time, '.')) {
4805 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4806 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4808 /* Time is in seconds */
4809 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4810 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4815 if (appData.zippyPlay && newGame &&
4816 gameMode != IcsObserving && gameMode != IcsIdle &&
4817 gameMode != IcsExamining)
4818 ZippyFirstBoard(moveNum, basetime, increment);
4821 /* Put the move on the move list, first converting
4822 to canonical algebraic form. */
4824 if (appData.debugMode) {
4825 int f = forwardMostMove;
4826 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4827 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4828 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4829 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4830 fprintf(debugFP, "moveNum = %d\n", moveNum);
4831 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4832 setbuf(debugFP, NULL);
4834 if (moveNum <= backwardMostMove) {
4835 /* We don't know what the board looked like before
4837 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4838 strcat(parseList[moveNum - 1], " ");
4839 strcat(parseList[moveNum - 1], elapsed_time);
4840 moveList[moveNum - 1][0] = NULLCHAR;
4841 } else if (strcmp(move_str, "none") == 0) {
4842 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4843 /* Again, we don't know what the board looked like;
4844 this is really the start of the game. */
4845 parseList[moveNum - 1][0] = NULLCHAR;
4846 moveList[moveNum - 1][0] = NULLCHAR;
4847 backwardMostMove = moveNum;
4848 startedFromSetupPosition = TRUE;
4849 fromX = fromY = toX = toY = -1;
4851 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4852 // So we parse the long-algebraic move string in stead of the SAN move
4853 int valid; char buf[MSG_SIZ], *prom;
4855 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4856 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4857 // str looks something like "Q/a1-a2"; kill the slash
4859 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4860 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4861 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4862 strcat(buf, prom); // long move lacks promo specification!
4863 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4864 if(appData.debugMode)
4865 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4866 safeStrCpy(move_str, buf, MSG_SIZ);
4868 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4869 &fromX, &fromY, &toX, &toY, &promoChar)
4870 || ParseOneMove(buf, moveNum - 1, &moveType,
4871 &fromX, &fromY, &toX, &toY, &promoChar);
4872 // end of long SAN patch
4874 (void) CoordsToAlgebraic(boards[moveNum - 1],
4875 PosFlags(moveNum - 1),
4876 fromY, fromX, toY, toX, promoChar,
4877 parseList[moveNum-1]);
4878 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4884 if(!IS_SHOGI(gameInfo.variant))
4885 strcat(parseList[moveNum - 1], "+");
4888 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4889 strcat(parseList[moveNum - 1], "#");
4892 strcat(parseList[moveNum - 1], " ");
4893 strcat(parseList[moveNum - 1], elapsed_time);
4894 /* currentMoveString is set as a side-effect of ParseOneMove */
4895 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4896 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4897 strcat(moveList[moveNum - 1], "\n");
4899 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4900 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4901 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4902 ChessSquare old, new = boards[moveNum][k][j];
4903 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4904 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4905 if(old == new) continue;
4906 if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4907 else if(new == WhiteWazir || new == BlackWazir) {
4908 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4909 boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4910 else boards[moveNum][k][j] = old; // preserve type of Gold
4911 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4912 boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4915 /* Move from ICS was illegal!? Punt. */
4916 if (appData.debugMode) {
4917 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4918 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4920 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4921 strcat(parseList[moveNum - 1], " ");
4922 strcat(parseList[moveNum - 1], elapsed_time);
4923 moveList[moveNum - 1][0] = NULLCHAR;
4924 fromX = fromY = toX = toY = -1;
4927 if (appData.debugMode) {
4928 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4929 setbuf(debugFP, NULL);
4933 /* Send move to chess program (BEFORE animating it). */
4934 if (appData.zippyPlay && !newGame && newMove &&
4935 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4937 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4938 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4939 if (moveList[moveNum - 1][0] == NULLCHAR) {
4940 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4942 DisplayError(str, 0);
4944 if (first.sendTime) {
4945 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4947 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4948 if (firstMove && !bookHit) {
4950 if (first.useColors) {
4951 SendToProgram(gameMode == IcsPlayingWhite ?
4953 "black\ngo\n", &first);
4955 SendToProgram("go\n", &first);
4957 first.maybeThinking = TRUE;
4960 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4961 if (moveList[moveNum - 1][0] == NULLCHAR) {
4962 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4963 DisplayError(str, 0);
4965 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4966 SendMoveToProgram(moveNum - 1, &first);
4973 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4974 /* If move comes from a remote source, animate it. If it
4975 isn't remote, it will have already been animated. */
4976 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4977 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4979 if (!pausing && appData.highlightLastMove) {
4980 SetHighlights(fromX, fromY, toX, toY);
4984 /* Start the clocks */
4985 whiteFlag = blackFlag = FALSE;
4986 appData.clockMode = !(basetime == 0 && increment == 0);
4988 ics_clock_paused = TRUE;
4990 } else if (ticking == 1) {
4991 ics_clock_paused = FALSE;
4993 if (gameMode == IcsIdle ||
4994 relation == RELATION_OBSERVING_STATIC ||
4995 relation == RELATION_EXAMINING ||
4997 DisplayBothClocks();
5001 /* Display opponents and material strengths */
5002 if (gameInfo.variant != VariantBughouse &&
5003 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5004 if (tinyLayout || smallLayout) {
5005 if(gameInfo.variant == VariantNormal)
5006 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5007 gameInfo.white, white_stren, gameInfo.black, black_stren,
5008 basetime, increment);
5010 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5011 gameInfo.white, white_stren, gameInfo.black, black_stren,
5012 basetime, increment, (int) gameInfo.variant);
5014 if(gameInfo.variant == VariantNormal)
5015 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5016 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5017 basetime, increment);
5019 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5020 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5021 basetime, increment, VariantName(gameInfo.variant));
5024 if (appData.debugMode) {
5025 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5030 /* Display the board */
5031 if (!pausing && !appData.noGUI) {
5033 if (appData.premove)
5035 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5036 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5037 ClearPremoveHighlights();
5039 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5040 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5041 DrawPosition(j, boards[currentMove]);
5043 DisplayMove(moveNum - 1);
5044 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5045 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5046 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
5047 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5051 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5053 if(bookHit) { // [HGM] book: simulate book reply
5054 static char bookMove[MSG_SIZ]; // a bit generous?
5056 programStats.nodes = programStats.depth = programStats.time =
5057 programStats.score = programStats.got_only_move = 0;
5058 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5060 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5061 strcat(bookMove, bookHit);
5062 HandleMachineMove(bookMove, &first);
5071 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5072 ics_getting_history = H_REQUESTED;
5073 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5079 SendToBoth (char *msg)
5080 { // to make it easy to keep two engines in step in dual analysis
5081 SendToProgram(msg, &first);
5082 if(second.analyzing) SendToProgram(msg, &second);
5086 AnalysisPeriodicEvent (int force)
5088 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5089 && !force) || !appData.periodicUpdates)
5092 /* Send . command to Crafty to collect stats */
5095 /* Don't send another until we get a response (this makes
5096 us stop sending to old Crafty's which don't understand
5097 the "." command (sending illegal cmds resets node count & time,
5098 which looks bad)) */
5099 programStats.ok_to_send = 0;
5103 ics_update_width (int new_width)
5105 ics_printf("set width %d\n", new_width);
5109 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5113 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5114 if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5115 sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5116 SendToProgram(buf, cps);
5119 // null move in variant where engine does not understand it (for analysis purposes)
5120 SendBoard(cps, moveNum + 1); // send position after move in stead.
5123 if (cps->useUsermove) {
5124 SendToProgram("usermove ", cps);
5128 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5129 int len = space - parseList[moveNum];
5130 memcpy(buf, parseList[moveNum], len);
5132 buf[len] = NULLCHAR;
5134 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5136 SendToProgram(buf, cps);
5138 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5139 AlphaRank(moveList[moveNum], 4);
5140 SendToProgram(moveList[moveNum], cps);
5141 AlphaRank(moveList[moveNum], 4); // and back
5143 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5144 * the engine. It would be nice to have a better way to identify castle
5146 if(appData.fischerCastling && cps->useOOCastle) {
5147 int fromX = moveList[moveNum][0] - AAA;
5148 int fromY = moveList[moveNum][1] - ONE;
5149 int toX = moveList[moveNum][2] - AAA;
5150 int toY = moveList[moveNum][3] - ONE;
5151 if((boards[moveNum][fromY][fromX] == WhiteKing
5152 && boards[moveNum][toY][toX] == WhiteRook)
5153 || (boards[moveNum][fromY][fromX] == BlackKing
5154 && boards[moveNum][toY][toX] == BlackRook)) {
5155 if(toX > fromX) SendToProgram("O-O\n", cps);
5156 else SendToProgram("O-O-O\n", cps);
5158 else SendToProgram(moveList[moveNum], cps);
5160 if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5161 char *m = moveList[moveNum];
5163 *c = m[7]; // promoChar
5164 if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5165 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5168 m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5169 else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5171 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
5176 m[2], m[3] - '0', c);
5178 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5181 m[2], m[3] - '0', c);
5182 SendToProgram(buf, cps);
5184 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5185 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5186 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5187 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5188 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5190 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5191 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5192 SendToProgram(buf, cps);
5194 else SendToProgram(moveList[moveNum], cps);
5195 /* End of additions by Tord */
5198 /* [HGM] setting up the opening has brought engine in force mode! */
5199 /* Send 'go' if we are in a mode where machine should play. */
5200 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5201 (gameMode == TwoMachinesPlay ||
5203 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5205 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5206 SendToProgram("go\n", cps);
5207 if (appData.debugMode) {
5208 fprintf(debugFP, "(extra)\n");
5211 setboardSpoiledMachineBlack = 0;
5215 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5217 char user_move[MSG_SIZ];
5220 if(gameInfo.variant == VariantSChess && promoChar) {
5221 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5222 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5223 } else suffix[0] = NULLCHAR;
5227 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5228 (int)moveType, fromX, fromY, toX, toY);
5229 DisplayError(user_move + strlen("say "), 0);
5231 case WhiteKingSideCastle:
5232 case BlackKingSideCastle:
5233 case WhiteQueenSideCastleWild:
5234 case BlackQueenSideCastleWild:
5236 case WhiteHSideCastleFR:
5237 case BlackHSideCastleFR:
5239 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5241 case WhiteQueenSideCastle:
5242 case BlackQueenSideCastle:
5243 case WhiteKingSideCastleWild:
5244 case BlackKingSideCastleWild:
5246 case WhiteASideCastleFR:
5247 case BlackASideCastleFR:
5249 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5251 case WhiteNonPromotion:
5252 case BlackNonPromotion:
5253 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5255 case WhitePromotion:
5256 case BlackPromotion:
5257 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5258 gameInfo.variant == VariantMakruk)
5259 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5260 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5261 PieceToChar(WhiteFerz));
5262 else if(gameInfo.variant == VariantGreat)
5263 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5264 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5265 PieceToChar(WhiteMan));
5267 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5268 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5274 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5275 ToUpper(PieceToChar((ChessSquare) fromX)),
5276 AAA + toX, ONE + toY);
5278 case IllegalMove: /* could be a variant we don't quite understand */
5279 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5281 case WhiteCapturesEnPassant:
5282 case BlackCapturesEnPassant:
5283 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5284 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5287 SendToICS(user_move);
5288 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5289 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5294 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5295 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5296 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5297 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5298 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5301 if(gameMode != IcsExamining) { // is this ever not the case?
5302 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5304 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5305 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5306 } else { // on FICS we must first go to general examine mode
5307 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5309 if(gameInfo.variant != VariantNormal) {
5310 // try figure out wild number, as xboard names are not always valid on ICS
5311 for(i=1; i<=36; i++) {
5312 snprintf(buf, MSG_SIZ, "wild/%d", i);
5313 if(StringToVariant(buf) == gameInfo.variant) break;
5315 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5316 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5317 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5318 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5319 SendToICS(ics_prefix);
5321 if(startedFromSetupPosition || backwardMostMove != 0) {
5322 fen = PositionToFEN(backwardMostMove, NULL, 1);
5323 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5324 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5326 } else { // FICS: everything has to set by separate bsetup commands
5327 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5328 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5330 if(!WhiteOnMove(backwardMostMove)) {
5331 SendToICS("bsetup tomove black\n");
5333 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5334 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5336 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5337 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5339 i = boards[backwardMostMove][EP_STATUS];
5340 if(i >= 0) { // set e.p.
5341 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5347 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5348 SendToICS("bsetup done\n"); // switch to normal examining.
5350 for(i = backwardMostMove; i<last; i++) {
5352 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5353 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5354 int len = strlen(moveList[i]);
5355 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5356 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5360 SendToICS(ics_prefix);
5361 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5364 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5368 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5370 if (rf == DROP_RANK) {
5371 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5372 sprintf(move, "%c@%c%c\n",
5373 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5375 if (promoChar == 'x' || promoChar == NULLCHAR) {
5376 sprintf(move, "%c%c%c%c\n",
5377 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5378 if(killX >= 0 && killY >= 0) {
5379 sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5380 if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5383 sprintf(move, "%c%c%c%c%c\n",
5384 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5385 if(killX >= 0 && killY >= 0) {
5386 sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5387 if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5394 ProcessICSInitScript (FILE *f)
5398 while (fgets(buf, MSG_SIZ, f)) {
5399 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5406 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5408 static ClickType lastClickType;
5411 PieceInString (char *s, ChessSquare piece)
5413 char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5414 while((p = strchr(s, ID))) {
5415 if(!suffix || p[1] == suffix) return TRUE;
5422 Partner (ChessSquare *p)
5423 { // change piece into promotion partner if one shogi-promotes to the other
5424 ChessSquare partner = promoPartner[*p];
5425 if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5426 if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5434 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5435 static int toggleFlag;
5436 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5437 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5438 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5439 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5440 if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5441 if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5443 if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5444 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5445 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5446 else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5447 else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5448 if(!step) step = -1;
5449 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5450 !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5451 promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it
5452 promoSweep == pawn ||
5453 appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5454 (promoSweep == WhiteLion || promoSweep == BlackLion)));
5456 int victim = boards[currentMove][toY][toX];
5457 boards[currentMove][toY][toX] = promoSweep;
5458 DrawPosition(FALSE, boards[currentMove]);
5459 boards[currentMove][toY][toX] = victim;
5461 ChangeDragPiece(promoSweep);
5465 PromoScroll (int x, int y)
5469 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5470 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5471 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5472 if(!step) return FALSE;
5473 lastX = x; lastY = y;
5474 if((promoSweep < BlackPawn) == flipView) step = -step;
5475 if(step > 0) selectFlag = 1;
5476 if(!selectFlag) Sweep(step);
5481 NextPiece (int step)
5483 ChessSquare piece = boards[currentMove][toY][toX];
5486 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5487 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5488 if(!step) step = -1;
5489 } while(PieceToChar(pieceSweep) == '.');
5490 boards[currentMove][toY][toX] = pieceSweep;
5491 DrawPosition(FALSE, boards[currentMove]);
5492 boards[currentMove][toY][toX] = piece;
5494 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5496 AlphaRank (char *move, int n)
5498 // char *p = move, c; int x, y;
5500 if (appData.debugMode) {
5501 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5505 move[2]>='0' && move[2]<='9' &&
5506 move[3]>='a' && move[3]<='x' ) {
5508 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5509 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5511 if(move[0]>='0' && move[0]<='9' &&
5512 move[1]>='a' && move[1]<='x' &&
5513 move[2]>='0' && move[2]<='9' &&
5514 move[3]>='a' && move[3]<='x' ) {
5515 /* input move, Shogi -> normal */
5516 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5517 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5518 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5519 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5522 move[3]>='0' && move[3]<='9' &&
5523 move[2]>='a' && move[2]<='x' ) {
5525 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5526 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5529 move[0]>='a' && move[0]<='x' &&
5530 move[3]>='0' && move[3]<='9' &&
5531 move[2]>='a' && move[2]<='x' ) {
5532 /* output move, normal -> Shogi */
5533 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5534 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5535 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5536 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5537 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5539 if (appData.debugMode) {
5540 fprintf(debugFP, " out = '%s'\n", move);
5544 char yy_textstr[8000];
5546 /* Parser for moves from gnuchess, ICS, or user typein box */
5548 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5550 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5552 switch (*moveType) {
5553 case WhitePromotion:
5554 case BlackPromotion:
5555 case WhiteNonPromotion:
5556 case BlackNonPromotion:
5559 case WhiteCapturesEnPassant:
5560 case BlackCapturesEnPassant:
5561 case WhiteKingSideCastle:
5562 case WhiteQueenSideCastle:
5563 case BlackKingSideCastle:
5564 case BlackQueenSideCastle:
5565 case WhiteKingSideCastleWild:
5566 case WhiteQueenSideCastleWild:
5567 case BlackKingSideCastleWild:
5568 case BlackQueenSideCastleWild:
5569 /* Code added by Tord: */
5570 case WhiteHSideCastleFR:
5571 case WhiteASideCastleFR:
5572 case BlackHSideCastleFR:
5573 case BlackASideCastleFR:
5574 /* End of code added by Tord */
5575 case IllegalMove: /* bug or odd chess variant */
5576 if(currentMoveString[1] == '@') { // illegal drop
5577 *fromX = WhiteOnMove(moveNum) ?
5578 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5579 (int) CharToPiece(ToLower(currentMoveString[0]));
5582 *fromX = currentMoveString[0] - AAA;
5583 *fromY = currentMoveString[1] - ONE;
5584 *toX = currentMoveString[2] - AAA;
5585 *toY = currentMoveString[3] - ONE;
5586 *promoChar = currentMoveString[4];
5587 if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5588 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5589 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5590 if (appData.debugMode) {
5591 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5593 *fromX = *fromY = *toX = *toY = 0;
5596 if (appData.testLegality) {
5597 return (*moveType != IllegalMove);
5599 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5600 // [HGM] lion: if this is a double move we are less critical
5601 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5606 *fromX = *moveType == WhiteDrop ?
5607 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5608 (int) CharToPiece(ToLower(currentMoveString[0]));
5611 *toX = currentMoveString[2] - AAA;
5612 *toY = currentMoveString[3] - ONE;
5613 *promoChar = NULLCHAR;
5617 case ImpossibleMove:
5627 if (appData.debugMode) {
5628 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5631 *fromX = *fromY = *toX = *toY = 0;
5632 *promoChar = NULLCHAR;
5637 Boolean pushed = FALSE;
5638 char *lastParseAttempt;
5641 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5642 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5643 int fromX, fromY, toX, toY; char promoChar;
5648 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5649 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5650 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5653 endPV = forwardMostMove;
5655 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5656 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5657 lastParseAttempt = pv;
5658 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5659 if(!valid && nr == 0 &&
5660 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5661 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5662 // Hande case where played move is different from leading PV move
5663 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5664 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5665 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5666 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5667 endPV += 2; // if position different, keep this
5668 moveList[endPV-1][0] = fromX + AAA;
5669 moveList[endPV-1][1] = fromY + ONE;
5670 moveList[endPV-1][2] = toX + AAA;
5671 moveList[endPV-1][3] = toY + ONE;
5672 parseList[endPV-1][0] = NULLCHAR;
5673 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5676 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5677 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5678 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5679 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5680 valid++; // allow comments in PV
5684 if(endPV+1 > framePtr) break; // no space, truncate
5687 CopyBoard(boards[endPV], boards[endPV-1]);
5688 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5689 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5690 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5691 CoordsToAlgebraic(boards[endPV - 1],
5692 PosFlags(endPV - 1),
5693 fromY, fromX, toY, toX, promoChar,
5694 parseList[endPV - 1]);
5696 if(atEnd == 2) return; // used hidden, for PV conversion
5697 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5698 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5699 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5700 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5701 DrawPosition(TRUE, boards[currentMove]);
5705 MultiPV (ChessProgramState *cps, int kind)
5706 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5708 for(i=0; i<cps->nrOptions; i++) {
5709 char *s = cps->option[i].name;
5710 if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5711 if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5712 && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5717 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5718 static int multi, pv_margin;
5719 static ChessProgramState *activeCps;
5722 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5724 int startPV, lineStart, origIndex = index;
5725 char *p, buf2[MSG_SIZ];
5726 ChessProgramState *cps = (pane ? &second : &first);
5728 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5729 lastX = x; lastY = y;
5730 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5731 lineStart = startPV = index;
5732 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5733 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5735 do{ while(buf[index] && buf[index] != '\n') index++;
5736 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5738 if(lineStart == 0 && gameMode == AnalyzeMode) {
5740 if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5741 if(n == 0) { // click not on "fewer" or "more"
5742 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5743 pv_margin = cps->option[multi].value;
5744 activeCps = cps; // non-null signals margin adjustment
5746 } else if((multi = MultiPV(cps, 1)) >= 0) {
5747 n += cps->option[multi].value; if(n < 1) n = 1;
5748 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5749 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5750 cps->option[multi].value = n;
5754 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5755 ExcludeClick(origIndex - lineStart);
5757 } else if(!strncmp(buf+lineStart, "dep\t", 4)) { // column headers clicked
5758 Collapse(origIndex - lineStart);
5761 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5762 *start = startPV; *end = index-1;
5763 extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5770 static char buf[10*MSG_SIZ];
5771 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5773 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5774 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5775 for(i = forwardMostMove; i<endPV; i++){
5776 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5777 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5780 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5781 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5782 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5788 LoadPV (int x, int y)
5789 { // called on right mouse click to load PV
5790 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5791 lastX = x; lastY = y;
5792 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5800 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5802 if(pv_margin != activeCps->option[multi].value) {
5804 snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5805 SendToProgram(buf, activeCps);
5806 activeCps->option[multi].value = pv_margin;
5811 if(endPV < 0) return;
5812 if(appData.autoCopyPV) CopyFENToClipboard();
5814 if(extendGame && currentMove > forwardMostMove) {
5815 Boolean saveAnimate = appData.animate;
5817 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5818 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5819 } else storedGames--; // abandon shelved tail of original game
5822 forwardMostMove = currentMove;
5823 currentMove = oldFMM;
5824 appData.animate = FALSE;
5825 ToNrEvent(forwardMostMove);
5826 appData.animate = saveAnimate;
5828 currentMove = forwardMostMove;
5829 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5830 ClearPremoveHighlights();
5831 DrawPosition(TRUE, boards[currentMove]);
5835 MovePV (int x, int y, int h)
5836 { // step through PV based on mouse coordinates (called on mouse move)
5837 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5839 if(activeCps) { // adjusting engine's multi-pv margin
5840 if(x > lastX) pv_margin++; else
5841 if(x < lastX) pv_margin -= (pv_margin > 0);
5844 snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5845 DisplayMessage(buf, "");
5850 // we must somehow check if right button is still down (might be released off board!)
5851 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5852 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5853 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5855 lastX = x; lastY = y;
5857 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5858 if(endPV < 0) return;
5859 if(y < margin) step = 1; else
5860 if(y > h - margin) step = -1;
5861 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5862 currentMove += step;
5863 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5864 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5865 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5866 DrawPosition(FALSE, boards[currentMove]);
5870 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5871 // All positions will have equal probability, but the current method will not provide a unique
5872 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5878 int piecesLeft[(int)BlackPawn];
5879 int seed, nrOfShuffles;
5882 GetPositionNumber ()
5883 { // sets global variable seed
5886 seed = appData.defaultFrcPosition;
5887 if(seed < 0) { // randomize based on time for negative FRC position numbers
5888 for(i=0; i<50; i++) seed += random();
5889 seed = random() ^ random() >> 8 ^ random() << 8;
5890 if(seed<0) seed = -seed;
5895 put (Board board, int pieceType, int rank, int n, int shade)
5896 // put the piece on the (n-1)-th empty squares of the given shade
5900 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5901 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5902 board[rank][i] = (ChessSquare) pieceType;
5903 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5905 piecesLeft[pieceType]--;
5914 AddOnePiece (Board board, int pieceType, int rank, int shade)
5915 // calculate where the next piece goes, (any empty square), and put it there
5919 i = seed % squaresLeft[shade];
5920 nrOfShuffles *= squaresLeft[shade];
5921 seed /= squaresLeft[shade];
5922 put(board, pieceType, rank, i, shade);
5926 AddTwoPieces (Board board, int pieceType, int rank)
5927 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5929 int i, n=squaresLeft[ANY], j=n-1, k;
5931 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5932 i = seed % k; // pick one
5935 while(i >= j) i -= j--;
5936 j = n - 1 - j; i += j;
5937 put(board, pieceType, rank, j, ANY);
5938 put(board, pieceType, rank, i, ANY);
5942 SetUpShuffle (Board board, int number)
5946 GetPositionNumber(); nrOfShuffles = 1;
5948 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5949 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5950 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5952 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5954 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5955 p = (int) board[0][i];
5956 if(p < (int) BlackPawn) piecesLeft[p] ++;
5957 board[0][i] = EmptySquare;
5960 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5961 // shuffles restricted to allow normal castling put KRR first
5962 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5963 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5964 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5965 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5966 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5967 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5968 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5969 put(board, WhiteRook, 0, 0, ANY);
5970 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5973 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5974 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5975 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5976 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5977 while(piecesLeft[p] >= 2) {
5978 AddOnePiece(board, p, 0, LITE);
5979 AddOnePiece(board, p, 0, DARK);
5981 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5984 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5985 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5986 // but we leave King and Rooks for last, to possibly obey FRC restriction
5987 if(p == (int)WhiteRook) continue;
5988 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5989 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5992 // now everything is placed, except perhaps King (Unicorn) and Rooks
5994 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5995 // Last King gets castling rights
5996 while(piecesLeft[(int)WhiteUnicorn]) {
5997 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5998 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
6001 while(piecesLeft[(int)WhiteKing]) {
6002 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6003 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
6008 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
6009 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6012 // Only Rooks can be left; simply place them all
6013 while(piecesLeft[(int)WhiteRook]) {
6014 i = put(board, WhiteRook, 0, 0, ANY);
6015 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6018 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
6020 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
6023 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6024 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6027 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6031 ptclen (const char *s, char *escapes)
6034 if(!*escapes) return strlen(s);
6035 while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6040 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6041 /* [HGM] moved here from winboard.c because of its general usefulness */
6042 /* Basically a safe strcpy that uses the last character as King */
6044 int result = FALSE; int NrPieces;
6045 unsigned char partner[EmptySquare];
6047 if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6048 && NrPieces >= 12 && !(NrPieces&1)) {
6049 int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6051 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6052 for( i=offs=0; i<NrPieces/2-1; i++ ) {
6054 if(map[j] == '/') offs = WhitePBishop - i, j++;
6055 if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6056 table[i+offs] = map[j++];
6057 if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6058 if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6059 if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6061 table[(int) WhiteKing] = map[j++];
6062 for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6064 if(map[j] == '/') offs = WhitePBishop - ii, j++;
6065 i = WHITE_TO_BLACK ii;
6066 if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6067 table[i+offs] = map[j++];
6068 if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6069 if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6070 if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6072 table[(int) BlackKing] = map[j++];
6075 if(*escapes) { // set up promotion pairing
6076 for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6077 // pieceToChar entirely filled, so we can look up specified partners
6078 for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6080 if(c == '^' || c == '-') { // has specified partner
6082 for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6083 if(c == '^') table[i] = '+';
6084 if(p < EmptySquare) {
6085 if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6086 if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6087 promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6089 } else if(c == '*') {
6090 table[i] = partner[i];
6091 promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6103 SetCharTable (unsigned char *table, const char * map)
6105 return SetCharTableEsc(table, map, "");
6109 Prelude (Board board)
6110 { // [HGM] superchess: random selection of exo-pieces
6111 int i, j, k; ChessSquare p;
6112 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6114 GetPositionNumber(); // use FRC position number
6116 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6117 SetCharTable(pieceToChar, appData.pieceToCharTable);
6118 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6119 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6122 j = seed%4; seed /= 4;
6123 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6124 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6125 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6126 j = seed%3 + (seed%3 >= j); seed /= 3;
6127 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6128 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6129 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6130 j = seed%3; seed /= 3;
6131 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6132 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6133 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6134 j = seed%2 + (seed%2 >= j); seed /= 2;
6135 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6136 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6137 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6138 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
6139 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
6140 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6141 put(board, exoPieces[0], 0, 0, ANY);
6142 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6146 InitPosition (int redraw)
6148 ChessSquare (* pieces)[BOARD_FILES];
6149 int i, j, pawnRow=1, pieceRows=1, overrule,
6150 oldx = gameInfo.boardWidth,
6151 oldy = gameInfo.boardHeight,
6152 oldh = gameInfo.holdingsWidth;
6155 if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6157 /* [AS] Initialize pv info list [HGM] and game status */
6159 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6160 pvInfoList[i].depth = 0;
6161 boards[i][EP_STATUS] = EP_NONE;
6162 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6165 initialRulePlies = 0; /* 50-move counter start */
6167 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6168 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6172 /* [HGM] logic here is completely changed. In stead of full positions */
6173 /* the initialized data only consist of the two backranks. The switch */
6174 /* selects which one we will use, which is than copied to the Board */
6175 /* initialPosition, which for the rest is initialized by Pawns and */
6176 /* empty squares. This initial position is then copied to boards[0], */
6177 /* possibly after shuffling, so that it remains available. */
6179 gameInfo.holdingsWidth = 0; /* default board sizes */
6180 gameInfo.boardWidth = 8;
6181 gameInfo.boardHeight = 8;
6182 gameInfo.holdingsSize = 0;
6183 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6184 for(i=0; i<BOARD_FILES-6; i++)
6185 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6186 initialPosition[EP_STATUS] = EP_NONE;
6187 initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6188 SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6189 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6190 SetCharTable(pieceNickName, appData.pieceNickNames);
6191 else SetCharTable(pieceNickName, "............");
6194 switch (gameInfo.variant) {
6195 case VariantFischeRandom:
6196 shuffleOpenings = TRUE;
6197 appData.fischerCastling = TRUE;
6200 case VariantShatranj:
6201 pieces = ShatranjArray;
6202 nrCastlingRights = 0;
6203 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6206 pieces = makrukArray;
6207 nrCastlingRights = 0;
6208 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6211 pieces = aseanArray;
6212 nrCastlingRights = 0;
6213 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6215 case VariantTwoKings:
6216 pieces = twoKingsArray;
6219 pieces = GrandArray;
6220 nrCastlingRights = 0;
6221 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6222 gameInfo.boardWidth = 10;
6223 gameInfo.boardHeight = 10;
6224 gameInfo.holdingsSize = 7;
6226 case VariantCapaRandom:
6227 shuffleOpenings = TRUE;
6228 appData.fischerCastling = TRUE;
6229 case VariantCapablanca:
6230 pieces = CapablancaArray;
6231 gameInfo.boardWidth = 10;
6232 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6235 pieces = GothicArray;
6236 gameInfo.boardWidth = 10;
6237 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6240 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6241 gameInfo.holdingsSize = 7;
6242 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6245 pieces = JanusArray;
6246 gameInfo.boardWidth = 10;
6247 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6248 nrCastlingRights = 6;
6249 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6250 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6251 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6252 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6253 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6254 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6257 pieces = FalconArray;
6258 gameInfo.boardWidth = 10;
6259 SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6261 case VariantXiangqi:
6262 pieces = XiangqiArray;
6263 gameInfo.boardWidth = 9;
6264 gameInfo.boardHeight = 10;
6265 nrCastlingRights = 0;
6266 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6269 pieces = ShogiArray;
6270 gameInfo.boardWidth = 9;
6271 gameInfo.boardHeight = 9;
6272 gameInfo.holdingsSize = 7;
6273 nrCastlingRights = 0;
6274 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6277 pieces = ChuArray; pieceRows = 3;
6278 gameInfo.boardWidth = 12;
6279 gameInfo.boardHeight = 12;
6280 nrCastlingRights = 0;
6281 SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6282 "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6284 case VariantCourier:
6285 pieces = CourierArray;
6286 gameInfo.boardWidth = 12;
6287 nrCastlingRights = 0;
6288 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6290 case VariantKnightmate:
6291 pieces = KnightmateArray;
6292 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6294 case VariantSpartan:
6295 pieces = SpartanArray;
6296 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6300 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6302 case VariantChuChess:
6303 pieces = ChuChessArray;
6304 gameInfo.boardWidth = 10;
6305 gameInfo.boardHeight = 10;
6306 SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6309 pieces = fairyArray;
6310 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6313 pieces = GreatArray;
6314 gameInfo.boardWidth = 10;
6315 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6316 gameInfo.holdingsSize = 8;
6320 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6321 gameInfo.holdingsSize = 8;
6322 startedFromSetupPosition = TRUE;
6324 case VariantCrazyhouse:
6325 case VariantBughouse:
6327 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6328 gameInfo.holdingsSize = 5;
6330 case VariantWildCastle:
6332 /* !!?shuffle with kings guaranteed to be on d or e file */
6333 shuffleOpenings = 1;
6335 case VariantNoCastle:
6337 nrCastlingRights = 0;
6338 /* !!?unconstrained back-rank shuffle */
6339 shuffleOpenings = 1;
6344 if(appData.NrFiles >= 0) {
6345 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6346 gameInfo.boardWidth = appData.NrFiles;
6348 if(appData.NrRanks >= 0) {
6349 gameInfo.boardHeight = appData.NrRanks;
6351 if(appData.holdingsSize >= 0) {
6352 i = appData.holdingsSize;
6353 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6354 gameInfo.holdingsSize = i;
6356 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6357 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6358 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6360 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6361 if(pawnRow < 1) pawnRow = 1;
6362 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6363 gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6364 if(gameInfo.variant == VariantChu) pawnRow = 3;
6366 /* User pieceToChar list overrules defaults */
6367 if(appData.pieceToCharTable != NULL)
6368 SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6370 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6372 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6373 s = (ChessSquare) 0; /* account holding counts in guard band */
6374 for( i=0; i<BOARD_HEIGHT; i++ )
6375 initialPosition[i][j] = s;
6377 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6378 initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6379 initialPosition[pawnRow][j] = WhitePawn;
6380 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6381 if(gameInfo.variant == VariantXiangqi) {
6383 initialPosition[pawnRow][j] =
6384 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6385 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6386 initialPosition[2][j] = WhiteCannon;
6387 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6391 if(gameInfo.variant == VariantChu) {
6392 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6393 initialPosition[pawnRow+1][j] = WhiteCobra,
6394 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6395 for(i=1; i<pieceRows; i++) {
6396 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6397 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6400 if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6401 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6402 initialPosition[0][j] = WhiteRook;
6403 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6406 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth];
6408 if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6409 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6412 initialPosition[1][j] = WhiteBishop;
6413 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6415 initialPosition[1][j] = WhiteRook;
6416 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6419 if( nrCastlingRights == -1) {
6420 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6421 /* This sets default castling rights from none to normal corners */
6422 /* Variants with other castling rights must set them themselves above */
6423 nrCastlingRights = 6;
6425 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6426 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6427 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6428 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6429 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6430 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6433 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6434 if(gameInfo.variant == VariantGreat) { // promotion commoners
6435 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6436 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6437 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6438 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6440 if( gameInfo.variant == VariantSChess ) {
6441 initialPosition[1][0] = BlackMarshall;
6442 initialPosition[2][0] = BlackAngel;
6443 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6444 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6445 initialPosition[1][1] = initialPosition[2][1] =
6446 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6448 if (appData.debugMode) {
6449 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6451 if(shuffleOpenings) {
6452 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6453 startedFromSetupPosition = TRUE;
6455 if(startedFromPositionFile) {
6456 /* [HGM] loadPos: use PositionFile for every new game */
6457 CopyBoard(initialPosition, filePosition);
6458 for(i=0; i<nrCastlingRights; i++)
6459 initialRights[i] = filePosition[CASTLING][i];
6460 startedFromSetupPosition = TRUE;
6462 if(*appData.men) LoadPieceDesc(appData.men);
6464 CopyBoard(boards[0], initialPosition);
6466 if(oldx != gameInfo.boardWidth ||
6467 oldy != gameInfo.boardHeight ||
6468 oldv != gameInfo.variant ||
6469 oldh != gameInfo.holdingsWidth
6471 InitDrawingSizes(-2 ,0);
6473 oldv = gameInfo.variant;
6475 DrawPosition(TRUE, boards[currentMove]);
6479 SendBoard (ChessProgramState *cps, int moveNum)
6481 char message[MSG_SIZ];
6483 if (cps->useSetboard) {
6484 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6485 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6486 SendToProgram(message, cps);
6491 int i, j, left=0, right=BOARD_WIDTH;
6492 /* Kludge to set black to move, avoiding the troublesome and now
6493 * deprecated "black" command.
6495 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6496 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6498 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6500 SendToProgram("edit\n", cps);
6501 SendToProgram("#\n", cps);
6502 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6503 bp = &boards[moveNum][i][left];
6504 for (j = left; j < right; j++, bp++) {
6505 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6506 if ((int) *bp < (int) BlackPawn) {
6507 if(j == BOARD_RGHT+1)
6508 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6509 else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6510 if(message[0] == '+' || message[0] == '~') {
6511 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6512 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6513 AAA + j, ONE + i - '0');
6515 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6516 message[1] = BOARD_RGHT - 1 - j + '1';
6517 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6519 SendToProgram(message, cps);
6524 SendToProgram("c\n", cps);
6525 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6526 bp = &boards[moveNum][i][left];
6527 for (j = left; j < right; j++, bp++) {
6528 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6529 if (((int) *bp != (int) EmptySquare)
6530 && ((int) *bp >= (int) BlackPawn)) {
6531 if(j == BOARD_LEFT-2)
6532 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6533 else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6534 AAA + j, ONE + i - '0');
6535 if(message[0] == '+' || message[0] == '~') {
6536 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6537 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6538 AAA + j, ONE + i - '0');
6540 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6541 message[1] = BOARD_RGHT - 1 - j + '1';
6542 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6544 SendToProgram(message, cps);
6549 SendToProgram(".\n", cps);
6551 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6554 char exclusionHeader[MSG_SIZ];
6555 int exCnt, excludePtr;
6556 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6557 static Exclusion excluTab[200];
6558 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6564 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6565 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6571 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6572 excludePtr = 24; exCnt = 0;
6577 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6578 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6579 char buf[2*MOVE_LEN], *p;
6580 Exclusion *e = excluTab;
6582 for(i=0; i<exCnt; i++)
6583 if(e[i].ff == fromX && e[i].fr == fromY &&
6584 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6585 if(i == exCnt) { // was not in exclude list; add it
6586 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6587 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6588 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6591 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6592 excludePtr++; e[i].mark = excludePtr++;
6593 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6596 exclusionHeader[e[i].mark] = state;
6600 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6601 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6605 if((signed char)promoChar == -1) { // kludge to indicate best move
6606 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6607 return 1; // if unparsable, abort
6609 // update exclusion map (resolving toggle by consulting existing state)
6610 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6612 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6613 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6614 excludeMap[k] |= 1<<j;
6615 else excludeMap[k] &= ~(1<<j);
6617 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6619 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6620 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6622 return (state == '+');
6626 ExcludeClick (int index)
6629 Exclusion *e = excluTab;
6630 if(index < 25) { // none, best or tail clicked
6631 if(index < 13) { // none: include all
6632 WriteMap(0); // clear map
6633 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6634 SendToBoth("include all\n"); // and inform engine
6635 } else if(index > 18) { // tail
6636 if(exclusionHeader[19] == '-') { // tail was excluded
6637 SendToBoth("include all\n");
6638 WriteMap(0); // clear map completely
6639 // now re-exclude selected moves
6640 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6641 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6642 } else { // tail was included or in mixed state
6643 SendToBoth("exclude all\n");
6644 WriteMap(0xFF); // fill map completely
6645 // now re-include selected moves
6646 j = 0; // count them
6647 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6648 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6649 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6652 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6655 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6656 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6657 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6664 DefaultPromoChoice (int white)
6667 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6668 gameInfo.variant == VariantMakruk)
6669 result = WhiteFerz; // no choice
6670 else if(gameInfo.variant == VariantASEAN)
6671 result = WhiteRook; // no choice
6672 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6673 result= WhiteKing; // in Suicide Q is the last thing we want
6674 else if(gameInfo.variant == VariantSpartan)
6675 result = white ? WhiteQueen : WhiteAngel;
6676 else result = WhiteQueen;
6677 if(!white) result = WHITE_TO_BLACK result;
6681 static int autoQueen; // [HGM] oneclick
6684 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6686 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6687 /* [HGM] add Shogi promotions */
6688 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6689 ChessSquare piece, partner;
6693 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6694 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6696 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6697 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6700 piece = boards[currentMove][fromY][fromX];
6701 if(gameInfo.variant == VariantChu) {
6702 promotionZoneSize = BOARD_HEIGHT/3;
6703 highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6704 } else if(gameInfo.variant == VariantShogi) {
6705 promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6706 highestPromotingPiece = (int)WhiteAlfil;
6707 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6708 promotionZoneSize = 3;
6711 // Treat Lance as Pawn when it is not representing Amazon or Lance
6712 if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6713 if(piece == WhiteLance) piece = WhitePawn; else
6714 if(piece == BlackLance) piece = BlackPawn;
6717 // next weed out all moves that do not touch the promotion zone at all
6718 if((int)piece >= BlackPawn) {
6719 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6721 if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6722 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6724 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6725 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6726 if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6730 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6732 // weed out mandatory Shogi promotions
6733 if(gameInfo.variant == VariantShogi) {
6734 if(piece >= BlackPawn) {
6735 if(toY == 0 && piece == BlackPawn ||
6736 toY == 0 && piece == BlackQueen ||
6737 toY <= 1 && piece == BlackKnight) {
6742 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6743 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6744 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6751 // weed out obviously illegal Pawn moves
6752 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6753 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6754 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6755 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6756 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6757 // note we are not allowed to test for valid (non-)capture, due to premove
6760 // we either have a choice what to promote to, or (in Shogi) whether to promote
6761 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6762 gameInfo.variant == VariantMakruk) {
6763 ChessSquare p=BlackFerz; // no choice
6764 while(p < EmptySquare) { //but make sure we use piece that exists
6765 *promoChoice = PieceToChar(p++);
6766 if(*promoChoice != '.') break;
6768 if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6770 // no sense asking what we must promote to if it is going to explode...
6771 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6772 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6775 // give caller the default choice even if we will not make it
6776 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6777 partner = piece; // pieces can promote if the pieceToCharTable says so
6778 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6779 else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6780 if( sweepSelect && gameInfo.variant != VariantGreat
6781 && gameInfo.variant != VariantGrand
6782 && gameInfo.variant != VariantSuper) return FALSE;
6783 if(autoQueen) return FALSE; // predetermined
6785 // suppress promotion popup on illegal moves that are not premoves
6786 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6787 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6788 if(appData.testLegality && !premove) {
6789 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6790 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6791 if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6792 if(moveType != WhitePromotion && moveType != BlackPromotion)
6800 InPalace (int row, int column)
6801 { /* [HGM] for Xiangqi */
6802 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6803 column < (BOARD_WIDTH + 4)/2 &&
6804 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6809 PieceForSquare (int x, int y)
6811 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6814 return boards[currentMove][y][x];
6818 OKToStartUserMove (int x, int y)
6820 ChessSquare from_piece;
6823 if (matchMode) return FALSE;
6824 if (gameMode == EditPosition) return TRUE;
6826 if (x >= 0 && y >= 0)
6827 from_piece = boards[currentMove][y][x];
6829 from_piece = EmptySquare;
6831 if (from_piece == EmptySquare) return FALSE;
6833 white_piece = (int)from_piece >= (int)WhitePawn &&
6834 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6838 case TwoMachinesPlay:
6846 case MachinePlaysWhite:
6847 case IcsPlayingBlack:
6848 if (appData.zippyPlay) return FALSE;
6850 DisplayMoveError(_("You are playing Black"));
6855 case MachinePlaysBlack:
6856 case IcsPlayingWhite:
6857 if (appData.zippyPlay) return FALSE;
6859 DisplayMoveError(_("You are playing White"));
6864 case PlayFromGameFile:
6865 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6868 if (!white_piece && WhiteOnMove(currentMove)) {
6869 DisplayMoveError(_("It is White's turn"));
6872 if (white_piece && !WhiteOnMove(currentMove)) {
6873 DisplayMoveError(_("It is Black's turn"));
6876 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6877 /* Editing correspondence game history */
6878 /* Could disallow this or prompt for confirmation */
6883 case BeginningOfGame:
6884 if (appData.icsActive) return FALSE;
6885 if (!appData.noChessProgram) {
6887 DisplayMoveError(_("You are playing White"));
6894 if (!white_piece && WhiteOnMove(currentMove)) {
6895 DisplayMoveError(_("It is White's turn"));
6898 if (white_piece && !WhiteOnMove(currentMove)) {
6899 DisplayMoveError(_("It is Black's turn"));
6908 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6909 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6910 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6911 && gameMode != AnalyzeFile && gameMode != Training) {
6912 DisplayMoveError(_("Displayed position is not current"));
6919 OnlyMove (int *x, int *y, Boolean captures)
6921 DisambiguateClosure cl;
6922 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6924 case MachinePlaysBlack:
6925 case IcsPlayingWhite:
6926 case BeginningOfGame:
6927 if(!WhiteOnMove(currentMove)) return FALSE;
6929 case MachinePlaysWhite:
6930 case IcsPlayingBlack:
6931 if(WhiteOnMove(currentMove)) return FALSE;
6938 cl.pieceIn = EmptySquare;
6943 cl.promoCharIn = NULLCHAR;
6944 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6945 if( cl.kind == NormalMove ||
6946 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6947 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6948 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6955 if(cl.kind != ImpossibleMove) return FALSE;
6956 cl.pieceIn = EmptySquare;
6961 cl.promoCharIn = NULLCHAR;
6962 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6963 if( cl.kind == NormalMove ||
6964 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6965 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6966 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6971 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6977 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6978 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6979 int lastLoadGameUseList = FALSE;
6980 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6981 ChessMove lastLoadGameStart = EndOfFile;
6983 Boolean addToBookFlag;
6986 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6990 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6992 /* Check if the user is playing in turn. This is complicated because we
6993 let the user "pick up" a piece before it is his turn. So the piece he
6994 tried to pick up may have been captured by the time he puts it down!
6995 Therefore we use the color the user is supposed to be playing in this
6996 test, not the color of the piece that is currently on the starting
6997 square---except in EditGame mode, where the user is playing both
6998 sides; fortunately there the capture race can't happen. (It can
6999 now happen in IcsExamining mode, but that's just too bad. The user
7000 will get a somewhat confusing message in that case.)
7005 case TwoMachinesPlay:
7009 /* We switched into a game mode where moves are not accepted,
7010 perhaps while the mouse button was down. */
7013 case MachinePlaysWhite:
7014 /* User is moving for Black */
7015 if (WhiteOnMove(currentMove)) {
7016 DisplayMoveError(_("It is White's turn"));
7021 case MachinePlaysBlack:
7022 /* User is moving for White */
7023 if (!WhiteOnMove(currentMove)) {
7024 DisplayMoveError(_("It is Black's turn"));
7029 case PlayFromGameFile:
7030 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7033 case BeginningOfGame:
7036 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7037 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7038 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7039 /* User is moving for Black */
7040 if (WhiteOnMove(currentMove)) {
7041 DisplayMoveError(_("It is White's turn"));
7045 /* User is moving for White */
7046 if (!WhiteOnMove(currentMove)) {
7047 DisplayMoveError(_("It is Black's turn"));
7053 case IcsPlayingBlack:
7054 /* User is moving for Black */
7055 if (WhiteOnMove(currentMove)) {
7056 if (!appData.premove) {
7057 DisplayMoveError(_("It is White's turn"));
7058 } else if (toX >= 0 && toY >= 0) {
7061 premoveFromX = fromX;
7062 premoveFromY = fromY;
7063 premovePromoChar = promoChar;
7065 if (appData.debugMode)
7066 fprintf(debugFP, "Got premove: fromX %d,"
7067 "fromY %d, toX %d, toY %d\n",
7068 fromX, fromY, toX, toY);
7070 DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7075 case IcsPlayingWhite:
7076 /* User is moving for White */
7077 if (!WhiteOnMove(currentMove)) {
7078 if (!appData.premove) {
7079 DisplayMoveError(_("It is Black's turn"));
7080 } else if (toX >= 0 && toY >= 0) {
7083 premoveFromX = fromX;
7084 premoveFromY = fromY;
7085 premovePromoChar = promoChar;
7087 if (appData.debugMode)
7088 fprintf(debugFP, "Got premove: fromX %d,"
7089 "fromY %d, toX %d, toY %d\n",
7090 fromX, fromY, toX, toY);
7092 DrawPosition(TRUE, boards[currentMove]);
7101 /* EditPosition, empty square, or different color piece;
7102 click-click move is possible */
7103 if (toX == -2 || toY == -2) {
7104 boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7105 DrawPosition(FALSE, boards[currentMove]);
7107 } else if (toX >= 0 && toY >= 0) {
7108 if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7109 ChessSquare p = boards[0][rf][ff];
7110 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7111 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p);
7113 boards[0][toY][toX] = boards[0][fromY][fromX];
7114 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7115 if(boards[0][fromY][0] != EmptySquare) {
7116 if(boards[0][fromY][1]) boards[0][fromY][1]--;
7117 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
7120 if(fromX == BOARD_RGHT+1) {
7121 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7122 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7123 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7126 boards[0][fromY][fromX] = gatingPiece;
7128 DrawPosition(FALSE, boards[currentMove]);
7134 if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7135 pup = boards[currentMove][toY][toX];
7137 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7138 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7139 if( pup != EmptySquare ) return;
7140 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7141 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7142 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7143 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7144 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7145 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7146 while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7150 /* [HGM] always test for legality, to get promotion info */
7151 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7152 fromY, fromX, toY, toX, promoChar);
7154 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7156 if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7158 /* [HGM] but possibly ignore an IllegalMove result */
7159 if (appData.testLegality) {
7160 if (moveType == IllegalMove || moveType == ImpossibleMove) {
7161 DisplayMoveError(_("Illegal move"));
7166 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7167 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7168 ClearPremoveHighlights(); // was included
7169 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
7173 if(addToBookFlag) { // adding moves to book
7174 char buf[MSG_SIZ], move[MSG_SIZ];
7175 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7176 if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7177 killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7178 snprintf(buf, MSG_SIZ, " 0.0%% 1 %s\n", move);
7180 addToBookFlag = FALSE;
7185 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7188 /* Common tail of UserMoveEvent and DropMenuEvent */
7190 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7194 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7195 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7196 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7197 if(WhiteOnMove(currentMove)) {
7198 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7200 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7204 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7205 move type in caller when we know the move is a legal promotion */
7206 if(moveType == NormalMove && promoChar)
7207 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7209 /* [HGM] <popupFix> The following if has been moved here from
7210 UserMoveEvent(). Because it seemed to belong here (why not allow
7211 piece drops in training games?), and because it can only be
7212 performed after it is known to what we promote. */
7213 if (gameMode == Training) {
7214 /* compare the move played on the board to the next move in the
7215 * game. If they match, display the move and the opponent's response.
7216 * If they don't match, display an error message.
7220 CopyBoard(testBoard, boards[currentMove]);
7221 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7223 if (CompareBoards(testBoard, boards[currentMove+1])) {
7224 ForwardInner(currentMove+1);
7226 /* Autoplay the opponent's response.
7227 * if appData.animate was TRUE when Training mode was entered,
7228 * the response will be animated.
7230 saveAnimate = appData.animate;
7231 appData.animate = animateTraining;
7232 ForwardInner(currentMove+1);
7233 appData.animate = saveAnimate;
7235 /* check for the end of the game */
7236 if (currentMove >= forwardMostMove) {
7237 gameMode = PlayFromGameFile;
7239 SetTrainingModeOff();
7240 DisplayInformation(_("End of game"));
7243 DisplayError(_("Incorrect move"), 0);
7248 /* Ok, now we know that the move is good, so we can kill
7249 the previous line in Analysis Mode */
7250 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7251 && currentMove < forwardMostMove) {
7252 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7253 else forwardMostMove = currentMove;
7258 /* If we need the chess program but it's dead, restart it */
7259 ResurrectChessProgram();
7261 /* A user move restarts a paused game*/
7265 thinkOutput[0] = NULLCHAR;
7267 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7269 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7270 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7274 if (gameMode == BeginningOfGame) {
7275 if (appData.noChessProgram) {
7276 gameMode = EditGame;
7280 gameMode = MachinePlaysBlack;
7283 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7285 if (first.sendName) {
7286 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7287 SendToProgram(buf, &first);
7294 /* Relay move to ICS or chess engine */
7295 if (appData.icsActive) {
7296 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7297 gameMode == IcsExamining) {
7298 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7299 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7301 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7303 // also send plain move, in case ICS does not understand atomic claims
7304 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7308 if (first.sendTime && (gameMode == BeginningOfGame ||
7309 gameMode == MachinePlaysWhite ||
7310 gameMode == MachinePlaysBlack)) {
7311 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7313 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7314 // [HGM] book: if program might be playing, let it use book
7315 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7316 first.maybeThinking = TRUE;
7317 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7318 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7319 SendBoard(&first, currentMove+1);
7320 if(second.analyzing) {
7321 if(!second.useSetboard) SendToProgram("undo\n", &second);
7322 SendBoard(&second, currentMove+1);
7325 SendMoveToProgram(forwardMostMove-1, &first);
7326 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7328 if (currentMove == cmailOldMove + 1) {
7329 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7333 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7337 if(appData.testLegality)
7338 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7344 if (WhiteOnMove(currentMove)) {
7345 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7347 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7351 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7356 case MachinePlaysBlack:
7357 case MachinePlaysWhite:
7358 /* disable certain menu options while machine is thinking */
7359 SetMachineThinkingEnables();
7366 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7367 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7369 if(bookHit) { // [HGM] book: simulate book reply
7370 static char bookMove[MSG_SIZ]; // a bit generous?
7372 programStats.nodes = programStats.depth = programStats.time =
7373 programStats.score = programStats.got_only_move = 0;
7374 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7376 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7377 strcat(bookMove, bookHit);
7378 HandleMachineMove(bookMove, &first);
7384 MarkByFEN(char *fen)
7387 if(!appData.markers || !appData.highlightDragging) return;
7388 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7389 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7393 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7394 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7395 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7396 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7397 if(*fen == 'T') marker[r][f++] = 0; else
7398 if(*fen == 'Y') marker[r][f++] = 1; else
7399 if(*fen == 'G') marker[r][f++] = 3; else
7400 if(*fen == 'B') marker[r][f++] = 4; else
7401 if(*fen == 'C') marker[r][f++] = 5; else
7402 if(*fen == 'M') marker[r][f++] = 6; else
7403 if(*fen == 'W') marker[r][f++] = 7; else
7404 if(*fen == 'D') marker[r][f++] = 8; else
7405 if(*fen == 'R') marker[r][f++] = 2; else {
7406 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7409 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7413 DrawPosition(TRUE, NULL);
7416 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7419 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7421 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7422 Markers *m = (Markers *) closure;
7423 if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7424 kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7425 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7426 || kind == WhiteCapturesEnPassant
7427 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7428 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7431 static int hoverSavedValid;
7434 MarkTargetSquares (int clear)
7437 if(clear) { // no reason to ever suppress clearing
7438 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7439 hoverSavedValid = 0;
7440 if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7443 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7444 !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7445 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7446 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7447 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7449 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7452 DrawPosition(FALSE, NULL);
7456 Explode (Board board, int fromX, int fromY, int toX, int toY)
7458 if(gameInfo.variant == VariantAtomic &&
7459 (board[toY][toX] != EmptySquare || // capture?
7460 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7461 board[fromY][fromX] == BlackPawn )
7463 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7469 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7472 CanPromote (ChessSquare piece, int y)
7474 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7475 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7476 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7477 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7478 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7479 (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7480 gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7481 return (piece == BlackPawn && y <= zone ||
7482 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7483 piece == BlackLance && y <= zone ||
7484 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7488 HoverEvent (int xPix, int yPix, int x, int y)
7490 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7492 if(!first.highlight) return;
7493 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7494 if(x == oldX && y == oldY) return; // only do something if we enter new square
7495 oldFromX = fromX; oldFromY = fromY;
7496 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7497 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7498 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7499 hoverSavedValid = 1;
7500 } else if(oldX != x || oldY != y) {
7501 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7502 if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7503 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7504 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7505 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7507 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7508 SendToProgram(buf, &first);
7511 // SetHighlights(fromX, fromY, x, y);
7515 void ReportClick(char *action, int x, int y)
7517 char buf[MSG_SIZ]; // Inform engine of what user does
7519 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7520 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7521 legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7522 if(!first.highlight || gameMode == EditPosition) return;
7523 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7524 SendToProgram(buf, &first);
7527 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7530 LeftClick (ClickType clickType, int xPix, int yPix)
7533 Boolean saveAnimate;
7534 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7535 char promoChoice = NULLCHAR;
7537 static TimeMark lastClickTime, prevClickTime;
7539 if(flashing) return;
7541 x = EventToSquare(xPix, BOARD_WIDTH);
7542 y = EventToSquare(yPix, BOARD_HEIGHT);
7543 if (!flipView && y >= 0) {
7544 y = BOARD_HEIGHT - 1 - y;
7546 if (flipView && x >= 0) {
7547 x = BOARD_WIDTH - 1 - x;
7550 if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7552 RightClick(clickType, xPix, yPix, &dummy, &dummy);
7557 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7559 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7561 if (clickType == Press) ErrorPopDown();
7562 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7564 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7565 defaultPromoChoice = promoSweep;
7566 promoSweep = EmptySquare; // terminate sweep
7567 promoDefaultAltered = TRUE;
7568 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7571 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7572 if(clickType == Release) return; // ignore upclick of click-click destination
7573 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7574 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7575 if(gameInfo.holdingsWidth &&
7576 (WhiteOnMove(currentMove)
7577 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7578 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7579 // click in right holdings, for determining promotion piece
7580 ChessSquare p = boards[currentMove][y][x];
7581 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7582 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7583 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7584 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7589 DrawPosition(FALSE, boards[currentMove]);
7593 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7594 if(clickType == Press
7595 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7596 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7597 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7600 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7601 // could be static click on premove from-square: abort premove
7603 ClearPremoveHighlights();
7606 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7607 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7609 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7610 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7611 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7612 defaultPromoChoice = DefaultPromoChoice(side);
7615 autoQueen = appData.alwaysPromoteToQueen;
7619 gatingPiece = EmptySquare;
7620 if (clickType != Press) {
7621 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7622 DragPieceEnd(xPix, yPix); dragging = 0;
7623 DrawPosition(FALSE, NULL);
7627 doubleClick = FALSE;
7628 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7629 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7631 fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7632 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7633 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7634 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7636 if (OKToStartUserMove(fromX, fromY)) {
7638 ReportClick("lift", x, y);
7639 MarkTargetSquares(0);
7640 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7641 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7642 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7643 promoSweep = defaultPromoChoice;
7644 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7645 Sweep(0); // Pawn that is going to promote: preview promotion piece
7646 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7648 if (appData.highlightDragging) {
7649 SetHighlights(fromX, fromY, -1, -1);
7653 } else fromX = fromY = -1;
7659 if (clickType == Press && gameMode != EditPosition) {
7664 // ignore off-board to clicks
7665 if(y < 0 || x < 0) return;
7667 /* Check if clicking again on the same color piece */
7668 fromP = boards[currentMove][fromY][fromX];
7669 toP = boards[currentMove][y][x];
7670 frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7671 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7672 marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7673 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7674 WhitePawn <= toP && toP <= WhiteKing &&
7675 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7676 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7677 (BlackPawn <= fromP && fromP <= BlackKing &&
7678 BlackPawn <= toP && toP <= BlackKing &&
7679 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7680 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7681 /* Clicked again on same color piece -- changed his mind */
7682 second = (x == fromX && y == fromY);
7683 killX = killY = kill2X = kill2Y = -1;
7684 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7685 second = FALSE; // first double-click rather than scond click
7686 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7688 promoDefaultAltered = FALSE;
7689 if(!second) MarkTargetSquares(1);
7690 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7691 if (appData.highlightDragging) {
7692 SetHighlights(x, y, -1, -1);
7696 if (OKToStartUserMove(x, y)) {
7697 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7698 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7699 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7700 gatingPiece = boards[currentMove][fromY][fromX];
7701 else gatingPiece = doubleClick ? fromP : EmptySquare;
7703 fromY = y; dragging = 1;
7704 if(!second) ReportClick("lift", x, y);
7705 MarkTargetSquares(0);
7706 DragPieceBegin(xPix, yPix, FALSE);
7707 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7708 promoSweep = defaultPromoChoice;
7709 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7710 Sweep(0); // Pawn that is going to promote: preview promotion piece
7714 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7717 // ignore clicks on holdings
7718 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7721 if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7722 gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7723 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7727 if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7728 DragPieceEnd(xPix, yPix); dragging = 0;
7730 // a deferred attempt to click-click move an empty square on top of a piece
7731 boards[currentMove][y][x] = EmptySquare;
7733 DrawPosition(FALSE, boards[currentMove]);
7734 fromX = fromY = -1; clearFlag = 0;
7737 if (appData.animateDragging) {
7738 /* Undo animation damage if any */
7739 DrawPosition(FALSE, NULL);
7742 /* Second up/down in same square; just abort move */
7745 gatingPiece = EmptySquare;
7748 ClearPremoveHighlights();
7749 MarkTargetSquares(-1);
7750 DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7752 /* First upclick in same square; start click-click mode */
7753 SetHighlights(x, y, -1, -1);
7760 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7761 fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7762 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7763 DisplayMessage(_("only marked squares are legal"),"");
7764 DrawPosition(TRUE, NULL);
7765 return; // ignore to-click
7768 /* we now have a different from- and (possibly off-board) to-square */
7769 /* Completed move */
7770 if(!sweepSelecting) {
7775 piece = boards[currentMove][fromY][fromX];
7777 saveAnimate = appData.animate;
7778 if (clickType == Press) {
7779 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7780 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7781 // must be Edit Position mode with empty-square selected
7782 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7783 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7786 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7789 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7790 killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7792 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7793 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7794 if(appData.sweepSelect) {
7795 promoSweep = defaultPromoChoice;
7796 if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7797 selectFlag = 0; lastX = xPix; lastY = yPix;
7798 ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7799 saveFlash = appData.flashCount; appData.flashCount = 0;
7800 Sweep(0); // Pawn that is going to promote: preview promotion piece
7802 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7803 MarkTargetSquares(1);
7805 return; // promo popup appears on up-click
7807 /* Finish clickclick move */
7808 if (appData.animate || appData.highlightLastMove) {
7809 SetHighlights(fromX, fromY, toX, toY);
7813 MarkTargetSquares(1);
7814 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7815 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7816 *promoRestrict = 0; appData.flashCount = saveFlash;
7817 if (appData.animate || appData.highlightLastMove) {
7818 SetHighlights(fromX, fromY, toX, toY);
7822 MarkTargetSquares(1);
7825 // [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
7826 /* Finish drag move */
7827 if (appData.highlightLastMove) {
7828 SetHighlights(fromX, fromY, toX, toY);
7833 if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7834 defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7835 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7836 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7837 dragging *= 2; // flag button-less dragging if we are dragging
7838 MarkTargetSquares(1);
7839 if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7841 kill2X = killX; kill2Y = killY;
7842 killX = x; killY = y; // remember this square as intermediate
7843 ReportClick("put", x, y); // and inform engine
7844 ReportClick("lift", x, y);
7845 MarkTargetSquares(0);
7849 DragPieceEnd(xPix, yPix); dragging = 0;
7850 /* Don't animate move and drag both */
7851 appData.animate = FALSE;
7852 MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7855 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7856 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7857 ChessSquare piece = boards[currentMove][fromY][fromX];
7858 if(gameMode == EditPosition && piece != EmptySquare &&
7859 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7862 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7863 n = PieceToNumber(piece - (int)BlackPawn);
7864 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7865 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7866 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7868 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7869 n = PieceToNumber(piece);
7870 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7871 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7872 boards[currentMove][n][BOARD_WIDTH-2]++;
7874 boards[currentMove][fromY][fromX] = EmptySquare;
7878 MarkTargetSquares(1);
7879 DrawPosition(TRUE, boards[currentMove]);
7883 // off-board moves should not be highlighted
7884 if(x < 0 || y < 0) ClearHighlights();
7885 else ReportClick("put", x, y);
7887 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7889 if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7891 if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7892 SetHighlights(fromX, fromY, toX, toY);
7893 MarkTargetSquares(1);
7894 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7895 // [HGM] super: promotion to captured piece selected from holdings
7896 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7897 promotionChoice = TRUE;
7898 // kludge follows to temporarily execute move on display, without promoting yet
7899 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7900 boards[currentMove][toY][toX] = p;
7901 DrawPosition(FALSE, boards[currentMove]);
7902 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7903 boards[currentMove][toY][toX] = q;
7904 DisplayMessage("Click in holdings to choose piece", "");
7907 PromotionPopUp(promoChoice);
7909 int oldMove = currentMove;
7910 flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7911 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7912 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7913 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7914 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7915 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7916 DrawPosition(TRUE, boards[currentMove]);
7920 appData.animate = saveAnimate;
7921 if (appData.animate || appData.animateDragging) {
7922 /* Undo animation damage if needed */
7923 // DrawPosition(FALSE, NULL);
7928 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7929 { // front-end-free part taken out of PieceMenuPopup
7930 int whichMenu; int xSqr, ySqr;
7932 if(seekGraphUp) { // [HGM] seekgraph
7933 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7934 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7938 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7939 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7940 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7941 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7942 if(action == Press) {
7943 originalFlip = flipView;
7944 flipView = !flipView; // temporarily flip board to see game from partners perspective
7945 DrawPosition(TRUE, partnerBoard);
7946 DisplayMessage(partnerStatus, "");
7948 } else if(action == Release) {
7949 flipView = originalFlip;
7950 DrawPosition(TRUE, boards[currentMove]);
7956 xSqr = EventToSquare(x, BOARD_WIDTH);
7957 ySqr = EventToSquare(y, BOARD_HEIGHT);
7958 if (action == Release) {
7959 if(pieceSweep != EmptySquare) {
7960 EditPositionMenuEvent(pieceSweep, toX, toY);
7961 pieceSweep = EmptySquare;
7962 } else UnLoadPV(); // [HGM] pv
7964 if (action != Press) return -2; // return code to be ignored
7967 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7969 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7970 if (xSqr < 0 || ySqr < 0) return -1;
7971 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7972 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7973 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7974 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7978 if(!appData.icsEngineAnalyze) return -1;
7979 case IcsPlayingWhite:
7980 case IcsPlayingBlack:
7981 if(!appData.zippyPlay) goto noZip;
7984 case MachinePlaysWhite:
7985 case MachinePlaysBlack:
7986 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7987 if (!appData.dropMenu) {
7989 return 2; // flag front-end to grab mouse events
7991 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7992 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7995 if (xSqr < 0 || ySqr < 0) return -1;
7996 if (!appData.dropMenu || appData.testLegality &&
7997 gameInfo.variant != VariantBughouse &&
7998 gameInfo.variant != VariantCrazyhouse) return -1;
7999 whichMenu = 1; // drop menu
8005 if (((*fromX = xSqr) < 0) ||
8006 ((*fromY = ySqr) < 0)) {
8007 *fromX = *fromY = -1;
8011 *fromX = BOARD_WIDTH - 1 - *fromX;
8013 *fromY = BOARD_HEIGHT - 1 - *fromY;
8019 Wheel (int dir, int x, int y)
8021 if(gameMode == EditPosition) {
8022 int xSqr = EventToSquare(x, BOARD_WIDTH);
8023 int ySqr = EventToSquare(y, BOARD_HEIGHT);
8024 if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8025 if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8027 boards[currentMove][ySqr][xSqr] += dir;
8028 if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8029 if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8030 } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8031 DrawPosition(FALSE, boards[currentMove]);
8032 } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8036 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8038 // char * hint = lastHint;
8039 FrontEndProgramStats stats;
8041 stats.which = cps == &first ? 0 : 1;
8042 stats.depth = cpstats->depth;
8043 stats.nodes = cpstats->nodes;
8044 stats.score = cpstats->score;
8045 stats.time = cpstats->time;
8046 stats.pv = cpstats->movelist;
8047 stats.hint = lastHint;
8048 stats.an_move_index = 0;
8049 stats.an_move_count = 0;
8051 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8052 stats.hint = cpstats->move_name;
8053 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8054 stats.an_move_count = cpstats->nr_moves;
8057 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
8059 SetProgramStats( &stats );
8063 ClearEngineOutputPane (int which)
8065 static FrontEndProgramStats dummyStats;
8066 dummyStats.which = which;
8067 dummyStats.pv = "#";
8068 SetProgramStats( &dummyStats );
8071 #define MAXPLAYERS 500
8074 TourneyStandings (int display)
8076 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8077 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8078 char result, *p, *names[MAXPLAYERS];
8080 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8081 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8082 names[0] = p = strdup(appData.participants);
8083 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8085 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8087 while(result = appData.results[nr]) {
8088 color = Pairing(nr, nPlayers, &w, &b, &dummy);
8089 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8090 wScore = bScore = 0;
8092 case '+': wScore = 2; break;
8093 case '-': bScore = 2; break;
8094 case '=': wScore = bScore = 1; break;
8096 case '*': return strdup("busy"); // tourney not finished
8104 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8105 for(w=0; w<nPlayers; w++) {
8107 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8108 ranking[w] = b; points[w] = bScore; score[b] = -2;
8110 p = malloc(nPlayers*34+1);
8111 for(w=0; w<nPlayers && w<display; w++)
8112 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8118 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8119 { // count all piece types
8121 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8122 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8123 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8126 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8127 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8128 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8129 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8130 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
8131 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8136 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8138 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8139 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8141 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8142 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8143 if(myPawns == 2 && nMine == 3) // KPP
8144 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8145 if(myPawns == 1 && nMine == 2) // KP
8146 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8147 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8148 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8149 if(myPawns) return FALSE;
8150 if(pCnt[WhiteRook+side])
8151 return pCnt[BlackRook-side] ||
8152 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8153 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8154 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8155 if(pCnt[WhiteCannon+side]) {
8156 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8157 return majorDefense || pCnt[BlackAlfil-side] >= 2;
8159 if(pCnt[WhiteKnight+side])
8160 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8165 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8167 VariantClass v = gameInfo.variant;
8169 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8170 if(v == VariantShatranj) return TRUE; // always winnable through baring
8171 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8172 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8174 if(v == VariantXiangqi) {
8175 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8177 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8178 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8179 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8180 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8181 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8182 if(stale) // we have at least one last-rank P plus perhaps C
8183 return majors // KPKX
8184 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8186 return pCnt[WhiteFerz+side] // KCAK
8187 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8188 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8189 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8191 } else if(v == VariantKnightmate) {
8192 if(nMine == 1) return FALSE;
8193 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8194 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8195 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8197 if(nMine == 1) return FALSE; // bare King
8198 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
8199 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8200 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8201 // by now we have King + 1 piece (or multiple Bishops on the same color)
8202 if(pCnt[WhiteKnight+side])
8203 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8204 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8205 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8207 return (pCnt[BlackKnight-side]); // KBKN, KFKN
8208 if(pCnt[WhiteAlfil+side])
8209 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8210 if(pCnt[WhiteWazir+side])
8211 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8218 CompareWithRights (Board b1, Board b2)
8221 if(!CompareBoards(b1, b2)) return FALSE;
8222 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8223 /* compare castling rights */
8224 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8225 rights++; /* King lost rights, while rook still had them */
8226 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8227 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8228 rights++; /* but at least one rook lost them */
8230 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8232 if( b1[CASTLING][5] != NoRights ) {
8233 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8240 Adjudicate (ChessProgramState *cps)
8241 { // [HGM] some adjudications useful with buggy engines
8242 // [HGM] adjudicate: made into separate routine, which now can be called after every move
8243 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8244 // Actually ending the game is now based on the additional internal condition canAdjudicate.
8245 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8246 int k, drop, count = 0; static int bare = 1;
8247 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8248 Boolean canAdjudicate = !appData.icsActive;
8250 // most tests only when we understand the game, i.e. legality-checking on
8251 if( appData.testLegality )
8252 { /* [HGM] Some more adjudications for obstinate engines */
8253 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8254 static int moveCount = 6;
8256 char *reason = NULL;
8258 /* Count what is on board. */
8259 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8261 /* Some material-based adjudications that have to be made before stalemate test */
8262 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8263 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8264 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8265 if(canAdjudicate && appData.checkMates) {
8267 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8268 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8269 "Xboard adjudication: King destroyed", GE_XBOARD );
8274 /* Bare King in Shatranj (loses) or Losers (wins) */
8275 if( nrW == 1 || nrB == 1) {
8276 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8277 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
8278 if(canAdjudicate && appData.checkMates) {
8280 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8281 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8282 "Xboard adjudication: Bare king", GE_XBOARD );
8286 if( gameInfo.variant == VariantShatranj && --bare < 0)
8288 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8289 if(canAdjudicate && appData.checkMates) {
8290 /* but only adjudicate if adjudication enabled */
8292 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8293 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8294 "Xboard adjudication: Bare king", GE_XBOARD );
8301 // don't wait for engine to announce game end if we can judge ourselves
8302 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8304 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8305 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
8306 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8307 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8310 reason = "Xboard adjudication: 3rd check";
8311 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8322 reason = "Xboard adjudication: Stalemate";
8323 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8324 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8325 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8326 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8327 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8328 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8329 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8330 EP_CHECKMATE : EP_WINS);
8331 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8332 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8336 reason = "Xboard adjudication: Checkmate";
8337 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8338 if(gameInfo.variant == VariantShogi) {
8339 if(forwardMostMove > backwardMostMove
8340 && moveList[forwardMostMove-1][1] == '@'
8341 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8342 reason = "XBoard adjudication: pawn-drop mate";
8343 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8349 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8351 result = GameIsDrawn; break;
8353 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8355 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8359 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8361 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8362 GameEnds( result, reason, GE_XBOARD );
8366 /* Next absolutely insufficient mating material. */
8367 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8368 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8369 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8371 /* always flag draws, for judging claims */
8372 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8374 if(canAdjudicate && appData.materialDraws) {
8375 /* but only adjudicate them if adjudication enabled */
8376 if(engineOpponent) {
8377 SendToProgram("force\n", engineOpponent); // suppress reply
8378 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8380 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8385 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8386 if(gameInfo.variant == VariantXiangqi ?
8387 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8389 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8390 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8391 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8392 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8394 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8395 { /* if the first 3 moves do not show a tactical win, declare draw */
8396 if(engineOpponent) {
8397 SendToProgram("force\n", engineOpponent); // suppress reply
8398 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8400 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8403 } else moveCount = 6;
8406 // Repetition draws and 50-move rule can be applied independently of legality testing
8408 /* Check for rep-draws */
8410 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8411 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8412 for(k = forwardMostMove-2;
8413 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8414 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8415 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8418 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8419 /* compare castling rights */
8420 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8421 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8422 rights++; /* King lost rights, while rook still had them */
8423 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8424 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8425 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8426 rights++; /* but at least one rook lost them */
8428 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8429 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8431 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8432 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8433 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8436 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8437 && appData.drawRepeats > 1) {
8438 /* adjudicate after user-specified nr of repeats */
8439 int result = GameIsDrawn;
8440 char *details = "XBoard adjudication: repetition draw";
8441 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8442 // [HGM] xiangqi: check for forbidden perpetuals
8443 int m, ourPerpetual = 1, hisPerpetual = 1;
8444 for(m=forwardMostMove; m>k; m-=2) {
8445 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8446 ourPerpetual = 0; // the current mover did not always check
8447 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8448 hisPerpetual = 0; // the opponent did not always check
8450 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8451 ourPerpetual, hisPerpetual);
8452 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8453 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8454 details = "Xboard adjudication: perpetual checking";
8456 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8457 break; // (or we would have caught him before). Abort repetition-checking loop.
8459 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8460 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8462 details = "Xboard adjudication: repetition";
8464 } else // it must be XQ
8465 // Now check for perpetual chases
8466 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8467 hisPerpetual = PerpetualChase(k, forwardMostMove);
8468 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8469 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8470 static char resdet[MSG_SIZ];
8471 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8473 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8475 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8476 break; // Abort repetition-checking loop.
8478 // if neither of us is checking or chasing all the time, or both are, it is draw
8480 if(engineOpponent) {
8481 SendToProgram("force\n", engineOpponent); // suppress reply
8482 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8484 GameEnds( result, details, GE_XBOARD );
8487 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8488 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8492 /* Now we test for 50-move draws. Determine ply count */
8493 count = forwardMostMove;
8494 /* look for last irreversble move */
8495 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8497 /* if we hit starting position, add initial plies */
8498 if( count == backwardMostMove )
8499 count -= initialRulePlies;
8500 count = forwardMostMove - count;
8501 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8502 // adjust reversible move counter for checks in Xiangqi
8503 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8504 if(i < backwardMostMove) i = backwardMostMove;
8505 while(i <= forwardMostMove) {
8506 lastCheck = inCheck; // check evasion does not count
8507 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8508 if(inCheck || lastCheck) count--; // check does not count
8513 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8514 /* this is used to judge if draw claims are legal */
8515 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8516 if(engineOpponent) {
8517 SendToProgram("force\n", engineOpponent); // suppress reply
8518 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8520 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8524 /* if draw offer is pending, treat it as a draw claim
8525 * when draw condition present, to allow engines a way to
8526 * claim draws before making their move to avoid a race
8527 * condition occurring after their move
8529 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8531 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8532 p = "Draw claim: 50-move rule";
8533 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8534 p = "Draw claim: 3-fold repetition";
8535 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8536 p = "Draw claim: insufficient mating material";
8537 if( p != NULL && canAdjudicate) {
8538 if(engineOpponent) {
8539 SendToProgram("force\n", engineOpponent); // suppress reply
8540 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8542 GameEnds( GameIsDrawn, p, GE_XBOARD );
8547 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8548 if(engineOpponent) {
8549 SendToProgram("force\n", engineOpponent); // suppress reply
8550 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8552 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8558 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8559 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8560 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8565 int pieces[10], squares[10], cnt=0, r, f, res;
8567 static PPROBE_EGBB probeBB;
8568 if(!appData.testLegality) return 10;
8569 if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8570 if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8571 if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8572 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8573 ChessSquare piece = boards[forwardMostMove][r][f];
8574 int black = (piece >= BlackPawn);
8575 int type = piece - black*BlackPawn;
8576 if(piece == EmptySquare) continue;
8577 if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8578 if(type == WhiteKing) type = WhiteQueen + 1;
8579 type = egbbCode[type];
8580 squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8581 pieces[cnt] = type + black*6;
8582 if(++cnt > 5) return 11;
8584 pieces[cnt] = squares[cnt] = 0;
8586 if(loaded == 2) return 13; // loading failed before
8588 char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8591 loaded = 2; // prepare for failure
8592 if(!path) return 13; // no egbb installed
8593 strncpy(buf, path + 8, MSG_SIZ);
8594 if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8595 snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8596 lib = LoadLibrary(buf);
8597 if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8598 loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8599 probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8600 if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8601 p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8602 loaded = 1; // success!
8604 res = probeBB(forwardMostMove & 1, pieces, squares);
8605 return res > 0 ? 1 : res < 0 ? -1 : 0;
8609 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8610 { // [HGM] book: this routine intercepts moves to simulate book replies
8611 char *bookHit = NULL;
8613 if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8615 snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8616 SendToProgram(buf, cps);
8618 //first determine if the incoming move brings opponent into his book
8619 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8620 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8621 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8622 if(bookHit != NULL && !cps->bookSuspend) {
8623 // make sure opponent is not going to reply after receiving move to book position
8624 SendToProgram("force\n", cps);
8625 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8627 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8628 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8629 // now arrange restart after book miss
8631 // after a book hit we never send 'go', and the code after the call to this routine
8632 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8633 char buf[MSG_SIZ], *move = bookHit;
8635 int fromX, fromY, toX, toY;
8639 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8640 &fromX, &fromY, &toX, &toY, &promoChar)) {
8641 (void) CoordsToAlgebraic(boards[forwardMostMove],
8642 PosFlags(forwardMostMove),
8643 fromY, fromX, toY, toX, promoChar, move);
8645 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8649 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8650 SendToProgram(buf, cps);
8651 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8652 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8653 SendToProgram("go\n", cps);
8654 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8655 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8656 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8657 SendToProgram("go\n", cps);
8658 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8660 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8664 LoadError (char *errmess, ChessProgramState *cps)
8665 { // unloads engine and switches back to -ncp mode if it was first
8666 if(cps->initDone) return FALSE;
8667 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8668 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8671 appData.noChessProgram = TRUE;
8672 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8673 gameMode = BeginningOfGame; ModeHighlight();
8676 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8677 DisplayMessage("", ""); // erase waiting message
8678 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8683 ChessProgramState *savedState;
8685 DeferredBookMove (void)
8687 if(savedState->lastPing != savedState->lastPong)
8688 ScheduleDelayedEvent(DeferredBookMove, 10);
8690 HandleMachineMove(savedMessage, savedState);
8693 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8694 static ChessProgramState *stalledEngine;
8695 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8698 HandleMachineMove (char *message, ChessProgramState *cps)
8700 static char firstLeg[20], legs;
8701 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8702 char realname[MSG_SIZ];
8703 int fromX, fromY, toX, toY;
8705 char promoChar, roar;
8710 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8711 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8712 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8713 DisplayError(_("Invalid pairing from pairing engine"), 0);
8716 pairingReceived = 1;
8718 return; // Skim the pairing messages here.
8721 oldError = cps->userError; cps->userError = 0;
8723 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8725 * Kludge to ignore BEL characters
8727 while (*message == '\007') message++;
8730 * [HGM] engine debug message: ignore lines starting with '#' character
8732 if(cps->debug && *message == '#') return;
8735 * Look for book output
8737 if (cps == &first && bookRequested) {
8738 if (message[0] == '\t' || message[0] == ' ') {
8739 /* Part of the book output is here; append it */
8740 strcat(bookOutput, message);
8741 strcat(bookOutput, " \n");
8743 } else if (bookOutput[0] != NULLCHAR) {
8744 /* All of book output has arrived; display it */
8745 char *p = bookOutput;
8746 while (*p != NULLCHAR) {
8747 if (*p == '\t') *p = ' ';
8750 DisplayInformation(bookOutput);
8751 bookRequested = FALSE;
8752 /* Fall through to parse the current output */
8757 * Look for machine move.
8759 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8760 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8762 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8763 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8764 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8765 stalledEngine = cps;
8766 if(appData.ponderNextMove) { // bring opponent out of ponder
8767 if(gameMode == TwoMachinesPlay) {
8768 if(cps->other->pause)
8769 PauseEngine(cps->other);
8771 SendToProgram("easy\n", cps->other);
8780 /* This method is only useful on engines that support ping */
8781 if(abortEngineThink) {
8782 if (appData.debugMode) {
8783 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8785 SendToProgram("undo\n", cps);
8789 if (cps->lastPing != cps->lastPong) {
8790 /* Extra move from before last new; ignore */
8791 if (appData.debugMode) {
8792 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8799 int machineWhite = FALSE;
8802 case BeginningOfGame:
8803 /* Extra move from before last reset; ignore */
8804 if (appData.debugMode) {
8805 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8812 /* Extra move after we tried to stop. The mode test is
8813 not a reliable way of detecting this problem, but it's
8814 the best we can do on engines that don't support ping.
8816 if (appData.debugMode) {
8817 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8818 cps->which, gameMode);
8820 SendToProgram("undo\n", cps);
8823 case MachinePlaysWhite:
8824 case IcsPlayingWhite:
8825 machineWhite = TRUE;
8828 case MachinePlaysBlack:
8829 case IcsPlayingBlack:
8830 machineWhite = FALSE;
8833 case TwoMachinesPlay:
8834 machineWhite = (cps->twoMachinesColor[0] == 'w');
8837 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8838 if (appData.debugMode) {
8840 "Ignoring move out of turn by %s, gameMode %d"
8841 ", forwardMost %d\n",
8842 cps->which, gameMode, forwardMostMove);
8848 if(cps->alphaRank) AlphaRank(machineMove, 4);
8850 // [HGM] lion: (some very limited) support for Alien protocol
8851 killX = killY = kill2X = kill2Y = -1;
8852 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8853 if(legs++) return; // middle leg contains only redundant info, ignore (but count it)
8854 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8857 if(p = strchr(machineMove, ',')) { // we got both legs in one (happens on book move)
8858 char *q = strchr(p+1, ','); // second comma?
8859 safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8860 if(q) legs = 2, p = q; else legs = 1; // with 3-leg move we clipof first two legs!
8861 safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8863 if(firstLeg[0]) { // there was a previous leg;
8864 // only support case where same piece makes two step
8865 char buf[20], *p = machineMove+1, *q = buf+1, f;
8866 safeStrCpy(buf, machineMove, 20);
8867 while(isdigit(*q)) q++; // find start of to-square
8868 safeStrCpy(machineMove, firstLeg, 20);
8869 while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8870 if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg
8871 else if(*p == *buf) // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling)
8872 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8873 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8874 firstLeg[0] = NULLCHAR; legs = 0;
8877 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8878 &fromX, &fromY, &toX, &toY, &promoChar)) {
8879 /* Machine move could not be parsed; ignore it. */
8880 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8881 machineMove, _(cps->which));
8882 DisplayMoveError(buf1);
8883 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8884 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8885 if (gameMode == TwoMachinesPlay) {
8886 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8892 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8893 /* So we have to redo legality test with true e.p. status here, */
8894 /* to make sure an illegal e.p. capture does not slip through, */
8895 /* to cause a forfeit on a justified illegal-move complaint */
8896 /* of the opponent. */
8897 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8899 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8900 fromY, fromX, toY, toX, promoChar);
8901 if(moveType == IllegalMove) {
8902 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8903 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8904 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8907 } else if(!appData.fischerCastling)
8908 /* [HGM] Kludge to handle engines that send FRC-style castling
8909 when they shouldn't (like TSCP-Gothic) */
8911 case WhiteASideCastleFR:
8912 case BlackASideCastleFR:
8914 currentMoveString[2]++;
8916 case WhiteHSideCastleFR:
8917 case BlackHSideCastleFR:
8919 currentMoveString[2]--;
8921 default: ; // nothing to do, but suppresses warning of pedantic compilers
8924 hintRequested = FALSE;
8925 lastHint[0] = NULLCHAR;
8926 bookRequested = FALSE;
8927 /* Program may be pondering now */
8928 cps->maybeThinking = TRUE;
8929 if (cps->sendTime == 2) cps->sendTime = 1;
8930 if (cps->offeredDraw) cps->offeredDraw--;
8932 /* [AS] Save move info*/
8933 pvInfoList[ forwardMostMove ].score = programStats.score;
8934 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8935 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8937 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8939 /* Test suites abort the 'game' after one move */
8940 if(*appData.finger) {
8942 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8943 if(!f) f = fopen(appData.finger, "w");
8944 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8945 else { DisplayFatalError("Bad output file", errno, 0); return; }
8947 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8950 if(solvingTime >= 0) {
8951 snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8952 totalTime += solvingTime; first.matchWins++;
8954 snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8957 OutputKibitz(2, buf1);
8958 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8961 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8962 if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8965 while( count < adjudicateLossPlies ) {
8966 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8969 score = -score; /* Flip score for winning side */
8972 if( score > appData.adjudicateLossThreshold ) {
8979 if( count >= adjudicateLossPlies ) {
8980 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8982 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8983 "Xboard adjudication",
8990 if(Adjudicate(cps)) {
8991 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8992 return; // [HGM] adjudicate: for all automatic game ends
8996 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8998 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8999 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9001 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9003 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9005 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9006 char buf[3*MSG_SIZ];
9008 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9009 programStats.score / 100.,
9011 programStats.time / 100.,
9012 (unsigned int)programStats.nodes,
9013 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9014 programStats.movelist);
9020 /* [AS] Clear stats for next move */
9021 ClearProgramStats();
9022 thinkOutput[0] = NULLCHAR;
9023 hiddenThinkOutputState = 0;
9026 if (gameMode == TwoMachinesPlay) {
9027 /* [HGM] relaying draw offers moved to after reception of move */
9028 /* and interpreting offer as claim if it brings draw condition */
9029 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9030 SendToProgram("draw\n", cps->other);
9032 if (cps->other->sendTime) {
9033 SendTimeRemaining(cps->other,
9034 cps->other->twoMachinesColor[0] == 'w');
9036 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9037 if (firstMove && !bookHit) {
9039 if (cps->other->useColors) {
9040 SendToProgram(cps->other->twoMachinesColor, cps->other);
9042 SendToProgram("go\n", cps->other);
9044 cps->other->maybeThinking = TRUE;
9047 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9049 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9051 if (!pausing && appData.ringBellAfterMoves) {
9052 if(!roar) RingBell();
9056 * Reenable menu items that were disabled while
9057 * machine was thinking
9059 if (gameMode != TwoMachinesPlay)
9060 SetUserThinkingEnables();
9062 // [HGM] book: after book hit opponent has received move and is now in force mode
9063 // force the book reply into it, and then fake that it outputted this move by jumping
9064 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9066 static char bookMove[MSG_SIZ]; // a bit generous?
9068 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9069 strcat(bookMove, bookHit);
9072 programStats.nodes = programStats.depth = programStats.time =
9073 programStats.score = programStats.got_only_move = 0;
9074 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9076 if(cps->lastPing != cps->lastPong) {
9077 savedMessage = message; // args for deferred call
9079 ScheduleDelayedEvent(DeferredBookMove, 10);
9088 /* Set special modes for chess engines. Later something general
9089 * could be added here; for now there is just one kludge feature,
9090 * needed because Crafty 15.10 and earlier don't ignore SIGINT
9091 * when "xboard" is given as an interactive command.
9093 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9094 cps->useSigint = FALSE;
9095 cps->useSigterm = FALSE;
9097 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9098 ParseFeatures(message+8, cps);
9099 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9102 if (!strncmp(message, "setup ", 6) &&
9103 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9104 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9105 ) { // [HGM] allow first engine to define opening position
9106 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9107 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9109 if(sscanf(message, "setup (%s", buf) == 1) {
9110 s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9111 ASSIGN(appData.pieceToCharTable, buf);
9113 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9115 while(message[s] && message[s++] != ' ');
9116 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9117 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9118 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9119 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
9120 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9121 if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9122 startedFromSetupPosition = FALSE;
9125 if(startedFromSetupPosition) return;
9126 ParseFEN(boards[0], &dummy, message+s, FALSE);
9127 DrawPosition(TRUE, boards[0]);
9128 CopyBoard(initialPosition, boards[0]);
9129 startedFromSetupPosition = TRUE;
9132 if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9133 ChessSquare piece = WhitePawn;
9134 char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9135 if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9136 if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9137 piece += CharToPiece(ID & 255) - WhitePawn;
9138 if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9139 /* always accept definition of */ && piece != WhiteFalcon && piece != BlackFalcon
9140 /* wild-card pieces. */ && piece != WhiteCobra && piece != BlackCobra
9141 /* For variants we don't have */ && gameInfo.variant != VariantBerolina
9142 /* correct rules for, we cannot */ && gameInfo.variant != VariantCylinder
9143 /* enforce legality on our own! */ && gameInfo.variant != VariantUnknown
9144 && gameInfo.variant != VariantGreat
9145 && gameInfo.variant != VariantFairy ) return;
9146 if(piece < EmptySquare) {
9148 ASSIGN(pieceDesc[piece], buf1);
9149 if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9153 if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9154 promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9158 /* [HGM] Allow engine to set up a position. Don't ask me why one would
9159 * want this, I was asked to put it in, and obliged.
9161 if (!strncmp(message, "setboard ", 9)) {
9162 Board initial_position;
9164 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9166 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9167 DisplayError(_("Bad FEN received from engine"), 0);
9171 CopyBoard(boards[0], initial_position);
9172 initialRulePlies = FENrulePlies;
9173 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9174 else gameMode = MachinePlaysBlack;
9175 DrawPosition(FALSE, boards[currentMove]);
9181 * Look for communication commands
9183 if (!strncmp(message, "telluser ", 9)) {
9184 if(message[9] == '\\' && message[10] == '\\')
9185 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9187 DisplayNote(message + 9);
9190 if (!strncmp(message, "tellusererror ", 14)) {
9192 if(message[14] == '\\' && message[15] == '\\')
9193 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9195 DisplayError(message + 14, 0);
9198 if (!strncmp(message, "tellopponent ", 13)) {
9199 if (appData.icsActive) {
9201 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9205 DisplayNote(message + 13);
9209 if (!strncmp(message, "tellothers ", 11)) {
9210 if (appData.icsActive) {
9212 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9215 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9218 if (!strncmp(message, "tellall ", 8)) {
9219 if (appData.icsActive) {
9221 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9225 DisplayNote(message + 8);
9229 if (strncmp(message, "warning", 7) == 0) {
9230 /* Undocumented feature, use tellusererror in new code */
9231 DisplayError(message, 0);
9234 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9235 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9236 strcat(realname, " query");
9237 AskQuestion(realname, buf2, buf1, cps->pr);
9240 /* Commands from the engine directly to ICS. We don't allow these to be
9241 * sent until we are logged on. Crafty kibitzes have been known to
9242 * interfere with the login process.
9245 if (!strncmp(message, "tellics ", 8)) {
9246 SendToICS(message + 8);
9250 if (!strncmp(message, "tellicsnoalias ", 15)) {
9251 SendToICS(ics_prefix);
9252 SendToICS(message + 15);
9256 /* The following are for backward compatibility only */
9257 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9258 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9259 SendToICS(ics_prefix);
9265 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9266 if(initPing == cps->lastPong) {
9267 if(gameInfo.variant == VariantUnknown) {
9268 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9269 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9270 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9274 if(cps->lastPing == cps->lastPong && abortEngineThink) {
9275 abortEngineThink = FALSE;
9276 DisplayMessage("", "");
9281 if(!strncmp(message, "highlight ", 10)) {
9282 if(appData.testLegality && !*engineVariant && appData.markers) return;
9283 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9286 if(!strncmp(message, "click ", 6)) {
9287 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9288 if(appData.testLegality || !appData.oneClick) return;
9289 sscanf(message+6, "%c%d%c", &f, &y, &c);
9290 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9291 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9292 x = x*squareSize + (x+1)*lineGap + squareSize/2;
9293 y = y*squareSize + (y+1)*lineGap + squareSize/2;
9294 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9295 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9296 LeftClick(Release, lastLeftX, lastLeftY);
9297 controlKey = (c == ',');
9298 LeftClick(Press, x, y);
9299 LeftClick(Release, x, y);
9300 first.highlight = f;
9304 * If the move is illegal, cancel it and redraw the board.
9305 * Also deal with other error cases. Matching is rather loose
9306 * here to accommodate engines written before the spec.
9308 if (strncmp(message + 1, "llegal move", 11) == 0 ||
9309 strncmp(message, "Error", 5) == 0) {
9310 if (StrStr(message, "name") ||
9311 StrStr(message, "rating") || StrStr(message, "?") ||
9312 StrStr(message, "result") || StrStr(message, "board") ||
9313 StrStr(message, "bk") || StrStr(message, "computer") ||
9314 StrStr(message, "variant") || StrStr(message, "hint") ||
9315 StrStr(message, "random") || StrStr(message, "depth") ||
9316 StrStr(message, "accepted")) {
9319 if (StrStr(message, "protover")) {
9320 /* Program is responding to input, so it's apparently done
9321 initializing, and this error message indicates it is
9322 protocol version 1. So we don't need to wait any longer
9323 for it to initialize and send feature commands. */
9324 FeatureDone(cps, 1);
9325 cps->protocolVersion = 1;
9328 cps->maybeThinking = FALSE;
9330 if (StrStr(message, "draw")) {
9331 /* Program doesn't have "draw" command */
9332 cps->sendDrawOffers = 0;
9335 if (cps->sendTime != 1 &&
9336 (StrStr(message, "time") || StrStr(message, "otim"))) {
9337 /* Program apparently doesn't have "time" or "otim" command */
9341 if (StrStr(message, "analyze")) {
9342 cps->analysisSupport = FALSE;
9343 cps->analyzing = FALSE;
9344 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9345 EditGameEvent(); // [HGM] try to preserve loaded game
9346 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9347 DisplayError(buf2, 0);
9350 if (StrStr(message, "(no matching move)st")) {
9351 /* Special kludge for GNU Chess 4 only */
9352 cps->stKludge = TRUE;
9353 SendTimeControl(cps, movesPerSession, timeControl,
9354 timeIncrement, appData.searchDepth,
9358 if (StrStr(message, "(no matching move)sd")) {
9359 /* Special kludge for GNU Chess 4 only */
9360 cps->sdKludge = TRUE;
9361 SendTimeControl(cps, movesPerSession, timeControl,
9362 timeIncrement, appData.searchDepth,
9366 if (!StrStr(message, "llegal")) {
9369 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9370 gameMode == IcsIdle) return;
9371 if (forwardMostMove <= backwardMostMove) return;
9372 if (pausing) PauseEvent();
9373 if(appData.forceIllegal) {
9374 // [HGM] illegal: machine refused move; force position after move into it
9375 SendToProgram("force\n", cps);
9376 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9377 // we have a real problem now, as SendBoard will use the a2a3 kludge
9378 // when black is to move, while there might be nothing on a2 or black
9379 // might already have the move. So send the board as if white has the move.
9380 // But first we must change the stm of the engine, as it refused the last move
9381 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9382 if(WhiteOnMove(forwardMostMove)) {
9383 SendToProgram("a7a6\n", cps); // for the engine black still had the move
9384 SendBoard(cps, forwardMostMove); // kludgeless board
9386 SendToProgram("a2a3\n", cps); // for the engine white still had the move
9387 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9388 SendBoard(cps, forwardMostMove+1); // kludgeless board
9390 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9391 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9392 gameMode == TwoMachinesPlay)
9393 SendToProgram("go\n", cps);
9396 if (gameMode == PlayFromGameFile) {
9397 /* Stop reading this game file */
9398 gameMode = EditGame;
9401 /* [HGM] illegal-move claim should forfeit game when Xboard */
9402 /* only passes fully legal moves */
9403 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9404 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9405 "False illegal-move claim", GE_XBOARD );
9406 return; // do not take back move we tested as valid
9408 currentMove = forwardMostMove-1;
9409 DisplayMove(currentMove-1); /* before DisplayMoveError */
9410 SwitchClocks(forwardMostMove-1); // [HGM] race
9411 DisplayBothClocks();
9412 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9413 parseList[currentMove], _(cps->which));
9414 DisplayMoveError(buf1);
9415 DrawPosition(FALSE, boards[currentMove]);
9417 SetUserThinkingEnables();
9420 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9421 /* Program has a broken "time" command that
9422 outputs a string not ending in newline.
9426 if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9427 if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9428 sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1 ) return;
9432 * If chess program startup fails, exit with an error message.
9433 * Attempts to recover here are futile. [HGM] Well, we try anyway
9435 if ((StrStr(message, "unknown host") != NULL)
9436 || (StrStr(message, "No remote directory") != NULL)
9437 || (StrStr(message, "not found") != NULL)
9438 || (StrStr(message, "No such file") != NULL)
9439 || (StrStr(message, "can't alloc") != NULL)
9440 || (StrStr(message, "Permission denied") != NULL)) {
9442 cps->maybeThinking = FALSE;
9443 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9444 _(cps->which), cps->program, cps->host, message);
9445 RemoveInputSource(cps->isr);
9446 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9447 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9448 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9454 * Look for hint output
9456 if (sscanf(message, "Hint: %s", buf1) == 1) {
9457 if (cps == &first && hintRequested) {
9458 hintRequested = FALSE;
9459 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9460 &fromX, &fromY, &toX, &toY, &promoChar)) {
9461 (void) CoordsToAlgebraic(boards[forwardMostMove],
9462 PosFlags(forwardMostMove),
9463 fromY, fromX, toY, toX, promoChar, buf1);
9464 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9465 DisplayInformation(buf2);
9467 /* Hint move could not be parsed!? */
9468 snprintf(buf2, sizeof(buf2),
9469 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9470 buf1, _(cps->which));
9471 DisplayError(buf2, 0);
9474 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9480 * Ignore other messages if game is not in progress
9482 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9483 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9486 * look for win, lose, draw, or draw offer
9488 if (strncmp(message, "1-0", 3) == 0) {
9489 char *p, *q, *r = "";
9490 p = strchr(message, '{');
9498 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9500 } else if (strncmp(message, "0-1", 3) == 0) {
9501 char *p, *q, *r = "";
9502 p = strchr(message, '{');
9510 /* Kludge for Arasan 4.1 bug */
9511 if (strcmp(r, "Black resigns") == 0) {
9512 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9515 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9517 } else if (strncmp(message, "1/2", 3) == 0) {
9518 char *p, *q, *r = "";
9519 p = strchr(message, '{');
9528 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9531 } else if (strncmp(message, "White resign", 12) == 0) {
9532 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9534 } else if (strncmp(message, "Black resign", 12) == 0) {
9535 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9537 } else if (strncmp(message, "White matches", 13) == 0 ||
9538 strncmp(message, "Black matches", 13) == 0 ) {
9539 /* [HGM] ignore GNUShogi noises */
9541 } else if (strncmp(message, "White", 5) == 0 &&
9542 message[5] != '(' &&
9543 StrStr(message, "Black") == NULL) {
9544 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9546 } else if (strncmp(message, "Black", 5) == 0 &&
9547 message[5] != '(') {
9548 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9550 } else if (strcmp(message, "resign") == 0 ||
9551 strcmp(message, "computer resigns") == 0) {
9553 case MachinePlaysBlack:
9554 case IcsPlayingBlack:
9555 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9557 case MachinePlaysWhite:
9558 case IcsPlayingWhite:
9559 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9561 case TwoMachinesPlay:
9562 if (cps->twoMachinesColor[0] == 'w')
9563 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9565 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9572 } else if (strncmp(message, "opponent mates", 14) == 0) {
9574 case MachinePlaysBlack:
9575 case IcsPlayingBlack:
9576 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9578 case MachinePlaysWhite:
9579 case IcsPlayingWhite:
9580 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9582 case TwoMachinesPlay:
9583 if (cps->twoMachinesColor[0] == 'w')
9584 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9586 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9593 } else if (strncmp(message, "computer mates", 14) == 0) {
9595 case MachinePlaysBlack:
9596 case IcsPlayingBlack:
9597 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9599 case MachinePlaysWhite:
9600 case IcsPlayingWhite:
9601 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9603 case TwoMachinesPlay:
9604 if (cps->twoMachinesColor[0] == 'w')
9605 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9607 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9614 } else if (strncmp(message, "checkmate", 9) == 0) {
9615 if (WhiteOnMove(forwardMostMove)) {
9616 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9618 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9621 } else if (strstr(message, "Draw") != NULL ||
9622 strstr(message, "game is a draw") != NULL) {
9623 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9625 } else if (strstr(message, "offer") != NULL &&
9626 strstr(message, "draw") != NULL) {
9628 if (appData.zippyPlay && first.initDone) {
9629 /* Relay offer to ICS */
9630 SendToICS(ics_prefix);
9631 SendToICS("draw\n");
9634 cps->offeredDraw = 2; /* valid until this engine moves twice */
9635 if (gameMode == TwoMachinesPlay) {
9636 if (cps->other->offeredDraw) {
9637 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9638 /* [HGM] in two-machine mode we delay relaying draw offer */
9639 /* until after we also have move, to see if it is really claim */
9641 } else if (gameMode == MachinePlaysWhite ||
9642 gameMode == MachinePlaysBlack) {
9643 if (userOfferedDraw) {
9644 DisplayInformation(_("Machine accepts your draw offer"));
9645 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9647 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9654 * Look for thinking output
9656 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9657 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9659 int plylev, mvleft, mvtot, curscore, time;
9660 char mvname[MOVE_LEN];
9664 int prefixHint = FALSE;
9665 mvname[0] = NULLCHAR;
9668 case MachinePlaysBlack:
9669 case IcsPlayingBlack:
9670 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9672 case MachinePlaysWhite:
9673 case IcsPlayingWhite:
9674 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9679 case IcsObserving: /* [DM] icsEngineAnalyze */
9680 if (!appData.icsEngineAnalyze) ignore = TRUE;
9682 case TwoMachinesPlay:
9683 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9693 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9695 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9696 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9697 char score_buf[MSG_SIZ];
9699 if(nodes>>32 == u64Const(0xFFFFFFFF)) // [HGM] negative node count read
9700 nodes += u64Const(0x100000000);
9702 if (plyext != ' ' && plyext != '\t') {
9706 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9707 if( cps->scoreIsAbsolute &&
9708 ( gameMode == MachinePlaysBlack ||
9709 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9710 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9711 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9712 !WhiteOnMove(currentMove)
9715 curscore = -curscore;
9718 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9720 if(*bestMove) { // rememer time best EPD move was first found
9721 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9723 int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9724 ok &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9725 solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9728 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9731 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9732 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9733 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9734 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9735 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9736 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9740 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9741 DisplayError(_("failed writing PV"), 0);
9744 tempStats.depth = plylev;
9745 tempStats.nodes = nodes;
9746 tempStats.time = time;
9747 tempStats.score = curscore;
9748 tempStats.got_only_move = 0;
9750 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9753 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9754 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9755 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9756 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9757 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9758 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9759 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9760 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9763 /* Buffer overflow protection */
9764 if (pv[0] != NULLCHAR) {
9765 if (strlen(pv) >= sizeof(tempStats.movelist)
9766 && appData.debugMode) {
9768 "PV is too long; using the first %u bytes.\n",
9769 (unsigned) sizeof(tempStats.movelist) - 1);
9772 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9774 sprintf(tempStats.movelist, " no PV\n");
9777 if (tempStats.seen_stat) {
9778 tempStats.ok_to_send = 1;
9781 if (strchr(tempStats.movelist, '(') != NULL) {
9782 tempStats.line_is_book = 1;
9783 tempStats.nr_moves = 0;
9784 tempStats.moves_left = 0;
9786 tempStats.line_is_book = 0;
9789 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9790 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9792 SendProgramStatsToFrontend( cps, &tempStats );
9795 [AS] Protect the thinkOutput buffer from overflow... this
9796 is only useful if buf1 hasn't overflowed first!
9798 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9799 if(curscore >= MATE_SCORE)
9800 snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9801 else if(curscore <= -MATE_SCORE)
9802 snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9804 snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9805 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9807 (gameMode == TwoMachinesPlay ?
9808 ToUpper(cps->twoMachinesColor[0]) : ' '),
9810 prefixHint ? lastHint : "",
9811 prefixHint ? " " : "" );
9813 if( buf1[0] != NULLCHAR ) {
9814 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9816 if( strlen(pv) > max_len ) {
9817 if( appData.debugMode) {
9818 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9820 pv[max_len+1] = '\0';
9823 strcat( thinkOutput, pv);
9826 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9827 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9828 DisplayMove(currentMove - 1);
9832 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9833 /* crafty (9.25+) says "(only move) <move>"
9834 * if there is only 1 legal move
9836 sscanf(p, "(only move) %s", buf1);
9837 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9838 sprintf(programStats.movelist, "%s (only move)", buf1);
9839 programStats.depth = 1;
9840 programStats.nr_moves = 1;
9841 programStats.moves_left = 1;
9842 programStats.nodes = 1;
9843 programStats.time = 1;
9844 programStats.got_only_move = 1;
9846 /* Not really, but we also use this member to
9847 mean "line isn't going to change" (Crafty
9848 isn't searching, so stats won't change) */
9849 programStats.line_is_book = 1;
9851 SendProgramStatsToFrontend( cps, &programStats );
9853 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9854 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9855 DisplayMove(currentMove - 1);
9858 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9859 &time, &nodes, &plylev, &mvleft,
9860 &mvtot, mvname) >= 5) {
9861 /* The stat01: line is from Crafty (9.29+) in response
9862 to the "." command */
9863 programStats.seen_stat = 1;
9864 cps->maybeThinking = TRUE;
9866 if (programStats.got_only_move || !appData.periodicUpdates)
9869 programStats.depth = plylev;
9870 programStats.time = time;
9871 programStats.nodes = nodes;
9872 programStats.moves_left = mvleft;
9873 programStats.nr_moves = mvtot;
9874 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9875 programStats.ok_to_send = 1;
9876 programStats.movelist[0] = '\0';
9878 SendProgramStatsToFrontend( cps, &programStats );
9882 } else if (strncmp(message,"++",2) == 0) {
9883 /* Crafty 9.29+ outputs this */
9884 programStats.got_fail = 2;
9887 } else if (strncmp(message,"--",2) == 0) {
9888 /* Crafty 9.29+ outputs this */
9889 programStats.got_fail = 1;
9892 } else if (thinkOutput[0] != NULLCHAR &&
9893 strncmp(message, " ", 4) == 0) {
9894 unsigned message_len;
9897 while (*p && *p == ' ') p++;
9899 message_len = strlen( p );
9901 /* [AS] Avoid buffer overflow */
9902 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9903 strcat(thinkOutput, " ");
9904 strcat(thinkOutput, p);
9907 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9908 strcat(programStats.movelist, " ");
9909 strcat(programStats.movelist, p);
9912 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9913 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9914 DisplayMove(currentMove - 1);
9922 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9923 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9925 ChessProgramStats cpstats;
9927 if (plyext != ' ' && plyext != '\t') {
9931 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9932 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9933 curscore = -curscore;
9936 cpstats.depth = plylev;
9937 cpstats.nodes = nodes;
9938 cpstats.time = time;
9939 cpstats.score = curscore;
9940 cpstats.got_only_move = 0;
9941 cpstats.movelist[0] = '\0';
9943 if (buf1[0] != NULLCHAR) {
9944 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9947 cpstats.ok_to_send = 0;
9948 cpstats.line_is_book = 0;
9949 cpstats.nr_moves = 0;
9950 cpstats.moves_left = 0;
9952 SendProgramStatsToFrontend( cps, &cpstats );
9959 /* Parse a game score from the character string "game", and
9960 record it as the history of the current game. The game
9961 score is NOT assumed to start from the standard position.
9962 The display is not updated in any way.
9965 ParseGameHistory (char *game)
9968 int fromX, fromY, toX, toY, boardIndex;
9973 if (appData.debugMode)
9974 fprintf(debugFP, "Parsing game history: %s\n", game);
9976 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9977 gameInfo.site = StrSave(appData.icsHost);
9978 gameInfo.date = PGNDate();
9979 gameInfo.round = StrSave("-");
9981 /* Parse out names of players */
9982 while (*game == ' ') game++;
9984 while (*game != ' ') *p++ = *game++;
9986 gameInfo.white = StrSave(buf);
9987 while (*game == ' ') game++;
9989 while (*game != ' ' && *game != '\n') *p++ = *game++;
9991 gameInfo.black = StrSave(buf);
9994 boardIndex = blackPlaysFirst ? 1 : 0;
9997 yyboardindex = boardIndex;
9998 moveType = (ChessMove) Myylex();
10000 case IllegalMove: /* maybe suicide chess, etc. */
10001 if (appData.debugMode) {
10002 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10003 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10004 setbuf(debugFP, NULL);
10006 case WhitePromotion:
10007 case BlackPromotion:
10008 case WhiteNonPromotion:
10009 case BlackNonPromotion:
10012 case WhiteCapturesEnPassant:
10013 case BlackCapturesEnPassant:
10014 case WhiteKingSideCastle:
10015 case WhiteQueenSideCastle:
10016 case BlackKingSideCastle:
10017 case BlackQueenSideCastle:
10018 case WhiteKingSideCastleWild:
10019 case WhiteQueenSideCastleWild:
10020 case BlackKingSideCastleWild:
10021 case BlackQueenSideCastleWild:
10023 case WhiteHSideCastleFR:
10024 case WhiteASideCastleFR:
10025 case BlackHSideCastleFR:
10026 case BlackASideCastleFR:
10028 fromX = currentMoveString[0] - AAA;
10029 fromY = currentMoveString[1] - ONE;
10030 toX = currentMoveString[2] - AAA;
10031 toY = currentMoveString[3] - ONE;
10032 promoChar = currentMoveString[4];
10036 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10037 fromX = moveType == WhiteDrop ?
10038 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10039 (int) CharToPiece(ToLower(currentMoveString[0]));
10041 toX = currentMoveString[2] - AAA;
10042 toY = currentMoveString[3] - ONE;
10043 promoChar = NULLCHAR;
10045 case AmbiguousMove:
10047 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10048 if (appData.debugMode) {
10049 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10050 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10051 setbuf(debugFP, NULL);
10053 DisplayError(buf, 0);
10055 case ImpossibleMove:
10057 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10058 if (appData.debugMode) {
10059 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10060 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10061 setbuf(debugFP, NULL);
10063 DisplayError(buf, 0);
10066 if (boardIndex < backwardMostMove) {
10067 /* Oops, gap. How did that happen? */
10068 DisplayError(_("Gap in move list"), 0);
10071 backwardMostMove = blackPlaysFirst ? 1 : 0;
10072 if (boardIndex > forwardMostMove) {
10073 forwardMostMove = boardIndex;
10077 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10078 strcat(parseList[boardIndex-1], " ");
10079 strcat(parseList[boardIndex-1], yy_text);
10091 case GameUnfinished:
10092 if (gameMode == IcsExamining) {
10093 if (boardIndex < backwardMostMove) {
10094 /* Oops, gap. How did that happen? */
10097 backwardMostMove = blackPlaysFirst ? 1 : 0;
10100 gameInfo.result = moveType;
10101 p = strchr(yy_text, '{');
10102 if (p == NULL) p = strchr(yy_text, '(');
10105 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10107 q = strchr(p, *p == '{' ? '}' : ')');
10108 if (q != NULL) *q = NULLCHAR;
10111 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10112 gameInfo.resultDetails = StrSave(p);
10115 if (boardIndex >= forwardMostMove &&
10116 !(gameMode == IcsObserving && ics_gamenum == -1)) {
10117 backwardMostMove = blackPlaysFirst ? 1 : 0;
10120 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10121 fromY, fromX, toY, toX, promoChar,
10122 parseList[boardIndex]);
10123 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10124 /* currentMoveString is set as a side-effect of yylex */
10125 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10126 strcat(moveList[boardIndex], "\n");
10128 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10129 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10135 if(!IS_SHOGI(gameInfo.variant))
10136 strcat(parseList[boardIndex - 1], "+");
10140 strcat(parseList[boardIndex - 1], "#");
10147 /* Apply a move to the given board */
10149 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10151 ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10152 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10154 /* [HGM] compute & store e.p. status and castling rights for new position */
10155 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10157 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10158 oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10159 board[EP_STATUS] = EP_NONE;
10160 board[EP_FILE] = board[EP_RANK] = 100;
10162 if (fromY == DROP_RANK) {
10163 /* must be first */
10164 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10165 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10168 piece = board[toY][toX] = (ChessSquare) fromX;
10170 // ChessSquare victim;
10173 if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10174 // victim = board[killY][killX],
10175 killed = board[killY][killX],
10176 board[killY][killX] = EmptySquare,
10177 board[EP_STATUS] = EP_CAPTURE;
10178 if( kill2X >= 0 && kill2Y >= 0)
10179 killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10182 if( board[toY][toX] != EmptySquare ) {
10183 board[EP_STATUS] = EP_CAPTURE;
10184 if( (fromX != toX || fromY != toY) && // not igui!
10185 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10186 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
10187 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10191 pawn = board[fromY][fromX];
10192 if( pawn == WhiteLance || pawn == BlackLance ) {
10193 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10194 if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10195 else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10198 if( pawn == WhitePawn ) {
10199 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10200 board[EP_STATUS] = EP_PAWN_MOVE;
10201 if( toY-fromY>=2) {
10202 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10203 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
10204 gameInfo.variant != VariantBerolina || toX < fromX)
10205 board[EP_STATUS] = toX | berolina;
10206 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10207 gameInfo.variant != VariantBerolina || toX > fromX)
10208 board[EP_STATUS] = toX;
10211 if( pawn == BlackPawn ) {
10212 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10213 board[EP_STATUS] = EP_PAWN_MOVE;
10214 if( toY-fromY<= -2) {
10215 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10216 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
10217 gameInfo.variant != VariantBerolina || toX < fromX)
10218 board[EP_STATUS] = toX | berolina;
10219 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10220 gameInfo.variant != VariantBerolina || toX > fromX)
10221 board[EP_STATUS] = toX;
10225 if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10226 if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10227 if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10228 if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10230 for(i=0; i<nrCastlingRights; i++) {
10231 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10232 board[CASTLING][i] == toX && castlingRank[i] == toY
10233 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10236 if(gameInfo.variant == VariantSChess) { // update virginity
10237 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10238 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10239 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
10240 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
10243 if (fromX == toX && fromY == toY && killX < 0) return;
10245 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10246 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10247 if(gameInfo.variant == VariantKnightmate)
10248 king += (int) WhiteUnicorn - (int) WhiteKing;
10250 if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10251 && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) { // and tramples own
10252 board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10253 board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10254 board[EP_STATUS] = EP_NONE; // capture was fake!
10256 if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10257 board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10258 board[toY][toX] = piece;
10259 board[EP_STATUS] = EP_NONE; // capture was fake!
10261 /* Code added by Tord: */
10262 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10263 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10264 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10265 board[EP_STATUS] = EP_NONE; // capture was fake!
10266 board[fromY][fromX] = EmptySquare;
10267 board[toY][toX] = EmptySquare;
10268 if((toX > fromX) != (piece == WhiteRook)) {
10269 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10271 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10273 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10274 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10275 board[EP_STATUS] = EP_NONE;
10276 board[fromY][fromX] = EmptySquare;
10277 board[toY][toX] = EmptySquare;
10278 if((toX > fromX) != (piece == BlackRook)) {
10279 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10281 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10283 /* End of code added by Tord */
10285 } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10286 board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10287 board[toY][toX] = piece;
10288 } else if (board[fromY][fromX] == king
10289 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10290 && toY == fromY && toX > fromX+1) {
10291 for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10292 board[fromY][toX-1] = board[fromY][rookX];
10293 board[fromY][rookX] = EmptySquare;
10294 board[fromY][fromX] = EmptySquare;
10295 board[toY][toX] = king;
10296 } else if (board[fromY][fromX] == king
10297 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10298 && toY == fromY && toX < fromX-1) {
10299 for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10300 board[fromY][toX+1] = board[fromY][rookX];
10301 board[fromY][rookX] = EmptySquare;
10302 board[fromY][fromX] = EmptySquare;
10303 board[toY][toX] = king;
10304 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10305 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10306 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10308 /* white pawn promotion */
10309 board[toY][toX] = CharToPiece(ToUpper(promoChar));
10310 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10311 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10312 board[fromY][fromX] = EmptySquare;
10313 } else if ((fromY >= BOARD_HEIGHT>>1)
10314 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10316 && gameInfo.variant != VariantXiangqi
10317 && gameInfo.variant != VariantBerolina
10318 && (pawn == WhitePawn)
10319 && (board[toY][toX] == EmptySquare)) {
10320 board[fromY][fromX] = EmptySquare;
10321 board[toY][toX] = piece;
10322 if(toY == epRank - 128 + 1)
10323 captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10325 captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10326 } else if ((fromY == BOARD_HEIGHT-4)
10328 && gameInfo.variant == VariantBerolina
10329 && (board[fromY][fromX] == WhitePawn)
10330 && (board[toY][toX] == EmptySquare)) {
10331 board[fromY][fromX] = EmptySquare;
10332 board[toY][toX] = WhitePawn;
10333 if(oldEP & EP_BEROLIN_A) {
10334 captured = board[fromY][fromX-1];
10335 board[fromY][fromX-1] = EmptySquare;
10336 }else{ captured = board[fromY][fromX+1];
10337 board[fromY][fromX+1] = EmptySquare;
10339 } else if (board[fromY][fromX] == king
10340 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10341 && toY == fromY && toX > fromX+1) {
10342 for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10343 board[fromY][toX-1] = board[fromY][rookX];
10344 board[fromY][rookX] = EmptySquare;
10345 board[fromY][fromX] = EmptySquare;
10346 board[toY][toX] = king;
10347 } else if (board[fromY][fromX] == king
10348 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10349 && toY == fromY && toX < fromX-1) {
10350 for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10351 board[fromY][toX+1] = board[fromY][rookX];
10352 board[fromY][rookX] = EmptySquare;
10353 board[fromY][fromX] = EmptySquare;
10354 board[toY][toX] = king;
10355 } else if (fromY == 7 && fromX == 3
10356 && board[fromY][fromX] == BlackKing
10357 && toY == 7 && toX == 5) {
10358 board[fromY][fromX] = EmptySquare;
10359 board[toY][toX] = BlackKing;
10360 board[fromY][7] = EmptySquare;
10361 board[toY][4] = BlackRook;
10362 } else if (fromY == 7 && fromX == 3
10363 && board[fromY][fromX] == BlackKing
10364 && toY == 7 && toX == 1) {
10365 board[fromY][fromX] = EmptySquare;
10366 board[toY][toX] = BlackKing;
10367 board[fromY][0] = EmptySquare;
10368 board[toY][2] = BlackRook;
10369 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10370 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10371 && toY < promoRank && promoChar
10373 /* black pawn promotion */
10374 board[toY][toX] = CharToPiece(ToLower(promoChar));
10375 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10376 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10377 board[fromY][fromX] = EmptySquare;
10378 } else if ((fromY < BOARD_HEIGHT>>1)
10379 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10381 && gameInfo.variant != VariantXiangqi
10382 && gameInfo.variant != VariantBerolina
10383 && (pawn == BlackPawn)
10384 && (board[toY][toX] == EmptySquare)) {
10385 board[fromY][fromX] = EmptySquare;
10386 board[toY][toX] = piece;
10387 if(toY == epRank - 128 - 1)
10388 captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10390 captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10391 } else if ((fromY == 3)
10393 && gameInfo.variant == VariantBerolina
10394 && (board[fromY][fromX] == BlackPawn)
10395 && (board[toY][toX] == EmptySquare)) {
10396 board[fromY][fromX] = EmptySquare;
10397 board[toY][toX] = BlackPawn;
10398 if(oldEP & EP_BEROLIN_A) {
10399 captured = board[fromY][fromX-1];
10400 board[fromY][fromX-1] = EmptySquare;
10401 }else{ captured = board[fromY][fromX+1];
10402 board[fromY][fromX+1] = EmptySquare;
10405 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10406 board[fromY][fromX] = EmptySquare;
10407 board[toY][toX] = piece;
10411 if (gameInfo.holdingsWidth != 0) {
10413 /* !!A lot more code needs to be written to support holdings */
10414 /* [HGM] OK, so I have written it. Holdings are stored in the */
10415 /* penultimate board files, so they are automaticlly stored */
10416 /* in the game history. */
10417 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10418 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10419 /* Delete from holdings, by decreasing count */
10420 /* and erasing image if necessary */
10421 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10422 if(p < (int) BlackPawn) { /* white drop */
10423 p -= (int)WhitePawn;
10424 p = PieceToNumber((ChessSquare)p);
10425 if(p >= gameInfo.holdingsSize) p = 0;
10426 if(--board[p][BOARD_WIDTH-2] <= 0)
10427 board[p][BOARD_WIDTH-1] = EmptySquare;
10428 if((int)board[p][BOARD_WIDTH-2] < 0)
10429 board[p][BOARD_WIDTH-2] = 0;
10430 } else { /* black drop */
10431 p -= (int)BlackPawn;
10432 p = PieceToNumber((ChessSquare)p);
10433 if(p >= gameInfo.holdingsSize) p = 0;
10434 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10435 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10436 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10437 board[BOARD_HEIGHT-1-p][1] = 0;
10440 if (captured != EmptySquare && gameInfo.holdingsSize > 0
10441 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
10442 /* [HGM] holdings: Add to holdings, if holdings exist */
10443 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10444 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10445 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10447 p = (int) captured;
10448 if (p >= (int) BlackPawn) {
10449 p -= (int)BlackPawn;
10450 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10451 /* Restore shogi-promoted piece to its original first */
10452 captured = (ChessSquare) (DEMOTED(captured));
10455 p = PieceToNumber((ChessSquare)p);
10456 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10457 board[p][BOARD_WIDTH-2]++;
10458 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10460 p -= (int)WhitePawn;
10461 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10462 captured = (ChessSquare) (DEMOTED(captured));
10465 p = PieceToNumber((ChessSquare)p);
10466 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10467 board[BOARD_HEIGHT-1-p][1]++;
10468 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10471 } else if (gameInfo.variant == VariantAtomic) {
10472 if (captured != EmptySquare) {
10474 for (y = toY-1; y <= toY+1; y++) {
10475 for (x = toX-1; x <= toX+1; x++) {
10476 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10477 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10478 board[y][x] = EmptySquare;
10482 board[toY][toX] = EmptySquare;
10486 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10487 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10489 if(promoChar == '+') {
10490 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10491 board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10492 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10493 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10494 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10495 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10496 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10497 && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10498 board[toY][toX] = newPiece;
10500 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10501 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10502 // [HGM] superchess: take promotion piece out of holdings
10503 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10504 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10505 if(!--board[k][BOARD_WIDTH-2])
10506 board[k][BOARD_WIDTH-1] = EmptySquare;
10508 if(!--board[BOARD_HEIGHT-1-k][1])
10509 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10514 /* Updates forwardMostMove */
10516 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10518 int x = toX, y = toY;
10519 char *s = parseList[forwardMostMove];
10520 ChessSquare p = boards[forwardMostMove][toY][toX];
10521 // forwardMostMove++; // [HGM] bare: moved downstream
10523 if(kill2X >= 0) x = kill2X, y = kill2Y; else
10524 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10525 (void) CoordsToAlgebraic(boards[forwardMostMove],
10526 PosFlags(forwardMostMove),
10527 fromY, fromX, y, x, (killX < 0)*promoChar,
10529 if(kill2X >= 0 && kill2Y >= 0)
10530 sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10531 if(killX >= 0 && killY >= 0)
10532 sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10533 toX + AAA, toY + ONE - '0', promoChar);
10535 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10536 int timeLeft; static int lastLoadFlag=0; int king, piece;
10537 piece = boards[forwardMostMove][fromY][fromX];
10538 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10539 if(gameInfo.variant == VariantKnightmate)
10540 king += (int) WhiteUnicorn - (int) WhiteKing;
10541 if(forwardMostMove == 0) {
10542 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10543 fprintf(serverMoves, "%s;", UserName());
10544 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10545 fprintf(serverMoves, "%s;", second.tidy);
10546 fprintf(serverMoves, "%s;", first.tidy);
10547 if(gameMode == MachinePlaysWhite)
10548 fprintf(serverMoves, "%s;", UserName());
10549 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10550 fprintf(serverMoves, "%s;", second.tidy);
10551 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10552 lastLoadFlag = loadFlag;
10554 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10555 // print castling suffix
10556 if( toY == fromY && piece == king ) {
10558 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10560 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10563 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10564 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10565 boards[forwardMostMove][toY][toX] == EmptySquare
10566 && fromX != toX && fromY != toY)
10567 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10568 // promotion suffix
10569 if(promoChar != NULLCHAR) {
10570 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10571 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10572 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10573 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10576 char buf[MOVE_LEN*2], *p; int len;
10577 fprintf(serverMoves, "/%d/%d",
10578 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10579 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10580 else timeLeft = blackTimeRemaining/1000;
10581 fprintf(serverMoves, "/%d", timeLeft);
10582 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10583 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10584 if(p = strchr(buf, '=')) *p = NULLCHAR;
10585 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10586 fprintf(serverMoves, "/%s", buf);
10588 fflush(serverMoves);
10591 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10592 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10595 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10596 if (commentList[forwardMostMove+1] != NULL) {
10597 free(commentList[forwardMostMove+1]);
10598 commentList[forwardMostMove+1] = NULL;
10600 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10601 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10602 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10603 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10604 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10605 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10606 adjustedClock = FALSE;
10607 gameInfo.result = GameUnfinished;
10608 if (gameInfo.resultDetails != NULL) {
10609 free(gameInfo.resultDetails);
10610 gameInfo.resultDetails = NULL;
10612 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10613 moveList[forwardMostMove - 1]);
10614 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10620 if(!IS_SHOGI(gameInfo.variant))
10621 strcat(parseList[forwardMostMove - 1], "+");
10625 strcat(parseList[forwardMostMove - 1], "#");
10630 /* Updates currentMove if not pausing */
10632 ShowMove (int fromX, int fromY, int toX, int toY)
10634 int instant = (gameMode == PlayFromGameFile) ?
10635 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10636 if(appData.noGUI) return;
10637 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10639 if (forwardMostMove == currentMove + 1) {
10640 AnimateMove(boards[forwardMostMove - 1],
10641 fromX, fromY, toX, toY);
10644 currentMove = forwardMostMove;
10647 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10649 if (instant) return;
10651 DisplayMove(currentMove - 1);
10652 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10653 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10654 SetHighlights(fromX, fromY, toX, toY);
10657 DrawPosition(FALSE, boards[currentMove]);
10658 DisplayBothClocks();
10659 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10663 SendEgtPath (ChessProgramState *cps)
10664 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10665 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10667 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10670 char c, *q = name+1, *r, *s;
10672 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10673 while(*p && *p != ',') *q++ = *p++;
10674 *q++ = ':'; *q = 0;
10675 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10676 strcmp(name, ",nalimov:") == 0 ) {
10677 // take nalimov path from the menu-changeable option first, if it is defined
10678 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10679 SendToProgram(buf,cps); // send egtbpath command for nalimov
10681 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10682 (s = StrStr(appData.egtFormats, name)) != NULL) {
10683 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10684 s = r = StrStr(s, ":") + 1; // beginning of path info
10685 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10686 c = *r; *r = 0; // temporarily null-terminate path info
10687 *--q = 0; // strip of trailig ':' from name
10688 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10690 SendToProgram(buf,cps); // send egtbpath command for this format
10692 if(*p == ',') p++; // read away comma to position for next format name
10697 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10699 int width = 8, height = 8, holdings = 0; // most common sizes
10700 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10701 // correct the deviations default for each variant
10702 if( v == VariantXiangqi ) width = 9, height = 10;
10703 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10704 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10705 if( v == VariantCapablanca || v == VariantCapaRandom ||
10706 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10708 if( v == VariantCourier ) width = 12;
10709 if( v == VariantSuper ) holdings = 8;
10710 if( v == VariantGreat ) width = 10, holdings = 8;
10711 if( v == VariantSChess ) holdings = 7;
10712 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10713 if( v == VariantChuChess) width = 10, height = 10;
10714 if( v == VariantChu ) width = 12, height = 12;
10715 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10716 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10717 holdingsSize >= 0 && holdingsSize != holdings;
10720 char variantError[MSG_SIZ];
10723 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10724 { // returns error message (recognizable by upper-case) if engine does not support the variant
10725 char *p, *variant = VariantName(v);
10726 static char b[MSG_SIZ];
10727 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10728 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10729 holdingsSize, variant); // cook up sized variant name
10730 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10731 if(StrStr(list, b) == NULL) {
10732 // specific sized variant not known, check if general sizing allowed
10733 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10734 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10735 boardWidth, boardHeight, holdingsSize, engine);
10738 /* [HGM] here we really should compare with the maximum supported board size */
10740 } else snprintf(b, MSG_SIZ,"%s", variant);
10741 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10742 p = StrStr(list, b);
10743 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10745 // occurs not at all in list, or only as sub-string
10746 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10747 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10748 int l = strlen(variantError);
10750 while(p != list && p[-1] != ',') p--;
10751 q = strchr(p, ',');
10752 if(q) *q = NULLCHAR;
10753 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10762 InitChessProgram (ChessProgramState *cps, int setup)
10763 /* setup needed to setup FRC opening position */
10765 char buf[MSG_SIZ], *b;
10766 if (appData.noChessProgram) return;
10767 hintRequested = FALSE;
10768 bookRequested = FALSE;
10770 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10771 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10772 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10773 if(cps->memSize) { /* [HGM] memory */
10774 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10775 SendToProgram(buf, cps);
10777 SendEgtPath(cps); /* [HGM] EGT */
10778 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10779 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10780 SendToProgram(buf, cps);
10783 setboardSpoiledMachineBlack = FALSE;
10784 SendToProgram(cps->initString, cps);
10785 if (gameInfo.variant != VariantNormal &&
10786 gameInfo.variant != VariantLoadable
10787 /* [HGM] also send variant if board size non-standard */
10788 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10790 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10791 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10794 char c, *q = cps->variants, *p = strchr(q, ',');
10795 if(p) *p = NULLCHAR;
10796 v = StringToVariant(q);
10797 DisplayError(variantError, 0);
10798 if(v != VariantUnknown && cps == &first) {
10800 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10801 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10802 ASSIGN(appData.variant, q);
10803 Reset(TRUE, FALSE);
10809 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10810 SendToProgram(buf, cps);
10812 currentlyInitializedVariant = gameInfo.variant;
10814 /* [HGM] send opening position in FRC to first engine */
10816 SendToProgram("force\n", cps);
10818 /* engine is now in force mode! Set flag to wake it up after first move. */
10819 setboardSpoiledMachineBlack = 1;
10822 if (cps->sendICS) {
10823 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10824 SendToProgram(buf, cps);
10826 cps->maybeThinking = FALSE;
10827 cps->offeredDraw = 0;
10828 if (!appData.icsActive) {
10829 SendTimeControl(cps, movesPerSession, timeControl,
10830 timeIncrement, appData.searchDepth,
10833 if (appData.showThinking
10834 // [HGM] thinking: four options require thinking output to be sent
10835 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10837 SendToProgram("post\n", cps);
10839 SendToProgram("hard\n", cps);
10840 if (!appData.ponderNextMove) {
10841 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10842 it without being sure what state we are in first. "hard"
10843 is not a toggle, so that one is OK.
10845 SendToProgram("easy\n", cps);
10847 if (cps->usePing) {
10848 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10849 SendToProgram(buf, cps);
10851 cps->initDone = TRUE;
10852 ClearEngineOutputPane(cps == &second);
10857 ResendOptions (ChessProgramState *cps)
10858 { // send the stored value of the options
10861 Option *opt = cps->option;
10862 for(i=0; i<cps->nrOptions; i++, opt++) {
10863 switch(opt->type) {
10867 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10870 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10873 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10879 SendToProgram(buf, cps);
10884 StartChessProgram (ChessProgramState *cps)
10889 if (appData.noChessProgram) return;
10890 cps->initDone = FALSE;
10892 if (strcmp(cps->host, "localhost") == 0) {
10893 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10894 } else if (*appData.remoteShell == NULLCHAR) {
10895 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10897 if (*appData.remoteUser == NULLCHAR) {
10898 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10901 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10902 cps->host, appData.remoteUser, cps->program);
10904 err = StartChildProcess(buf, "", &cps->pr);
10908 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10909 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10910 if(cps != &first) return;
10911 appData.noChessProgram = TRUE;
10914 // DisplayFatalError(buf, err, 1);
10915 // cps->pr = NoProc;
10916 // cps->isr = NULL;
10920 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10921 if (cps->protocolVersion > 1) {
10922 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10923 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10924 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10925 cps->comboCnt = 0; // and values of combo boxes
10927 SendToProgram(buf, cps);
10928 if(cps->reload) ResendOptions(cps);
10930 SendToProgram("xboard\n", cps);
10935 TwoMachinesEventIfReady P((void))
10937 static int curMess = 0;
10938 if (first.lastPing != first.lastPong) {
10939 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10940 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10943 if (second.lastPing != second.lastPong) {
10944 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10945 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10948 DisplayMessage("", ""); curMess = 0;
10949 TwoMachinesEvent();
10953 MakeName (char *template)
10957 static char buf[MSG_SIZ];
10961 clock = time((time_t *)NULL);
10962 tm = localtime(&clock);
10964 while(*p++ = *template++) if(p[-1] == '%') {
10965 switch(*template++) {
10966 case 0: *p = 0; return buf;
10967 case 'Y': i = tm->tm_year+1900; break;
10968 case 'y': i = tm->tm_year-100; break;
10969 case 'M': i = tm->tm_mon+1; break;
10970 case 'd': i = tm->tm_mday; break;
10971 case 'h': i = tm->tm_hour; break;
10972 case 'm': i = tm->tm_min; break;
10973 case 's': i = tm->tm_sec; break;
10976 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10982 CountPlayers (char *p)
10985 while(p = strchr(p, '\n')) p++, n++; // count participants
10990 WriteTourneyFile (char *results, FILE *f)
10991 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10992 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10993 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10994 // create a file with tournament description
10995 fprintf(f, "-participants {%s}\n", appData.participants);
10996 fprintf(f, "-seedBase %d\n", appData.seedBase);
10997 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10998 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10999 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11000 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11001 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11002 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11003 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11004 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11005 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11006 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11007 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11008 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11009 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11010 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11011 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11012 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11013 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11014 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11015 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11016 fprintf(f, "-smpCores %d\n", appData.smpCores);
11018 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11020 fprintf(f, "-mps %d\n", appData.movesPerSession);
11021 fprintf(f, "-tc %s\n", appData.timeControl);
11022 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11024 fprintf(f, "-results \"%s\"\n", results);
11029 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11032 Substitute (char *participants, int expunge)
11034 int i, changed, changes=0, nPlayers=0;
11035 char *p, *q, *r, buf[MSG_SIZ];
11036 if(participants == NULL) return;
11037 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11038 r = p = participants; q = appData.participants;
11039 while(*p && *p == *q) {
11040 if(*p == '\n') r = p+1, nPlayers++;
11043 if(*p) { // difference
11044 while(*p && *p++ != '\n');
11045 while(*q && *q++ != '\n');
11046 changed = nPlayers;
11047 changes = 1 + (strcmp(p, q) != 0);
11049 if(changes == 1) { // a single engine mnemonic was changed
11050 q = r; while(*q) nPlayers += (*q++ == '\n');
11051 p = buf; while(*r && (*p = *r++) != '\n') p++;
11053 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11054 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11055 if(mnemonic[i]) { // The substitute is valid
11057 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11058 flock(fileno(f), LOCK_EX);
11059 ParseArgsFromFile(f);
11060 fseek(f, 0, SEEK_SET);
11061 FREE(appData.participants); appData.participants = participants;
11062 if(expunge) { // erase results of replaced engine
11063 int len = strlen(appData.results), w, b, dummy;
11064 for(i=0; i<len; i++) {
11065 Pairing(i, nPlayers, &w, &b, &dummy);
11066 if((w == changed || b == changed) && appData.results[i] == '*') {
11067 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11072 for(i=0; i<len; i++) {
11073 Pairing(i, nPlayers, &w, &b, &dummy);
11074 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11077 WriteTourneyFile(appData.results, f);
11078 fclose(f); // release lock
11081 } else DisplayError(_("No engine with the name you gave is installed"), 0);
11083 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11084 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
11085 free(participants);
11090 CheckPlayers (char *participants)
11093 char buf[MSG_SIZ], *p;
11094 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11095 while(p = strchr(participants, '\n')) {
11097 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11099 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11101 DisplayError(buf, 0);
11105 participants = p + 1;
11111 CreateTourney (char *name)
11114 if(matchMode && strcmp(name, appData.tourneyFile)) {
11115 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11117 if(name[0] == NULLCHAR) {
11118 if(appData.participants[0])
11119 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11122 f = fopen(name, "r");
11123 if(f) { // file exists
11124 ASSIGN(appData.tourneyFile, name);
11125 ParseArgsFromFile(f); // parse it
11127 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11128 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11129 DisplayError(_("Not enough participants"), 0);
11132 if(CheckPlayers(appData.participants)) return 0;
11133 ASSIGN(appData.tourneyFile, name);
11134 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11135 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11138 appData.noChessProgram = FALSE;
11139 appData.clockMode = TRUE;
11145 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11147 char buf[MSG_SIZ], *p, *q;
11148 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11149 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11150 skip = !all && group[0]; // if group requested, we start in skip mode
11151 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11152 p = names; q = buf; header = 0;
11153 while(*p && *p != '\n') *q++ = *p++;
11155 if(*p == '\n') p++;
11156 if(buf[0] == '#') {
11157 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11158 depth++; // we must be entering a new group
11159 if(all) continue; // suppress printing group headers when complete list requested
11161 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11163 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11164 if(engineList[i]) free(engineList[i]);
11165 engineList[i] = strdup(buf);
11166 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11167 if(engineMnemonic[i]) free(engineMnemonic[i]);
11168 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11170 sscanf(q + 8, "%s", buf + strlen(buf));
11173 engineMnemonic[i] = strdup(buf);
11176 engineList[i] = engineMnemonic[i] = NULL;
11180 // following implemented as macro to avoid type limitations
11181 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11184 SwapEngines (int n)
11185 { // swap settings for first engine and other engine (so far only some selected options)
11190 SWAP(chessProgram, p)
11192 SWAP(hasOwnBookUCI, h)
11193 SWAP(protocolVersion, h)
11195 SWAP(scoreIsAbsolute, h)
11200 SWAP(engOptions, p)
11201 SWAP(engInitString, p)
11202 SWAP(computerString, p)
11204 SWAP(fenOverride, p)
11206 SWAP(accumulateTC, h)
11213 GetEngineLine (char *s, int n)
11217 extern char *icsNames;
11218 if(!s || !*s) return 0;
11219 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11220 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11221 if(!mnemonic[i]) return 0;
11222 if(n == 11) return 1; // just testing if there was a match
11223 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11224 if(n == 1) SwapEngines(n);
11225 ParseArgsFromString(buf);
11226 if(n == 1) SwapEngines(n);
11227 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11228 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11229 ParseArgsFromString(buf);
11235 SetPlayer (int player, char *p)
11236 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
11238 char buf[MSG_SIZ], *engineName;
11239 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11240 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11241 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11243 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11244 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11245 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11246 ParseArgsFromString(buf);
11247 } else { // no engine with this nickname is installed!
11248 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11249 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11250 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11252 DisplayError(buf, 0);
11259 char *recentEngines;
11262 RecentEngineEvent (int nr)
11265 // SwapEngines(1); // bump first to second
11266 // ReplaceEngine(&second, 1); // and load it there
11267 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11268 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11269 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11270 ReplaceEngine(&first, 0);
11271 FloatToFront(&appData.recentEngineList, command[n]);
11276 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11277 { // determine players from game number
11278 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11280 if(appData.tourneyType == 0) {
11281 roundsPerCycle = (nPlayers - 1) | 1;
11282 pairingsPerRound = nPlayers / 2;
11283 } else if(appData.tourneyType > 0) {
11284 roundsPerCycle = nPlayers - appData.tourneyType;
11285 pairingsPerRound = appData.tourneyType;
11287 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11288 gamesPerCycle = gamesPerRound * roundsPerCycle;
11289 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11290 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11291 curRound = nr / gamesPerRound; nr %= gamesPerRound;
11292 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11293 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11294 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11296 if(appData.cycleSync) *syncInterval = gamesPerCycle;
11297 if(appData.roundSync) *syncInterval = gamesPerRound;
11299 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11301 if(appData.tourneyType == 0) {
11302 if(curPairing == (nPlayers-1)/2 ) {
11303 *whitePlayer = curRound;
11304 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11306 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11307 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11308 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11309 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11311 } else if(appData.tourneyType > 1) {
11312 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11313 *whitePlayer = curRound + appData.tourneyType;
11314 } else if(appData.tourneyType > 0) {
11315 *whitePlayer = curPairing;
11316 *blackPlayer = curRound + appData.tourneyType;
11319 // take care of white/black alternation per round.
11320 // For cycles and games this is already taken care of by default, derived from matchGame!
11321 return curRound & 1;
11325 NextTourneyGame (int nr, int *swapColors)
11326 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11328 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11330 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11331 tf = fopen(appData.tourneyFile, "r");
11332 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11333 ParseArgsFromFile(tf); fclose(tf);
11334 InitTimeControls(); // TC might be altered from tourney file
11336 nPlayers = CountPlayers(appData.participants); // count participants
11337 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11338 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11341 p = q = appData.results;
11342 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11343 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11344 DisplayMessage(_("Waiting for other game(s)"),"");
11345 waitingForGame = TRUE;
11346 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11349 waitingForGame = FALSE;
11352 if(appData.tourneyType < 0) {
11353 if(nr>=0 && !pairingReceived) {
11355 if(pairing.pr == NoProc) {
11356 if(!appData.pairingEngine[0]) {
11357 DisplayFatalError(_("No pairing engine specified"), 0, 1);
11360 StartChessProgram(&pairing); // starts the pairing engine
11362 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11363 SendToProgram(buf, &pairing);
11364 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11365 SendToProgram(buf, &pairing);
11366 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11368 pairingReceived = 0; // ... so we continue here
11370 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11371 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11372 matchGame = 1; roundNr = nr / syncInterval + 1;
11375 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11377 // redefine engines, engine dir, etc.
11378 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11379 if(first.pr == NoProc) {
11380 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11381 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
11383 if(second.pr == NoProc) {
11385 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11386 SwapEngines(1); // and make that valid for second engine by swapping
11387 InitEngine(&second, 1);
11389 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
11390 UpdateLogos(FALSE); // leave display to ModeHiglight()
11396 { // performs game initialization that does not invoke engines, and then tries to start the game
11397 int res, firstWhite, swapColors = 0;
11398 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11399 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
11401 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11402 if(strcmp(buf, currentDebugFile)) { // name has changed
11403 FILE *f = fopen(buf, "w");
11404 if(f) { // if opening the new file failed, just keep using the old one
11405 ASSIGN(currentDebugFile, buf);
11409 if(appData.serverFileName) {
11410 if(serverFP) fclose(serverFP);
11411 serverFP = fopen(appData.serverFileName, "w");
11412 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11413 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11417 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11418 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11419 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11420 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11421 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11422 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11423 Reset(FALSE, first.pr != NoProc);
11424 res = LoadGameOrPosition(matchGame); // setup game
11425 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11426 if(!res) return; // abort when bad game/pos file
11427 TwoMachinesEvent();
11431 UserAdjudicationEvent (int result)
11433 ChessMove gameResult = GameIsDrawn;
11436 gameResult = WhiteWins;
11438 else if( result < 0 ) {
11439 gameResult = BlackWins;
11442 if( gameMode == TwoMachinesPlay ) {
11443 GameEnds( gameResult, "User adjudication", GE_XBOARD );
11448 // [HGM] save: calculate checksum of game to make games easily identifiable
11450 StringCheckSum (char *s)
11453 if(s==NULL) return 0;
11454 while(*s) i = i*259 + *s++;
11462 for(i=backwardMostMove; i<forwardMostMove; i++) {
11463 sum += pvInfoList[i].depth;
11464 sum += StringCheckSum(parseList[i]);
11465 sum += StringCheckSum(commentList[i]);
11468 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11469 return sum + StringCheckSum(commentList[i]);
11470 } // end of save patch
11473 GameEnds (ChessMove result, char *resultDetails, int whosays)
11475 GameMode nextGameMode;
11477 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11479 if(endingGame) return; /* [HGM] crash: forbid recursion */
11481 if(twoBoards) { // [HGM] dual: switch back to one board
11482 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11483 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11485 if (appData.debugMode) {
11486 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11487 result, resultDetails ? resultDetails : "(null)", whosays);
11490 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11492 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11494 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11495 /* If we are playing on ICS, the server decides when the
11496 game is over, but the engine can offer to draw, claim
11500 if (appData.zippyPlay && first.initDone) {
11501 if (result == GameIsDrawn) {
11502 /* In case draw still needs to be claimed */
11503 SendToICS(ics_prefix);
11504 SendToICS("draw\n");
11505 } else if (StrCaseStr(resultDetails, "resign")) {
11506 SendToICS(ics_prefix);
11507 SendToICS("resign\n");
11511 endingGame = 0; /* [HGM] crash */
11515 /* If we're loading the game from a file, stop */
11516 if (whosays == GE_FILE) {
11517 (void) StopLoadGameTimer();
11521 /* Cancel draw offers */
11522 first.offeredDraw = second.offeredDraw = 0;
11524 /* If this is an ICS game, only ICS can really say it's done;
11525 if not, anyone can. */
11526 isIcsGame = (gameMode == IcsPlayingWhite ||
11527 gameMode == IcsPlayingBlack ||
11528 gameMode == IcsObserving ||
11529 gameMode == IcsExamining);
11531 if (!isIcsGame || whosays == GE_ICS) {
11532 /* OK -- not an ICS game, or ICS said it was done */
11534 if (!isIcsGame && !appData.noChessProgram)
11535 SetUserThinkingEnables();
11537 /* [HGM] if a machine claims the game end we verify this claim */
11538 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11539 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11541 ChessMove trueResult = (ChessMove) -1;
11543 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11544 first.twoMachinesColor[0] :
11545 second.twoMachinesColor[0] ;
11547 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11548 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11549 /* [HGM] verify: engine mate claims accepted if they were flagged */
11550 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11552 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11553 /* [HGM] verify: engine mate claims accepted if they were flagged */
11554 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11556 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11557 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11560 // now verify win claims, but not in drop games, as we don't understand those yet
11561 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11562 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11563 (result == WhiteWins && claimer == 'w' ||
11564 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11565 if (appData.debugMode) {
11566 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11567 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11569 if(result != trueResult) {
11570 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11571 result = claimer == 'w' ? BlackWins : WhiteWins;
11572 resultDetails = buf;
11575 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11576 && (forwardMostMove <= backwardMostMove ||
11577 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11578 (claimer=='b')==(forwardMostMove&1))
11580 /* [HGM] verify: draws that were not flagged are false claims */
11581 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11582 result = claimer == 'w' ? BlackWins : WhiteWins;
11583 resultDetails = buf;
11585 /* (Claiming a loss is accepted no questions asked!) */
11586 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11587 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11588 result = GameUnfinished;
11589 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11591 /* [HGM] bare: don't allow bare King to win */
11592 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11593 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11594 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11595 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11596 && result != GameIsDrawn)
11597 { int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11598 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11599 int p = (signed char)boards[forwardMostMove][i][j] - color;
11600 if(p >= 0 && p <= (int)WhiteKing) k++;
11601 oppoKings += (p + color == WhiteKing + BlackPawn - color);
11603 if (appData.debugMode) {
11604 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11605 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11607 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11608 result = GameIsDrawn;
11609 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11610 resultDetails = buf;
11616 if(serverMoves != NULL && !loadFlag) { char c = '=';
11617 if(result==WhiteWins) c = '+';
11618 if(result==BlackWins) c = '-';
11619 if(resultDetails != NULL)
11620 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11622 if (resultDetails != NULL) {
11623 gameInfo.result = result;
11624 gameInfo.resultDetails = StrSave(resultDetails);
11626 /* display last move only if game was not loaded from file */
11627 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11628 DisplayMove(currentMove - 1);
11630 if (forwardMostMove != 0) {
11631 if (gameMode != PlayFromGameFile && gameMode != EditGame
11632 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11634 if (*appData.saveGameFile != NULLCHAR) {
11635 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11636 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11638 SaveGameToFile(appData.saveGameFile, TRUE);
11639 } else if (appData.autoSaveGames) {
11640 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11642 if (*appData.savePositionFile != NULLCHAR) {
11643 SavePositionToFile(appData.savePositionFile);
11645 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11649 /* Tell program how game ended in case it is learning */
11650 /* [HGM] Moved this to after saving the PGN, just in case */
11651 /* engine died and we got here through time loss. In that */
11652 /* case we will get a fatal error writing the pipe, which */
11653 /* would otherwise lose us the PGN. */
11654 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11655 /* output during GameEnds should never be fatal anymore */
11656 if (gameMode == MachinePlaysWhite ||
11657 gameMode == MachinePlaysBlack ||
11658 gameMode == TwoMachinesPlay ||
11659 gameMode == IcsPlayingWhite ||
11660 gameMode == IcsPlayingBlack ||
11661 gameMode == BeginningOfGame) {
11663 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11665 if (first.pr != NoProc) {
11666 SendToProgram(buf, &first);
11668 if (second.pr != NoProc &&
11669 gameMode == TwoMachinesPlay) {
11670 SendToProgram(buf, &second);
11675 if (appData.icsActive) {
11676 if (appData.quietPlay &&
11677 (gameMode == IcsPlayingWhite ||
11678 gameMode == IcsPlayingBlack)) {
11679 SendToICS(ics_prefix);
11680 SendToICS("set shout 1\n");
11682 nextGameMode = IcsIdle;
11683 ics_user_moved = FALSE;
11684 /* clean up premove. It's ugly when the game has ended and the
11685 * premove highlights are still on the board.
11688 gotPremove = FALSE;
11689 ClearPremoveHighlights();
11690 DrawPosition(FALSE, boards[currentMove]);
11692 if (whosays == GE_ICS) {
11695 if (gameMode == IcsPlayingWhite)
11697 else if(gameMode == IcsPlayingBlack)
11698 PlayIcsLossSound();
11701 if (gameMode == IcsPlayingBlack)
11703 else if(gameMode == IcsPlayingWhite)
11704 PlayIcsLossSound();
11707 PlayIcsDrawSound();
11710 PlayIcsUnfinishedSound();
11713 if(appData.quitNext) { ExitEvent(0); return; }
11714 } else if (gameMode == EditGame ||
11715 gameMode == PlayFromGameFile ||
11716 gameMode == AnalyzeMode ||
11717 gameMode == AnalyzeFile) {
11718 nextGameMode = gameMode;
11720 nextGameMode = EndOfGame;
11725 nextGameMode = gameMode;
11728 if (appData.noChessProgram) {
11729 gameMode = nextGameMode;
11731 endingGame = 0; /* [HGM] crash */
11736 /* Put first chess program into idle state */
11737 if (first.pr != NoProc &&
11738 (gameMode == MachinePlaysWhite ||
11739 gameMode == MachinePlaysBlack ||
11740 gameMode == TwoMachinesPlay ||
11741 gameMode == IcsPlayingWhite ||
11742 gameMode == IcsPlayingBlack ||
11743 gameMode == BeginningOfGame)) {
11744 SendToProgram("force\n", &first);
11745 if (first.usePing) {
11747 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11748 SendToProgram(buf, &first);
11751 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11752 /* Kill off first chess program */
11753 if (first.isr != NULL)
11754 RemoveInputSource(first.isr);
11757 if (first.pr != NoProc) {
11759 DoSleep( appData.delayBeforeQuit );
11760 SendToProgram("quit\n", &first);
11761 DestroyChildProcess(first.pr, 4 + first.useSigterm);
11762 first.reload = TRUE;
11766 if (second.reuse) {
11767 /* Put second chess program into idle state */
11768 if (second.pr != NoProc &&
11769 gameMode == TwoMachinesPlay) {
11770 SendToProgram("force\n", &second);
11771 if (second.usePing) {
11773 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11774 SendToProgram(buf, &second);
11777 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11778 /* Kill off second chess program */
11779 if (second.isr != NULL)
11780 RemoveInputSource(second.isr);
11783 if (second.pr != NoProc) {
11784 DoSleep( appData.delayBeforeQuit );
11785 SendToProgram("quit\n", &second);
11786 DestroyChildProcess(second.pr, 4 + second.useSigterm);
11787 second.reload = TRUE;
11789 second.pr = NoProc;
11792 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11793 char resChar = '=';
11797 if (first.twoMachinesColor[0] == 'w') {
11800 second.matchWins++;
11805 if (first.twoMachinesColor[0] == 'b') {
11808 second.matchWins++;
11811 case GameUnfinished:
11817 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11818 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11819 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11820 ReserveGame(nextGame, resChar); // sets nextGame
11821 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11822 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11823 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11825 if (nextGame <= appData.matchGames && !abortMatch) {
11826 gameMode = nextGameMode;
11827 matchGame = nextGame; // this will be overruled in tourney mode!
11828 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11829 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11830 endingGame = 0; /* [HGM] crash */
11833 gameMode = nextGameMode;
11834 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11835 first.tidy, second.tidy,
11836 first.matchWins, second.matchWins,
11837 appData.matchGames - (first.matchWins + second.matchWins));
11838 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11839 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11840 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11841 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11842 first.twoMachinesColor = "black\n";
11843 second.twoMachinesColor = "white\n";
11845 first.twoMachinesColor = "white\n";
11846 second.twoMachinesColor = "black\n";
11850 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11851 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11853 gameMode = nextGameMode;
11855 endingGame = 0; /* [HGM] crash */
11856 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11857 if(matchMode == TRUE) { // match through command line: exit with or without popup
11859 ToNrEvent(forwardMostMove);
11860 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11862 } else DisplayFatalError(buf, 0, 0);
11863 } else { // match through menu; just stop, with or without popup
11864 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11867 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11868 } else DisplayNote(buf);
11870 if(ranking) free(ranking);
11874 /* Assumes program was just initialized (initString sent).
11875 Leaves program in force mode. */
11877 FeedMovesToProgram (ChessProgramState *cps, int upto)
11881 if (appData.debugMode)
11882 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11883 startedFromSetupPosition ? "position and " : "",
11884 backwardMostMove, upto, cps->which);
11885 if(currentlyInitializedVariant != gameInfo.variant) {
11887 // [HGM] variantswitch: make engine aware of new variant
11888 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11889 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11890 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11891 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11892 SendToProgram(buf, cps);
11893 currentlyInitializedVariant = gameInfo.variant;
11895 SendToProgram("force\n", cps);
11896 if (startedFromSetupPosition) {
11897 SendBoard(cps, backwardMostMove);
11898 if (appData.debugMode) {
11899 fprintf(debugFP, "feedMoves\n");
11902 for (i = backwardMostMove; i < upto; i++) {
11903 SendMoveToProgram(i, cps);
11909 ResurrectChessProgram ()
11911 /* The chess program may have exited.
11912 If so, restart it and feed it all the moves made so far. */
11913 static int doInit = 0;
11915 if (appData.noChessProgram) return 1;
11917 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11918 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11919 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11920 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11922 if (first.pr != NoProc) return 1;
11923 StartChessProgram(&first);
11925 InitChessProgram(&first, FALSE);
11926 FeedMovesToProgram(&first, currentMove);
11928 if (!first.sendTime) {
11929 /* can't tell gnuchess what its clock should read,
11930 so we bow to its notion. */
11932 timeRemaining[0][currentMove] = whiteTimeRemaining;
11933 timeRemaining[1][currentMove] = blackTimeRemaining;
11936 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11937 appData.icsEngineAnalyze) && first.analysisSupport) {
11938 SendToProgram("analyze\n", &first);
11939 first.analyzing = TRUE;
11945 * Button procedures
11948 Reset (int redraw, int init)
11952 if (appData.debugMode) {
11953 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11954 redraw, init, gameMode);
11956 pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11957 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11958 CleanupTail(); // [HGM] vari: delete any stored variations
11959 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11960 pausing = pauseExamInvalid = FALSE;
11961 startedFromSetupPosition = blackPlaysFirst = FALSE;
11963 whiteFlag = blackFlag = FALSE;
11964 userOfferedDraw = FALSE;
11965 hintRequested = bookRequested = FALSE;
11966 first.maybeThinking = FALSE;
11967 second.maybeThinking = FALSE;
11968 first.bookSuspend = FALSE; // [HGM] book
11969 second.bookSuspend = FALSE;
11970 thinkOutput[0] = NULLCHAR;
11971 lastHint[0] = NULLCHAR;
11972 ClearGameInfo(&gameInfo);
11973 gameInfo.variant = StringToVariant(appData.variant);
11974 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11975 ics_user_moved = ics_clock_paused = FALSE;
11976 ics_getting_history = H_FALSE;
11978 white_holding[0] = black_holding[0] = NULLCHAR;
11979 ClearProgramStats();
11980 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11984 flipView = appData.flipView;
11985 ClearPremoveHighlights();
11986 gotPremove = FALSE;
11987 alarmSounded = FALSE;
11988 killX = killY = kill2X = kill2Y = -1; // [HGM] lion
11990 GameEnds(EndOfFile, NULL, GE_PLAYER);
11991 if(appData.serverMovesName != NULL) {
11992 /* [HGM] prepare to make moves file for broadcasting */
11993 clock_t t = clock();
11994 if(serverMoves != NULL) fclose(serverMoves);
11995 serverMoves = fopen(appData.serverMovesName, "r");
11996 if(serverMoves != NULL) {
11997 fclose(serverMoves);
11998 /* delay 15 sec before overwriting, so all clients can see end */
11999 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12001 serverMoves = fopen(appData.serverMovesName, "w");
12005 gameMode = BeginningOfGame;
12007 if(appData.icsActive) gameInfo.variant = VariantNormal;
12008 currentMove = forwardMostMove = backwardMostMove = 0;
12009 MarkTargetSquares(1);
12010 InitPosition(redraw);
12011 for (i = 0; i < MAX_MOVES; i++) {
12012 if (commentList[i] != NULL) {
12013 free(commentList[i]);
12014 commentList[i] = NULL;
12018 timeRemaining[0][0] = whiteTimeRemaining;
12019 timeRemaining[1][0] = blackTimeRemaining;
12021 if (first.pr == NoProc) {
12022 StartChessProgram(&first);
12025 InitChessProgram(&first, startedFromSetupPosition);
12028 DisplayMessage("", "");
12029 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12030 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12031 ClearMap(); // [HGM] exclude: invalidate map
12035 AutoPlayGameLoop ()
12038 if (!AutoPlayOneMove())
12040 if (matchMode || appData.timeDelay == 0)
12042 if (appData.timeDelay < 0)
12044 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12052 ReloadGame(1); // next game
12058 int fromX, fromY, toX, toY;
12060 if (appData.debugMode) {
12061 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12064 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12067 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12068 pvInfoList[currentMove].depth = programStats.depth;
12069 pvInfoList[currentMove].score = programStats.score;
12070 pvInfoList[currentMove].time = 0;
12071 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12072 else { // append analysis of final position as comment
12074 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12075 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12077 programStats.depth = 0;
12080 if (currentMove >= forwardMostMove) {
12081 if(gameMode == AnalyzeFile) {
12082 if(appData.loadGameIndex == -1) {
12083 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12084 ScheduleDelayedEvent(AnalyzeNextGame, 10);
12086 ExitAnalyzeMode(); SendToProgram("force\n", &first);
12089 // gameMode = EndOfGame;
12090 // ModeHighlight();
12092 /* [AS] Clear current move marker at the end of a game */
12093 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12098 toX = moveList[currentMove][2] - AAA;
12099 toY = moveList[currentMove][3] - ONE;
12101 if (moveList[currentMove][1] == '@') {
12102 if (appData.highlightLastMove) {
12103 SetHighlights(-1, -1, toX, toY);
12106 fromX = moveList[currentMove][0] - AAA;
12107 fromY = moveList[currentMove][1] - ONE;
12109 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12111 if(moveList[currentMove][4] == ';') { // multi-leg
12112 killX = moveList[currentMove][5] - AAA;
12113 killY = moveList[currentMove][6] - ONE;
12115 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12116 killX = killY = -1;
12118 if (appData.highlightLastMove) {
12119 SetHighlights(fromX, fromY, toX, toY);
12122 DisplayMove(currentMove);
12123 SendMoveToProgram(currentMove++, &first);
12124 DisplayBothClocks();
12125 DrawPosition(FALSE, boards[currentMove]);
12126 // [HGM] PV info: always display, routine tests if empty
12127 DisplayComment(currentMove - 1, commentList[currentMove]);
12133 LoadGameOneMove (ChessMove readAhead)
12135 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12136 char promoChar = NULLCHAR;
12137 ChessMove moveType;
12138 char move[MSG_SIZ];
12141 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12142 gameMode != AnalyzeMode && gameMode != Training) {
12147 yyboardindex = forwardMostMove;
12148 if (readAhead != EndOfFile) {
12149 moveType = readAhead;
12151 if (gameFileFP == NULL)
12153 moveType = (ChessMove) Myylex();
12157 switch (moveType) {
12159 if (appData.debugMode)
12160 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12163 /* append the comment but don't display it */
12164 AppendComment(currentMove, p, FALSE);
12167 case WhiteCapturesEnPassant:
12168 case BlackCapturesEnPassant:
12169 case WhitePromotion:
12170 case BlackPromotion:
12171 case WhiteNonPromotion:
12172 case BlackNonPromotion:
12175 case WhiteKingSideCastle:
12176 case WhiteQueenSideCastle:
12177 case BlackKingSideCastle:
12178 case BlackQueenSideCastle:
12179 case WhiteKingSideCastleWild:
12180 case WhiteQueenSideCastleWild:
12181 case BlackKingSideCastleWild:
12182 case BlackQueenSideCastleWild:
12184 case WhiteHSideCastleFR:
12185 case WhiteASideCastleFR:
12186 case BlackHSideCastleFR:
12187 case BlackASideCastleFR:
12189 if (appData.debugMode)
12190 fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12191 fromX = currentMoveString[0] - AAA;
12192 fromY = currentMoveString[1] - ONE;
12193 toX = currentMoveString[2] - AAA;
12194 toY = currentMoveString[3] - ONE;
12195 promoChar = currentMoveString[4];
12196 if(promoChar == ';') promoChar = currentMoveString[7];
12201 if (appData.debugMode)
12202 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12203 fromX = moveType == WhiteDrop ?
12204 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12205 (int) CharToPiece(ToLower(currentMoveString[0]));
12207 toX = currentMoveString[2] - AAA;
12208 toY = currentMoveString[3] - ONE;
12214 case GameUnfinished:
12215 if (appData.debugMode)
12216 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12217 p = strchr(yy_text, '{');
12218 if (p == NULL) p = strchr(yy_text, '(');
12221 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12223 q = strchr(p, *p == '{' ? '}' : ')');
12224 if (q != NULL) *q = NULLCHAR;
12227 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12228 GameEnds(moveType, p, GE_FILE);
12230 if (cmailMsgLoaded) {
12232 flipView = WhiteOnMove(currentMove);
12233 if (moveType == GameUnfinished) flipView = !flipView;
12234 if (appData.debugMode)
12235 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12240 if (appData.debugMode)
12241 fprintf(debugFP, "Parser hit end of file\n");
12242 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12248 if (WhiteOnMove(currentMove)) {
12249 GameEnds(BlackWins, "Black mates", GE_FILE);
12251 GameEnds(WhiteWins, "White mates", GE_FILE);
12255 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12261 case MoveNumberOne:
12262 if (lastLoadGameStart == GNUChessGame) {
12263 /* GNUChessGames have numbers, but they aren't move numbers */
12264 if (appData.debugMode)
12265 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12266 yy_text, (int) moveType);
12267 return LoadGameOneMove(EndOfFile); /* tail recursion */
12269 /* else fall thru */
12274 /* Reached start of next game in file */
12275 if (appData.debugMode)
12276 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12277 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12283 if (WhiteOnMove(currentMove)) {
12284 GameEnds(BlackWins, "Black mates", GE_FILE);
12286 GameEnds(WhiteWins, "White mates", GE_FILE);
12290 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12296 case PositionDiagram: /* should not happen; ignore */
12297 case ElapsedTime: /* ignore */
12298 case NAG: /* ignore */
12299 if (appData.debugMode)
12300 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12301 yy_text, (int) moveType);
12302 return LoadGameOneMove(EndOfFile); /* tail recursion */
12305 if (appData.testLegality) {
12306 if (appData.debugMode)
12307 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12308 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12309 (forwardMostMove / 2) + 1,
12310 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12311 DisplayError(move, 0);
12314 if (appData.debugMode)
12315 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12316 yy_text, currentMoveString);
12317 if(currentMoveString[1] == '@') {
12318 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12321 fromX = currentMoveString[0] - AAA;
12322 fromY = currentMoveString[1] - ONE;
12324 toX = currentMoveString[2] - AAA;
12325 toY = currentMoveString[3] - ONE;
12326 promoChar = currentMoveString[4];
12330 case AmbiguousMove:
12331 if (appData.debugMode)
12332 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12333 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12334 (forwardMostMove / 2) + 1,
12335 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12336 DisplayError(move, 0);
12341 case ImpossibleMove:
12342 if (appData.debugMode)
12343 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12344 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12345 (forwardMostMove / 2) + 1,
12346 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12347 DisplayError(move, 0);
12353 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12354 DrawPosition(FALSE, boards[currentMove]);
12355 DisplayBothClocks();
12356 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12357 DisplayComment(currentMove - 1, commentList[currentMove]);
12359 (void) StopLoadGameTimer();
12361 cmailOldMove = forwardMostMove;
12364 /* currentMoveString is set as a side-effect of yylex */
12366 thinkOutput[0] = NULLCHAR;
12367 MakeMove(fromX, fromY, toX, toY, promoChar);
12368 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12369 currentMove = forwardMostMove;
12374 /* Load the nth game from the given file */
12376 LoadGameFromFile (char *filename, int n, char *title, int useList)
12381 if (strcmp(filename, "-") == 0) {
12385 f = fopen(filename, "rb");
12387 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12388 DisplayError(buf, errno);
12392 if (fseek(f, 0, 0) == -1) {
12393 /* f is not seekable; probably a pipe */
12396 if (useList && n == 0) {
12397 int error = GameListBuild(f);
12399 DisplayError(_("Cannot build game list"), error);
12400 } else if (!ListEmpty(&gameList) &&
12401 ((ListGame *) gameList.tailPred)->number > 1) {
12402 GameListPopUp(f, title);
12409 return LoadGame(f, n, title, FALSE);
12414 MakeRegisteredMove ()
12416 int fromX, fromY, toX, toY;
12418 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12419 switch (cmailMoveType[lastLoadGameNumber - 1]) {
12422 if (appData.debugMode)
12423 fprintf(debugFP, "Restoring %s for game %d\n",
12424 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12426 thinkOutput[0] = NULLCHAR;
12427 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12428 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12429 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12430 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12431 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12432 promoChar = cmailMove[lastLoadGameNumber - 1][4];
12433 MakeMove(fromX, fromY, toX, toY, promoChar);
12434 ShowMove(fromX, fromY, toX, toY);
12436 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12443 if (WhiteOnMove(currentMove)) {
12444 GameEnds(BlackWins, "Black mates", GE_PLAYER);
12446 GameEnds(WhiteWins, "White mates", GE_PLAYER);
12451 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12458 if (WhiteOnMove(currentMove)) {
12459 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12461 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12466 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12477 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12479 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12483 if (gameNumber > nCmailGames) {
12484 DisplayError(_("No more games in this message"), 0);
12487 if (f == lastLoadGameFP) {
12488 int offset = gameNumber - lastLoadGameNumber;
12490 cmailMsg[0] = NULLCHAR;
12491 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12492 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12493 nCmailMovesRegistered--;
12495 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12496 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12497 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12500 if (! RegisterMove()) return FALSE;
12504 retVal = LoadGame(f, gameNumber, title, useList);
12506 /* Make move registered during previous look at this game, if any */
12507 MakeRegisteredMove();
12509 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12510 commentList[currentMove]
12511 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12512 DisplayComment(currentMove - 1, commentList[currentMove]);
12518 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12520 ReloadGame (int offset)
12522 int gameNumber = lastLoadGameNumber + offset;
12523 if (lastLoadGameFP == NULL) {
12524 DisplayError(_("No game has been loaded yet"), 0);
12527 if (gameNumber <= 0) {
12528 DisplayError(_("Can't back up any further"), 0);
12531 if (cmailMsgLoaded) {
12532 return CmailLoadGame(lastLoadGameFP, gameNumber,
12533 lastLoadGameTitle, lastLoadGameUseList);
12535 return LoadGame(lastLoadGameFP, gameNumber,
12536 lastLoadGameTitle, lastLoadGameUseList);
12540 int keys[EmptySquare+1];
12543 PositionMatches (Board b1, Board b2)
12546 switch(appData.searchMode) {
12547 case 1: return CompareWithRights(b1, b2);
12549 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12550 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12554 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12555 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12556 sum += keys[b1[r][f]] - keys[b2[r][f]];
12560 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12561 sum += keys[b1[r][f]] - keys[b2[r][f]];
12573 int pieceList[256], quickBoard[256];
12574 ChessSquare pieceType[256] = { EmptySquare };
12575 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12576 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12577 int soughtTotal, turn;
12578 Boolean epOK, flipSearch;
12581 unsigned char piece, to;
12584 #define DSIZE (250000)
12586 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12587 Move *moveDatabase = initialSpace;
12588 unsigned int movePtr, dataSize = DSIZE;
12591 MakePieceList (Board board, int *counts)
12593 int r, f, n=Q_PROMO, total=0;
12594 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12595 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12596 int sq = f + (r<<4);
12597 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12598 quickBoard[sq] = ++n;
12600 pieceType[n] = board[r][f];
12601 counts[board[r][f]]++;
12602 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12603 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12607 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12612 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12614 int sq = fromX + (fromY<<4);
12615 int piece = quickBoard[sq], rook;
12616 quickBoard[sq] = 0;
12617 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12618 if(piece == pieceList[1] && fromY == toY) {
12619 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12620 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12621 moveDatabase[movePtr++].piece = Q_WCASTL;
12622 quickBoard[sq] = piece;
12623 piece = quickBoard[from]; quickBoard[from] = 0;
12624 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12625 } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12626 quickBoard[sq] = 0; // remove Rook
12627 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12628 moveDatabase[movePtr++].piece = Q_WCASTL;
12629 quickBoard[sq] = pieceList[1]; // put King
12631 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12634 if(piece == pieceList[2] && fromY == toY) {
12635 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12636 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12637 moveDatabase[movePtr++].piece = Q_BCASTL;
12638 quickBoard[sq] = piece;
12639 piece = quickBoard[from]; quickBoard[from] = 0;
12640 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12641 } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12642 quickBoard[sq] = 0; // remove Rook
12643 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12644 moveDatabase[movePtr++].piece = Q_BCASTL;
12645 quickBoard[sq] = pieceList[2]; // put King
12647 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12650 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12651 quickBoard[(fromY<<4)+toX] = 0;
12652 moveDatabase[movePtr].piece = Q_EP;
12653 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12654 moveDatabase[movePtr].to = sq;
12656 if(promoPiece != pieceType[piece]) {
12657 moveDatabase[movePtr++].piece = Q_PROMO;
12658 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12660 moveDatabase[movePtr].piece = piece;
12661 quickBoard[sq] = piece;
12666 PackGame (Board board)
12668 Move *newSpace = NULL;
12669 moveDatabase[movePtr].piece = 0; // terminate previous game
12670 if(movePtr > dataSize) {
12671 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12672 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12673 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12676 Move *p = moveDatabase, *q = newSpace;
12677 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12678 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12679 moveDatabase = newSpace;
12680 } else { // calloc failed, we must be out of memory. Too bad...
12681 dataSize = 0; // prevent calloc events for all subsequent games
12682 return 0; // and signal this one isn't cached
12686 MakePieceList(board, counts);
12691 QuickCompare (Board board, int *minCounts, int *maxCounts)
12692 { // compare according to search mode
12694 switch(appData.searchMode)
12696 case 1: // exact position match
12697 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12698 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12699 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12702 case 2: // can have extra material on empty squares
12703 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12704 if(board[r][f] == EmptySquare) continue;
12705 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12708 case 3: // material with exact Pawn structure
12709 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12710 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12711 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12712 } // fall through to material comparison
12713 case 4: // exact material
12714 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12716 case 6: // material range with given imbalance
12717 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12718 // fall through to range comparison
12719 case 5: // material range
12720 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12726 QuickScan (Board board, Move *move)
12727 { // reconstruct game,and compare all positions in it
12728 int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12730 int piece = move->piece;
12731 int to = move->to, from = pieceList[piece];
12732 if(found < 0) { // if already found just scan to game end for final piece count
12733 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12734 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12735 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12736 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12738 static int lastCounts[EmptySquare+1];
12740 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12741 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12742 } else stretch = 0;
12743 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12744 if(found >= 0 && !appData.minPieces) return found;
12746 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12747 if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12748 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12749 piece = (++move)->piece;
12750 from = pieceList[piece];
12751 counts[pieceType[piece]]--;
12752 pieceType[piece] = (ChessSquare) move->to;
12753 counts[move->to]++;
12754 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12755 counts[pieceType[quickBoard[to]]]--;
12756 quickBoard[to] = 0; total--;
12759 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12760 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12761 from = pieceList[piece]; // so this must be King
12762 quickBoard[from] = 0;
12763 pieceList[piece] = to;
12764 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12765 quickBoard[from] = 0; // rook
12766 quickBoard[to] = piece;
12767 to = move->to; piece = move->piece;
12771 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12772 if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12773 quickBoard[from] = 0;
12775 quickBoard[to] = piece;
12776 pieceList[piece] = to;
12786 flipSearch = FALSE;
12787 CopyBoard(soughtBoard, boards[currentMove]);
12788 soughtTotal = MakePieceList(soughtBoard, maxSought);
12789 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12790 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12791 CopyBoard(reverseBoard, boards[currentMove]);
12792 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12793 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12794 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12795 reverseBoard[r][f] = piece;
12797 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12798 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12799 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12800 || (boards[currentMove][CASTLING][2] == NoRights ||
12801 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12802 && (boards[currentMove][CASTLING][5] == NoRights ||
12803 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12806 CopyBoard(flipBoard, soughtBoard);
12807 CopyBoard(rotateBoard, reverseBoard);
12808 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12809 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12810 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12813 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12814 if(appData.searchMode >= 5) {
12815 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12816 MakePieceList(soughtBoard, minSought);
12817 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12819 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12820 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12823 GameInfo dummyInfo;
12824 static int creatingBook;
12827 GameContainsPosition (FILE *f, ListGame *lg)
12829 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12830 int fromX, fromY, toX, toY;
12832 static int initDone=FALSE;
12834 // weed out games based on numerical tag comparison
12835 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12836 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12837 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12838 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12840 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12843 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12844 else CopyBoard(boards[scratch], initialPosition); // default start position
12847 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12848 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12851 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12852 fseek(f, lg->offset, 0);
12855 yyboardindex = scratch;
12856 quickFlag = plyNr+1;
12861 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12867 if(plyNr) return -1; // after we have seen moves, this is for new game
12870 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12871 case ImpossibleMove:
12872 case WhiteWins: // game ends here with these four
12875 case GameUnfinished:
12879 if(appData.testLegality) return -1;
12880 case WhiteCapturesEnPassant:
12881 case BlackCapturesEnPassant:
12882 case WhitePromotion:
12883 case BlackPromotion:
12884 case WhiteNonPromotion:
12885 case BlackNonPromotion:
12888 case WhiteKingSideCastle:
12889 case WhiteQueenSideCastle:
12890 case BlackKingSideCastle:
12891 case BlackQueenSideCastle:
12892 case WhiteKingSideCastleWild:
12893 case WhiteQueenSideCastleWild:
12894 case BlackKingSideCastleWild:
12895 case BlackQueenSideCastleWild:
12896 case WhiteHSideCastleFR:
12897 case WhiteASideCastleFR:
12898 case BlackHSideCastleFR:
12899 case BlackASideCastleFR:
12900 fromX = currentMoveString[0] - AAA;
12901 fromY = currentMoveString[1] - ONE;
12902 toX = currentMoveString[2] - AAA;
12903 toY = currentMoveString[3] - ONE;
12904 promoChar = currentMoveString[4];
12908 fromX = next == WhiteDrop ?
12909 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12910 (int) CharToPiece(ToLower(currentMoveString[0]));
12912 toX = currentMoveString[2] - AAA;
12913 toY = currentMoveString[3] - ONE;
12917 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12919 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12920 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12921 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12922 if(appData.findMirror) {
12923 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12924 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12929 /* Load the nth game from open file f */
12931 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12935 int gn = gameNumber;
12936 ListGame *lg = NULL;
12937 int numPGNTags = 0, i;
12939 GameMode oldGameMode;
12940 VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12941 char oldName[MSG_SIZ];
12943 safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12945 if (appData.debugMode)
12946 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12948 if (gameMode == Training )
12949 SetTrainingModeOff();
12951 oldGameMode = gameMode;
12952 if (gameMode != BeginningOfGame) {
12953 Reset(FALSE, TRUE);
12955 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
12958 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12959 fclose(lastLoadGameFP);
12963 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12966 fseek(f, lg->offset, 0);
12967 GameListHighlight(gameNumber);
12968 pos = lg->position;
12972 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12973 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12975 DisplayError(_("Game number out of range"), 0);
12980 if (fseek(f, 0, 0) == -1) {
12981 if (f == lastLoadGameFP ?
12982 gameNumber == lastLoadGameNumber + 1 :
12986 DisplayError(_("Can't seek on game file"), 0);
12991 lastLoadGameFP = f;
12992 lastLoadGameNumber = gameNumber;
12993 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12994 lastLoadGameUseList = useList;
12998 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12999 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13000 lg->gameInfo.black);
13002 } else if (*title != NULLCHAR) {
13003 if (gameNumber > 1) {
13004 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13007 DisplayTitle(title);
13011 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13012 gameMode = PlayFromGameFile;
13016 currentMove = forwardMostMove = backwardMostMove = 0;
13017 CopyBoard(boards[0], initialPosition);
13021 * Skip the first gn-1 games in the file.
13022 * Also skip over anything that precedes an identifiable
13023 * start of game marker, to avoid being confused by
13024 * garbage at the start of the file. Currently
13025 * recognized start of game markers are the move number "1",
13026 * the pattern "gnuchess .* game", the pattern
13027 * "^[#;%] [^ ]* game file", and a PGN tag block.
13028 * A game that starts with one of the latter two patterns
13029 * will also have a move number 1, possibly
13030 * following a position diagram.
13031 * 5-4-02: Let's try being more lenient and allowing a game to
13032 * start with an unnumbered move. Does that break anything?
13034 cm = lastLoadGameStart = EndOfFile;
13036 yyboardindex = forwardMostMove;
13037 cm = (ChessMove) Myylex();
13040 if (cmailMsgLoaded) {
13041 nCmailGames = CMAIL_MAX_GAMES - gn;
13044 DisplayError(_("Game not found in file"), 0);
13051 lastLoadGameStart = cm;
13054 case MoveNumberOne:
13055 switch (lastLoadGameStart) {
13060 case MoveNumberOne:
13062 gn--; /* count this game */
13063 lastLoadGameStart = cm;
13072 switch (lastLoadGameStart) {
13075 case MoveNumberOne:
13077 gn--; /* count this game */
13078 lastLoadGameStart = cm;
13081 lastLoadGameStart = cm; /* game counted already */
13089 yyboardindex = forwardMostMove;
13090 cm = (ChessMove) Myylex();
13091 } while (cm == PGNTag || cm == Comment);
13098 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13099 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
13100 != CMAIL_OLD_RESULT) {
13102 cmailResult[ CMAIL_MAX_GAMES
13103 - gn - 1] = CMAIL_OLD_RESULT;
13110 /* Only a NormalMove can be at the start of a game
13111 * without a position diagram. */
13112 if (lastLoadGameStart == EndOfFile ) {
13114 lastLoadGameStart = MoveNumberOne;
13123 if (appData.debugMode)
13124 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13126 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13128 if (cm == XBoardGame) {
13129 /* Skip any header junk before position diagram and/or move 1 */
13131 yyboardindex = forwardMostMove;
13132 cm = (ChessMove) Myylex();
13134 if (cm == EndOfFile ||
13135 cm == GNUChessGame || cm == XBoardGame) {
13136 /* Empty game; pretend end-of-file and handle later */
13141 if (cm == MoveNumberOne || cm == PositionDiagram ||
13142 cm == PGNTag || cm == Comment)
13145 } else if (cm == GNUChessGame) {
13146 if (gameInfo.event != NULL) {
13147 free(gameInfo.event);
13149 gameInfo.event = StrSave(yy_text);
13152 startedFromSetupPosition = startedFromPositionFile; // [HGM]
13153 while (cm == PGNTag) {
13154 if (appData.debugMode)
13155 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13156 err = ParsePGNTag(yy_text, &gameInfo);
13157 if (!err) numPGNTags++;
13159 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13160 if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13161 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13162 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13163 InitPosition(TRUE);
13164 oldVariant = gameInfo.variant;
13165 if (appData.debugMode)
13166 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13170 if (gameInfo.fen != NULL) {
13171 Board initial_position;
13172 startedFromSetupPosition = TRUE;
13173 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13175 DisplayError(_("Bad FEN position in file"), 0);
13178 CopyBoard(boards[0], initial_position);
13179 if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13180 CopyBoard(initialPosition, initial_position);
13181 if (blackPlaysFirst) {
13182 currentMove = forwardMostMove = backwardMostMove = 1;
13183 CopyBoard(boards[1], initial_position);
13184 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13185 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13186 timeRemaining[0][1] = whiteTimeRemaining;
13187 timeRemaining[1][1] = blackTimeRemaining;
13188 if (commentList[0] != NULL) {
13189 commentList[1] = commentList[0];
13190 commentList[0] = NULL;
13193 currentMove = forwardMostMove = backwardMostMove = 0;
13195 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13197 initialRulePlies = FENrulePlies;
13198 for( i=0; i< nrCastlingRights; i++ )
13199 initialRights[i] = initial_position[CASTLING][i];
13201 yyboardindex = forwardMostMove;
13202 free(gameInfo.fen);
13203 gameInfo.fen = NULL;
13206 yyboardindex = forwardMostMove;
13207 cm = (ChessMove) Myylex();
13209 /* Handle comments interspersed among the tags */
13210 while (cm == Comment) {
13212 if (appData.debugMode)
13213 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13215 AppendComment(currentMove, p, FALSE);
13216 yyboardindex = forwardMostMove;
13217 cm = (ChessMove) Myylex();
13221 /* don't rely on existence of Event tag since if game was
13222 * pasted from clipboard the Event tag may not exist
13224 if (numPGNTags > 0){
13226 if (gameInfo.variant == VariantNormal) {
13227 VariantClass v = StringToVariant(gameInfo.event);
13228 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13229 if(v < VariantShogi) gameInfo.variant = v;
13232 if( appData.autoDisplayTags ) {
13233 tags = PGNTags(&gameInfo);
13234 TagsPopUp(tags, CmailMsg());
13239 /* Make something up, but don't display it now */
13244 if (cm == PositionDiagram) {
13247 Board initial_position;
13249 if (appData.debugMode)
13250 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13252 if (!startedFromSetupPosition) {
13254 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13255 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13266 initial_position[i][j++] = CharToPiece(*p);
13269 while (*p == ' ' || *p == '\t' ||
13270 *p == '\n' || *p == '\r') p++;
13272 if (strncmp(p, "black", strlen("black"))==0)
13273 blackPlaysFirst = TRUE;
13275 blackPlaysFirst = FALSE;
13276 startedFromSetupPosition = TRUE;
13278 CopyBoard(boards[0], initial_position);
13279 if (blackPlaysFirst) {
13280 currentMove = forwardMostMove = backwardMostMove = 1;
13281 CopyBoard(boards[1], initial_position);
13282 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13283 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13284 timeRemaining[0][1] = whiteTimeRemaining;
13285 timeRemaining[1][1] = blackTimeRemaining;
13286 if (commentList[0] != NULL) {
13287 commentList[1] = commentList[0];
13288 commentList[0] = NULL;
13291 currentMove = forwardMostMove = backwardMostMove = 0;
13294 yyboardindex = forwardMostMove;
13295 cm = (ChessMove) Myylex();
13298 if(!creatingBook) {
13299 if (first.pr == NoProc) {
13300 StartChessProgram(&first);
13302 InitChessProgram(&first, FALSE);
13303 if(gameInfo.variant == VariantUnknown && *oldName) {
13304 safeStrCpy(engineVariant, oldName, MSG_SIZ);
13305 gameInfo.variant = v;
13307 SendToProgram("force\n", &first);
13308 if (startedFromSetupPosition) {
13309 SendBoard(&first, forwardMostMove);
13310 if (appData.debugMode) {
13311 fprintf(debugFP, "Load Game\n");
13313 DisplayBothClocks();
13317 /* [HGM] server: flag to write setup moves in broadcast file as one */
13318 loadFlag = appData.suppressLoadMoves;
13320 while (cm == Comment) {
13322 if (appData.debugMode)
13323 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13325 AppendComment(currentMove, p, FALSE);
13326 yyboardindex = forwardMostMove;
13327 cm = (ChessMove) Myylex();
13330 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13331 cm == WhiteWins || cm == BlackWins ||
13332 cm == GameIsDrawn || cm == GameUnfinished) {
13333 DisplayMessage("", _("No moves in game"));
13334 if (cmailMsgLoaded) {
13335 if (appData.debugMode)
13336 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13340 DrawPosition(FALSE, boards[currentMove]);
13341 DisplayBothClocks();
13342 gameMode = EditGame;
13349 // [HGM] PV info: routine tests if comment empty
13350 if (!matchMode && (pausing || appData.timeDelay != 0)) {
13351 DisplayComment(currentMove - 1, commentList[currentMove]);
13353 if (!matchMode && appData.timeDelay != 0)
13354 DrawPosition(FALSE, boards[currentMove]);
13356 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13357 programStats.ok_to_send = 1;
13360 /* if the first token after the PGN tags is a move
13361 * and not move number 1, retrieve it from the parser
13363 if (cm != MoveNumberOne)
13364 LoadGameOneMove(cm);
13366 /* load the remaining moves from the file */
13367 while (LoadGameOneMove(EndOfFile)) {
13368 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13369 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13372 /* rewind to the start of the game */
13373 currentMove = backwardMostMove;
13375 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13377 if (oldGameMode == AnalyzeFile) {
13378 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13379 AnalyzeFileEvent();
13381 if (oldGameMode == AnalyzeMode) {
13382 AnalyzeFileEvent();
13385 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13386 long int w, b; // [HGM] adjourn: restore saved clock times
13387 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13388 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13389 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13390 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13394 if(creatingBook) return TRUE;
13395 if (!matchMode && pos > 0) {
13396 ToNrEvent(pos); // [HGM] no autoplay if selected on position
13398 if (matchMode || appData.timeDelay == 0) {
13400 } else if (appData.timeDelay > 0) {
13401 AutoPlayGameLoop();
13404 if (appData.debugMode)
13405 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13407 loadFlag = 0; /* [HGM] true game starts */
13411 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13413 ReloadPosition (int offset)
13415 int positionNumber = lastLoadPositionNumber + offset;
13416 if (lastLoadPositionFP == NULL) {
13417 DisplayError(_("No position has been loaded yet"), 0);
13420 if (positionNumber <= 0) {
13421 DisplayError(_("Can't back up any further"), 0);
13424 return LoadPosition(lastLoadPositionFP, positionNumber,
13425 lastLoadPositionTitle);
13428 /* Load the nth position from the given file */
13430 LoadPositionFromFile (char *filename, int n, char *title)
13435 if (strcmp(filename, "-") == 0) {
13436 return LoadPosition(stdin, n, "stdin");
13438 f = fopen(filename, "rb");
13440 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13441 DisplayError(buf, errno);
13444 return LoadPosition(f, n, title);
13449 /* Load the nth position from the given open file, and close it */
13451 LoadPosition (FILE *f, int positionNumber, char *title)
13453 char *p, line[MSG_SIZ];
13454 Board initial_position;
13455 int i, j, fenMode, pn;
13457 if (gameMode == Training )
13458 SetTrainingModeOff();
13460 if (gameMode != BeginningOfGame) {
13461 Reset(FALSE, TRUE);
13463 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13464 fclose(lastLoadPositionFP);
13466 if (positionNumber == 0) positionNumber = 1;
13467 lastLoadPositionFP = f;
13468 lastLoadPositionNumber = positionNumber;
13469 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13470 if (first.pr == NoProc && !appData.noChessProgram) {
13471 StartChessProgram(&first);
13472 InitChessProgram(&first, FALSE);
13474 pn = positionNumber;
13475 if (positionNumber < 0) {
13476 /* Negative position number means to seek to that byte offset */
13477 if (fseek(f, -positionNumber, 0) == -1) {
13478 DisplayError(_("Can't seek on position file"), 0);
13483 if (fseek(f, 0, 0) == -1) {
13484 if (f == lastLoadPositionFP ?
13485 positionNumber == lastLoadPositionNumber + 1 :
13486 positionNumber == 1) {
13489 DisplayError(_("Can't seek on position file"), 0);
13494 /* See if this file is FEN or old-style xboard */
13495 if (fgets(line, MSG_SIZ, f) == NULL) {
13496 DisplayError(_("Position not found in file"), 0);
13499 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13500 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13503 if (fenMode || line[0] == '#') pn--;
13505 /* skip positions before number pn */
13506 if (fgets(line, MSG_SIZ, f) == NULL) {
13508 DisplayError(_("Position not found in file"), 0);
13511 if (fenMode || line[0] == '#') pn--;
13517 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13518 DisplayError(_("Bad FEN position in file"), 0);
13521 if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13522 sscanf(p+3, "%s", bestMove);
13523 } else *bestMove = NULLCHAR;
13525 (void) fgets(line, MSG_SIZ, f);
13526 (void) fgets(line, MSG_SIZ, f);
13528 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13529 (void) fgets(line, MSG_SIZ, f);
13530 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13533 initial_position[i][j++] = CharToPiece(*p);
13537 blackPlaysFirst = FALSE;
13539 (void) fgets(line, MSG_SIZ, f);
13540 if (strncmp(line, "black", strlen("black"))==0)
13541 blackPlaysFirst = TRUE;
13544 startedFromSetupPosition = TRUE;
13546 CopyBoard(boards[0], initial_position);
13547 if (blackPlaysFirst) {
13548 currentMove = forwardMostMove = backwardMostMove = 1;
13549 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13550 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13551 CopyBoard(boards[1], initial_position);
13552 DisplayMessage("", _("Black to play"));
13554 currentMove = forwardMostMove = backwardMostMove = 0;
13555 DisplayMessage("", _("White to play"));
13557 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13558 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13559 SendToProgram("force\n", &first);
13560 SendBoard(&first, forwardMostMove);
13562 if (appData.debugMode) {
13564 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13565 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13566 fprintf(debugFP, "Load Position\n");
13569 if (positionNumber > 1) {
13570 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13571 DisplayTitle(line);
13573 DisplayTitle(title);
13575 gameMode = EditGame;
13578 timeRemaining[0][1] = whiteTimeRemaining;
13579 timeRemaining[1][1] = blackTimeRemaining;
13580 DrawPosition(FALSE, boards[currentMove]);
13587 CopyPlayerNameIntoFileName (char **dest, char *src)
13589 while (*src != NULLCHAR && *src != ',') {
13594 *(*dest)++ = *src++;
13600 DefaultFileName (char *ext)
13602 static char def[MSG_SIZ];
13605 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13607 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13609 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13611 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13618 /* Save the current game to the given file */
13620 SaveGameToFile (char *filename, int append)
13624 int result, i, t,tot=0;
13626 if (strcmp(filename, "-") == 0) {
13627 return SaveGame(stdout, 0, NULL);
13629 for(i=0; i<10; i++) { // upto 10 tries
13630 f = fopen(filename, append ? "a" : "w");
13631 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13632 if(f || errno != 13) break;
13633 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13637 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13638 DisplayError(buf, errno);
13641 safeStrCpy(buf, lastMsg, MSG_SIZ);
13642 DisplayMessage(_("Waiting for access to save file"), "");
13643 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13644 DisplayMessage(_("Saving game"), "");
13645 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13646 result = SaveGame(f, 0, NULL);
13647 DisplayMessage(buf, "");
13654 SavePart (char *str)
13656 static char buf[MSG_SIZ];
13659 p = strchr(str, ' ');
13660 if (p == NULL) return str;
13661 strncpy(buf, str, p - str);
13662 buf[p - str] = NULLCHAR;
13666 #define PGN_MAX_LINE 75
13668 #define PGN_SIDE_WHITE 0
13669 #define PGN_SIDE_BLACK 1
13672 FindFirstMoveOutOfBook (int side)
13676 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13677 int index = backwardMostMove;
13678 int has_book_hit = 0;
13680 if( (index % 2) != side ) {
13684 while( index < forwardMostMove ) {
13685 /* Check to see if engine is in book */
13686 int depth = pvInfoList[index].depth;
13687 int score = pvInfoList[index].score;
13693 else if( score == 0 && depth == 63 ) {
13694 in_book = 1; /* Zappa */
13696 else if( score == 2 && depth == 99 ) {
13697 in_book = 1; /* Abrok */
13700 has_book_hit += in_book;
13716 GetOutOfBookInfo (char * buf)
13720 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13722 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13723 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13727 if( oob[0] >= 0 || oob[1] >= 0 ) {
13728 for( i=0; i<2; i++ ) {
13732 if( i > 0 && oob[0] >= 0 ) {
13733 strcat( buf, " " );
13736 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13737 sprintf( buf+strlen(buf), "%s%.2f",
13738 pvInfoList[idx].score >= 0 ? "+" : "",
13739 pvInfoList[idx].score / 100.0 );
13745 /* Save game in PGN style */
13747 SaveGamePGN2 (FILE *f)
13749 int i, offset, linelen, newblock;
13752 int movelen, numlen, blank;
13753 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13755 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13757 PrintPGNTags(f, &gameInfo);
13759 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13761 if (backwardMostMove > 0 || startedFromSetupPosition) {
13762 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13763 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13764 fprintf(f, "\n{--------------\n");
13765 PrintPosition(f, backwardMostMove);
13766 fprintf(f, "--------------}\n");
13770 /* [AS] Out of book annotation */
13771 if( appData.saveOutOfBookInfo ) {
13774 GetOutOfBookInfo( buf );
13776 if( buf[0] != '\0' ) {
13777 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13784 i = backwardMostMove;
13788 while (i < forwardMostMove) {
13789 /* Print comments preceding this move */
13790 if (commentList[i] != NULL) {
13791 if (linelen > 0) fprintf(f, "\n");
13792 fprintf(f, "%s", commentList[i]);
13797 /* Format move number */
13799 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13802 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13804 numtext[0] = NULLCHAR;
13806 numlen = strlen(numtext);
13809 /* Print move number */
13810 blank = linelen > 0 && numlen > 0;
13811 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13820 fprintf(f, "%s", numtext);
13824 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13825 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13828 blank = linelen > 0 && movelen > 0;
13829 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13838 fprintf(f, "%s", move_buffer);
13839 linelen += movelen;
13841 /* [AS] Add PV info if present */
13842 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13843 /* [HGM] add time */
13844 char buf[MSG_SIZ]; int seconds;
13846 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13852 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13855 seconds = (seconds + 4)/10; // round to full seconds
13857 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13859 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13862 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13863 pvInfoList[i].score >= 0 ? "+" : "",
13864 pvInfoList[i].score / 100.0,
13865 pvInfoList[i].depth,
13868 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13870 /* Print score/depth */
13871 blank = linelen > 0 && movelen > 0;
13872 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13881 fprintf(f, "%s", move_buffer);
13882 linelen += movelen;
13888 /* Start a new line */
13889 if (linelen > 0) fprintf(f, "\n");
13891 /* Print comments after last move */
13892 if (commentList[i] != NULL) {
13893 fprintf(f, "%s\n", commentList[i]);
13897 if (gameInfo.resultDetails != NULL &&
13898 gameInfo.resultDetails[0] != NULLCHAR) {
13899 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13900 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13901 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13902 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13903 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13905 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13909 /* Save game in PGN style and close the file */
13911 SaveGamePGN (FILE *f)
13915 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13919 /* Save game in old style and close the file */
13921 SaveGameOldStyle (FILE *f)
13926 tm = time((time_t *) NULL);
13928 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13931 if (backwardMostMove > 0 || startedFromSetupPosition) {
13932 fprintf(f, "\n[--------------\n");
13933 PrintPosition(f, backwardMostMove);
13934 fprintf(f, "--------------]\n");
13939 i = backwardMostMove;
13940 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13942 while (i < forwardMostMove) {
13943 if (commentList[i] != NULL) {
13944 fprintf(f, "[%s]\n", commentList[i]);
13947 if ((i % 2) == 1) {
13948 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13951 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13953 if (commentList[i] != NULL) {
13957 if (i >= forwardMostMove) {
13961 fprintf(f, "%s\n", parseList[i]);
13966 if (commentList[i] != NULL) {
13967 fprintf(f, "[%s]\n", commentList[i]);
13970 /* This isn't really the old style, but it's close enough */
13971 if (gameInfo.resultDetails != NULL &&
13972 gameInfo.resultDetails[0] != NULLCHAR) {
13973 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13974 gameInfo.resultDetails);
13976 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13983 /* Save the current game to open file f and close the file */
13985 SaveGame (FILE *f, int dummy, char *dummy2)
13987 if (gameMode == EditPosition) EditPositionDone(TRUE);
13988 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13989 if (appData.oldSaveStyle)
13990 return SaveGameOldStyle(f);
13992 return SaveGamePGN(f);
13995 /* Save the current position to the given file */
13997 SavePositionToFile (char *filename)
14002 if (strcmp(filename, "-") == 0) {
14003 return SavePosition(stdout, 0, NULL);
14005 f = fopen(filename, "a");
14007 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14008 DisplayError(buf, errno);
14011 safeStrCpy(buf, lastMsg, MSG_SIZ);
14012 DisplayMessage(_("Waiting for access to save file"), "");
14013 flock(fileno(f), LOCK_EX); // [HGM] lock
14014 DisplayMessage(_("Saving position"), "");
14015 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
14016 SavePosition(f, 0, NULL);
14017 DisplayMessage(buf, "");
14023 /* Save the current position to the given open file and close the file */
14025 SavePosition (FILE *f, int dummy, char *dummy2)
14030 if (gameMode == EditPosition) EditPositionDone(TRUE);
14031 if (appData.oldSaveStyle) {
14032 tm = time((time_t *) NULL);
14034 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14036 fprintf(f, "[--------------\n");
14037 PrintPosition(f, currentMove);
14038 fprintf(f, "--------------]\n");
14040 fen = PositionToFEN(currentMove, NULL, 1);
14041 fprintf(f, "%s\n", fen);
14049 ReloadCmailMsgEvent (int unregister)
14052 static char *inFilename = NULL;
14053 static char *outFilename;
14055 struct stat inbuf, outbuf;
14058 /* Any registered moves are unregistered if unregister is set, */
14059 /* i.e. invoked by the signal handler */
14061 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14062 cmailMoveRegistered[i] = FALSE;
14063 if (cmailCommentList[i] != NULL) {
14064 free(cmailCommentList[i]);
14065 cmailCommentList[i] = NULL;
14068 nCmailMovesRegistered = 0;
14071 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14072 cmailResult[i] = CMAIL_NOT_RESULT;
14076 if (inFilename == NULL) {
14077 /* Because the filenames are static they only get malloced once */
14078 /* and they never get freed */
14079 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14080 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14082 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14083 sprintf(outFilename, "%s.out", appData.cmailGameName);
14086 status = stat(outFilename, &outbuf);
14088 cmailMailedMove = FALSE;
14090 status = stat(inFilename, &inbuf);
14091 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14094 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14095 counts the games, notes how each one terminated, etc.
14097 It would be nice to remove this kludge and instead gather all
14098 the information while building the game list. (And to keep it
14099 in the game list nodes instead of having a bunch of fixed-size
14100 parallel arrays.) Note this will require getting each game's
14101 termination from the PGN tags, as the game list builder does
14102 not process the game moves. --mann
14104 cmailMsgLoaded = TRUE;
14105 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14107 /* Load first game in the file or popup game menu */
14108 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14110 #endif /* !WIN32 */
14118 char string[MSG_SIZ];
14120 if ( cmailMailedMove
14121 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14122 return TRUE; /* Allow free viewing */
14125 /* Unregister move to ensure that we don't leave RegisterMove */
14126 /* with the move registered when the conditions for registering no */
14128 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14129 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14130 nCmailMovesRegistered --;
14132 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14134 free(cmailCommentList[lastLoadGameNumber - 1]);
14135 cmailCommentList[lastLoadGameNumber - 1] = NULL;
14139 if (cmailOldMove == -1) {
14140 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14144 if (currentMove > cmailOldMove + 1) {
14145 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14149 if (currentMove < cmailOldMove) {
14150 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14154 if (forwardMostMove > currentMove) {
14155 /* Silently truncate extra moves */
14159 if ( (currentMove == cmailOldMove + 1)
14160 || ( (currentMove == cmailOldMove)
14161 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14162 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14163 if (gameInfo.result != GameUnfinished) {
14164 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14167 if (commentList[currentMove] != NULL) {
14168 cmailCommentList[lastLoadGameNumber - 1]
14169 = StrSave(commentList[currentMove]);
14171 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14173 if (appData.debugMode)
14174 fprintf(debugFP, "Saving %s for game %d\n",
14175 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14177 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14179 f = fopen(string, "w");
14180 if (appData.oldSaveStyle) {
14181 SaveGameOldStyle(f); /* also closes the file */
14183 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14184 f = fopen(string, "w");
14185 SavePosition(f, 0, NULL); /* also closes the file */
14187 fprintf(f, "{--------------\n");
14188 PrintPosition(f, currentMove);
14189 fprintf(f, "--------------}\n\n");
14191 SaveGame(f, 0, NULL); /* also closes the file*/
14194 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14195 nCmailMovesRegistered ++;
14196 } else if (nCmailGames == 1) {
14197 DisplayError(_("You have not made a move yet"), 0);
14208 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14209 FILE *commandOutput;
14210 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14211 int nBytes = 0; /* Suppress warnings on uninitialized variables */
14217 if (! cmailMsgLoaded) {
14218 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14222 if (nCmailGames == nCmailResults) {
14223 DisplayError(_("No unfinished games"), 0);
14227 #if CMAIL_PROHIBIT_REMAIL
14228 if (cmailMailedMove) {
14229 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);
14230 DisplayError(msg, 0);
14235 if (! (cmailMailedMove || RegisterMove())) return;
14237 if ( cmailMailedMove
14238 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14239 snprintf(string, MSG_SIZ, partCommandString,
14240 appData.debugMode ? " -v" : "", appData.cmailGameName);
14241 commandOutput = popen(string, "r");
14243 if (commandOutput == NULL) {
14244 DisplayError(_("Failed to invoke cmail"), 0);
14246 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14247 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14249 if (nBuffers > 1) {
14250 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14251 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14252 nBytes = MSG_SIZ - 1;
14254 (void) memcpy(msg, buffer, nBytes);
14256 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14258 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14259 cmailMailedMove = TRUE; /* Prevent >1 moves */
14262 for (i = 0; i < nCmailGames; i ++) {
14263 if (cmailResult[i] == CMAIL_NOT_RESULT) {
14268 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14270 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14272 appData.cmailGameName,
14274 LoadGameFromFile(buffer, 1, buffer, FALSE);
14275 cmailMsgLoaded = FALSE;
14279 DisplayInformation(msg);
14280 pclose(commandOutput);
14283 if ((*cmailMsg) != '\0') {
14284 DisplayInformation(cmailMsg);
14289 #endif /* !WIN32 */
14298 int prependComma = 0;
14300 char string[MSG_SIZ]; /* Space for game-list */
14303 if (!cmailMsgLoaded) return "";
14305 if (cmailMailedMove) {
14306 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14308 /* Create a list of games left */
14309 snprintf(string, MSG_SIZ, "[");
14310 for (i = 0; i < nCmailGames; i ++) {
14311 if (! ( cmailMoveRegistered[i]
14312 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14313 if (prependComma) {
14314 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14316 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14320 strcat(string, number);
14323 strcat(string, "]");
14325 if (nCmailMovesRegistered + nCmailResults == 0) {
14326 switch (nCmailGames) {
14328 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14332 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14336 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14341 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14343 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14348 if (nCmailResults == nCmailGames) {
14349 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14351 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14356 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14368 if (gameMode == Training)
14369 SetTrainingModeOff();
14372 cmailMsgLoaded = FALSE;
14373 if (appData.icsActive) {
14374 SendToICS(ics_prefix);
14375 SendToICS("refresh\n");
14380 ExitEvent (int status)
14384 /* Give up on clean exit */
14388 /* Keep trying for clean exit */
14392 if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14393 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14395 if (telnetISR != NULL) {
14396 RemoveInputSource(telnetISR);
14398 if (icsPR != NoProc) {
14399 DestroyChildProcess(icsPR, TRUE);
14402 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14403 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14405 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14406 /* make sure this other one finishes before killing it! */
14407 if(endingGame) { int count = 0;
14408 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14409 while(endingGame && count++ < 10) DoSleep(1);
14410 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14413 /* Kill off chess programs */
14414 if (first.pr != NoProc) {
14417 DoSleep( appData.delayBeforeQuit );
14418 SendToProgram("quit\n", &first);
14419 DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14421 if (second.pr != NoProc) {
14422 DoSleep( appData.delayBeforeQuit );
14423 SendToProgram("quit\n", &second);
14424 DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14426 if (first.isr != NULL) {
14427 RemoveInputSource(first.isr);
14429 if (second.isr != NULL) {
14430 RemoveInputSource(second.isr);
14433 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14434 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14436 ShutDownFrontEnd();
14441 PauseEngine (ChessProgramState *cps)
14443 SendToProgram("pause\n", cps);
14448 UnPauseEngine (ChessProgramState *cps)
14450 SendToProgram("resume\n", cps);
14457 if (appData.debugMode)
14458 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14462 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14464 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14465 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14466 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14468 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14469 HandleMachineMove(stashedInputMove, stalledEngine);
14470 stalledEngine = NULL;
14473 if (gameMode == MachinePlaysWhite ||
14474 gameMode == TwoMachinesPlay ||
14475 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14476 if(first.pause) UnPauseEngine(&first);
14477 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14478 if(second.pause) UnPauseEngine(&second);
14479 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14482 DisplayBothClocks();
14484 if (gameMode == PlayFromGameFile) {
14485 if (appData.timeDelay >= 0)
14486 AutoPlayGameLoop();
14487 } else if (gameMode == IcsExamining && pauseExamInvalid) {
14488 Reset(FALSE, TRUE);
14489 SendToICS(ics_prefix);
14490 SendToICS("refresh\n");
14491 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14492 ForwardInner(forwardMostMove);
14494 pauseExamInvalid = FALSE;
14496 switch (gameMode) {
14500 pauseExamForwardMostMove = forwardMostMove;
14501 pauseExamInvalid = FALSE;
14504 case IcsPlayingWhite:
14505 case IcsPlayingBlack:
14509 case PlayFromGameFile:
14510 (void) StopLoadGameTimer();
14514 case BeginningOfGame:
14515 if (appData.icsActive) return;
14516 /* else fall through */
14517 case MachinePlaysWhite:
14518 case MachinePlaysBlack:
14519 case TwoMachinesPlay:
14520 if (forwardMostMove == 0)
14521 return; /* don't pause if no one has moved */
14522 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14523 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14524 if(onMove->pause) { // thinking engine can be paused
14525 PauseEngine(onMove); // do it
14526 if(onMove->other->pause) // pondering opponent can always be paused immediately
14527 PauseEngine(onMove->other);
14529 SendToProgram("easy\n", onMove->other);
14531 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14532 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14534 PauseEngine(&first);
14536 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14537 } else { // human on move, pause pondering by either method
14539 PauseEngine(&first);
14540 else if(appData.ponderNextMove)
14541 SendToProgram("easy\n", &first);
14544 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14554 EditCommentEvent ()
14556 char title[MSG_SIZ];
14558 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14559 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14561 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14562 WhiteOnMove(currentMove - 1) ? " " : ".. ",
14563 parseList[currentMove - 1]);
14566 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14573 char *tags = PGNTags(&gameInfo);
14575 EditTagsPopUp(tags, NULL);
14582 if(second.analyzing) {
14583 SendToProgram("exit\n", &second);
14584 second.analyzing = FALSE;
14586 if (second.pr == NoProc) StartChessProgram(&second);
14587 InitChessProgram(&second, FALSE);
14588 FeedMovesToProgram(&second, currentMove);
14590 SendToProgram("analyze\n", &second);
14591 second.analyzing = TRUE;
14595 /* Toggle ShowThinking */
14597 ToggleShowThinking()
14599 appData.showThinking = !appData.showThinking;
14600 ShowThinkingEvent();
14604 AnalyzeModeEvent ()
14608 if (!first.analysisSupport) {
14609 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14610 DisplayError(buf, 0);
14613 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14614 if (appData.icsActive) {
14615 if (gameMode != IcsObserving) {
14616 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14617 DisplayError(buf, 0);
14619 if (appData.icsEngineAnalyze) {
14620 if (appData.debugMode)
14621 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14627 /* if enable, user wants to disable icsEngineAnalyze */
14628 if (appData.icsEngineAnalyze) {
14633 appData.icsEngineAnalyze = TRUE;
14634 if (appData.debugMode)
14635 fprintf(debugFP, "ICS engine analyze starting... \n");
14638 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14639 if (appData.noChessProgram || gameMode == AnalyzeMode)
14642 if (gameMode != AnalyzeFile) {
14643 if (!appData.icsEngineAnalyze) {
14645 if (gameMode != EditGame) return 0;
14647 if (!appData.showThinking) ToggleShowThinking();
14648 ResurrectChessProgram();
14649 SendToProgram("analyze\n", &first);
14650 first.analyzing = TRUE;
14651 /*first.maybeThinking = TRUE;*/
14652 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14653 EngineOutputPopUp();
14655 if (!appData.icsEngineAnalyze) {
14656 gameMode = AnalyzeMode;
14657 ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14663 StartAnalysisClock();
14664 GetTimeMark(&lastNodeCountTime);
14670 AnalyzeFileEvent ()
14672 if (appData.noChessProgram || gameMode == AnalyzeFile)
14675 if (!first.analysisSupport) {
14677 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14678 DisplayError(buf, 0);
14682 if (gameMode != AnalyzeMode) {
14683 keepInfo = 1; // mere annotating should not alter PGN tags
14686 if (gameMode != EditGame) return;
14687 if (!appData.showThinking) ToggleShowThinking();
14688 ResurrectChessProgram();
14689 SendToProgram("analyze\n", &first);
14690 first.analyzing = TRUE;
14691 /*first.maybeThinking = TRUE;*/
14692 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14693 EngineOutputPopUp();
14695 gameMode = AnalyzeFile;
14699 StartAnalysisClock();
14700 GetTimeMark(&lastNodeCountTime);
14702 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14703 AnalysisPeriodicEvent(1);
14707 MachineWhiteEvent ()
14710 char *bookHit = NULL;
14712 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14716 if (gameMode == PlayFromGameFile ||
14717 gameMode == TwoMachinesPlay ||
14718 gameMode == Training ||
14719 gameMode == AnalyzeMode ||
14720 gameMode == EndOfGame)
14723 if (gameMode == EditPosition)
14724 EditPositionDone(TRUE);
14726 if (!WhiteOnMove(currentMove)) {
14727 DisplayError(_("It is not White's turn"), 0);
14731 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14734 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14735 gameMode == AnalyzeFile)
14738 ResurrectChessProgram(); /* in case it isn't running */
14739 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14740 gameMode = MachinePlaysWhite;
14743 gameMode = MachinePlaysWhite;
14747 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14749 if (first.sendName) {
14750 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14751 SendToProgram(buf, &first);
14753 if (first.sendTime) {
14754 if (first.useColors) {
14755 SendToProgram("black\n", &first); /*gnu kludge*/
14757 SendTimeRemaining(&first, TRUE);
14759 if (first.useColors) {
14760 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14762 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14763 SetMachineThinkingEnables();
14764 first.maybeThinking = TRUE;
14768 if (appData.autoFlipView && !flipView) {
14769 flipView = !flipView;
14770 DrawPosition(FALSE, NULL);
14771 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14774 if(bookHit) { // [HGM] book: simulate book reply
14775 static char bookMove[MSG_SIZ]; // a bit generous?
14777 programStats.nodes = programStats.depth = programStats.time =
14778 programStats.score = programStats.got_only_move = 0;
14779 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14781 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14782 strcat(bookMove, bookHit);
14783 HandleMachineMove(bookMove, &first);
14788 MachineBlackEvent ()
14791 char *bookHit = NULL;
14793 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14797 if (gameMode == PlayFromGameFile ||
14798 gameMode == TwoMachinesPlay ||
14799 gameMode == Training ||
14800 gameMode == AnalyzeMode ||
14801 gameMode == EndOfGame)
14804 if (gameMode == EditPosition)
14805 EditPositionDone(TRUE);
14807 if (WhiteOnMove(currentMove)) {
14808 DisplayError(_("It is not Black's turn"), 0);
14812 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14815 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14816 gameMode == AnalyzeFile)
14819 ResurrectChessProgram(); /* in case it isn't running */
14820 gameMode = MachinePlaysBlack;
14824 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14826 if (first.sendName) {
14827 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14828 SendToProgram(buf, &first);
14830 if (first.sendTime) {
14831 if (first.useColors) {
14832 SendToProgram("white\n", &first); /*gnu kludge*/
14834 SendTimeRemaining(&first, FALSE);
14836 if (first.useColors) {
14837 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14839 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14840 SetMachineThinkingEnables();
14841 first.maybeThinking = TRUE;
14844 if (appData.autoFlipView && flipView) {
14845 flipView = !flipView;
14846 DrawPosition(FALSE, NULL);
14847 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14849 if(bookHit) { // [HGM] book: simulate book reply
14850 static char bookMove[MSG_SIZ]; // a bit generous?
14852 programStats.nodes = programStats.depth = programStats.time =
14853 programStats.score = programStats.got_only_move = 0;
14854 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14856 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14857 strcat(bookMove, bookHit);
14858 HandleMachineMove(bookMove, &first);
14864 DisplayTwoMachinesTitle ()
14867 if (appData.matchGames > 0) {
14868 if(appData.tourneyFile[0]) {
14869 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14870 gameInfo.white, _("vs."), gameInfo.black,
14871 nextGame+1, appData.matchGames+1,
14872 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14874 if (first.twoMachinesColor[0] == 'w') {
14875 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14876 gameInfo.white, _("vs."), gameInfo.black,
14877 first.matchWins, second.matchWins,
14878 matchGame - 1 - (first.matchWins + second.matchWins));
14880 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14881 gameInfo.white, _("vs."), gameInfo.black,
14882 second.matchWins, first.matchWins,
14883 matchGame - 1 - (first.matchWins + second.matchWins));
14886 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14892 SettingsMenuIfReady ()
14894 if (second.lastPing != second.lastPong) {
14895 DisplayMessage("", _("Waiting for second chess program"));
14896 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14900 DisplayMessage("", "");
14901 SettingsPopUp(&second);
14905 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14908 if (cps->pr == NoProc) {
14909 StartChessProgram(cps);
14910 if (cps->protocolVersion == 1) {
14912 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14914 /* kludge: allow timeout for initial "feature" command */
14915 if(retry != TwoMachinesEventIfReady) FreezeUI();
14916 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14917 DisplayMessage("", buf);
14918 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14926 TwoMachinesEvent P((void))
14930 ChessProgramState *onmove;
14931 char *bookHit = NULL;
14932 static int stalling = 0;
14936 if (appData.noChessProgram) return;
14938 switch (gameMode) {
14939 case TwoMachinesPlay:
14941 case MachinePlaysWhite:
14942 case MachinePlaysBlack:
14943 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14944 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14948 case BeginningOfGame:
14949 case PlayFromGameFile:
14952 if (gameMode != EditGame) return;
14955 EditPositionDone(TRUE);
14966 // forwardMostMove = currentMove;
14967 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14968 startingEngine = TRUE;
14970 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14972 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14973 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14974 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14977 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14979 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14980 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14981 startingEngine = matchMode = FALSE;
14982 DisplayError("second engine does not play this", 0);
14983 gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14984 EditGameEvent(); // switch back to EditGame mode
14989 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14990 SendToProgram("force\n", &second);
14992 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14995 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14996 if(appData.matchPause>10000 || appData.matchPause<10)
14997 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14998 wait = SubtractTimeMarks(&now, &pauseStart);
14999 if(wait < appData.matchPause) {
15000 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15003 // we are now committed to starting the game
15005 DisplayMessage("", "");
15006 if (startedFromSetupPosition) {
15007 SendBoard(&second, backwardMostMove);
15008 if (appData.debugMode) {
15009 fprintf(debugFP, "Two Machines\n");
15012 for (i = backwardMostMove; i < forwardMostMove; i++) {
15013 SendMoveToProgram(i, &second);
15016 gameMode = TwoMachinesPlay;
15017 pausing = startingEngine = FALSE;
15018 ModeHighlight(); // [HGM] logo: this triggers display update of logos
15020 DisplayTwoMachinesTitle();
15022 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15027 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15028 SendToProgram(first.computerString, &first);
15029 if (first.sendName) {
15030 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15031 SendToProgram(buf, &first);
15033 SendToProgram(second.computerString, &second);
15034 if (second.sendName) {
15035 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15036 SendToProgram(buf, &second);
15040 if (!first.sendTime || !second.sendTime) {
15041 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15042 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15044 if (onmove->sendTime) {
15045 if (onmove->useColors) {
15046 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15048 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15050 if (onmove->useColors) {
15051 SendToProgram(onmove->twoMachinesColor, onmove);
15053 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15054 // SendToProgram("go\n", onmove);
15055 onmove->maybeThinking = TRUE;
15056 SetMachineThinkingEnables();
15060 if(bookHit) { // [HGM] book: simulate book reply
15061 static char bookMove[MSG_SIZ]; // a bit generous?
15063 programStats.nodes = programStats.depth = programStats.time =
15064 programStats.score = programStats.got_only_move = 0;
15065 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15067 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15068 strcat(bookMove, bookHit);
15069 savedMessage = bookMove; // args for deferred call
15070 savedState = onmove;
15071 ScheduleDelayedEvent(DeferredBookMove, 1);
15078 if (gameMode == Training) {
15079 SetTrainingModeOff();
15080 gameMode = PlayFromGameFile;
15081 DisplayMessage("", _("Training mode off"));
15083 gameMode = Training;
15084 animateTraining = appData.animate;
15086 /* make sure we are not already at the end of the game */
15087 if (currentMove < forwardMostMove) {
15088 SetTrainingModeOn();
15089 DisplayMessage("", _("Training mode on"));
15091 gameMode = PlayFromGameFile;
15092 DisplayError(_("Already at end of game"), 0);
15101 if (!appData.icsActive) return;
15102 switch (gameMode) {
15103 case IcsPlayingWhite:
15104 case IcsPlayingBlack:
15107 case BeginningOfGame:
15115 EditPositionDone(TRUE);
15128 gameMode = IcsIdle;
15138 switch (gameMode) {
15140 SetTrainingModeOff();
15142 case MachinePlaysWhite:
15143 case MachinePlaysBlack:
15144 case BeginningOfGame:
15145 SendToProgram("force\n", &first);
15146 if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15147 if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15149 abortEngineThink = TRUE;
15150 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15151 SendToProgram(buf, &first);
15152 DisplayMessage("Aborting engine think", "");
15156 SetUserThinkingEnables();
15158 case PlayFromGameFile:
15159 (void) StopLoadGameTimer();
15160 if (gameFileFP != NULL) {
15165 EditPositionDone(TRUE);
15170 SendToProgram("force\n", &first);
15172 case TwoMachinesPlay:
15173 GameEnds(EndOfFile, NULL, GE_PLAYER);
15174 ResurrectChessProgram();
15175 SetUserThinkingEnables();
15178 ResurrectChessProgram();
15180 case IcsPlayingBlack:
15181 case IcsPlayingWhite:
15182 DisplayError(_("Warning: You are still playing a game"), 0);
15185 DisplayError(_("Warning: You are still observing a game"), 0);
15188 DisplayError(_("Warning: You are still examining a game"), 0);
15199 first.offeredDraw = second.offeredDraw = 0;
15201 if (gameMode == PlayFromGameFile) {
15202 whiteTimeRemaining = timeRemaining[0][currentMove];
15203 blackTimeRemaining = timeRemaining[1][currentMove];
15207 if (gameMode == MachinePlaysWhite ||
15208 gameMode == MachinePlaysBlack ||
15209 gameMode == TwoMachinesPlay ||
15210 gameMode == EndOfGame) {
15211 i = forwardMostMove;
15212 while (i > currentMove) {
15213 SendToProgram("undo\n", &first);
15216 if(!adjustedClock) {
15217 whiteTimeRemaining = timeRemaining[0][currentMove];
15218 blackTimeRemaining = timeRemaining[1][currentMove];
15219 DisplayBothClocks();
15221 if (whiteFlag || blackFlag) {
15222 whiteFlag = blackFlag = 0;
15227 gameMode = EditGame;
15234 EditPositionEvent ()
15236 if (gameMode == EditPosition) {
15242 if (gameMode != EditGame) return;
15244 gameMode = EditPosition;
15247 if (currentMove > 0)
15248 CopyBoard(boards[0], boards[currentMove]);
15250 blackPlaysFirst = !WhiteOnMove(currentMove);
15252 currentMove = forwardMostMove = backwardMostMove = 0;
15253 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15255 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15261 /* [DM] icsEngineAnalyze - possible call from other functions */
15262 if (appData.icsEngineAnalyze) {
15263 appData.icsEngineAnalyze = FALSE;
15265 DisplayMessage("",_("Close ICS engine analyze..."));
15267 if (first.analysisSupport && first.analyzing) {
15268 SendToBoth("exit\n");
15269 first.analyzing = second.analyzing = FALSE;
15271 thinkOutput[0] = NULLCHAR;
15275 EditPositionDone (Boolean fakeRights)
15277 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15279 startedFromSetupPosition = TRUE;
15280 InitChessProgram(&first, FALSE);
15281 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15282 boards[0][EP_STATUS] = EP_NONE;
15283 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15284 if(boards[0][0][BOARD_WIDTH>>1] == king) {
15285 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15286 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15287 } else boards[0][CASTLING][2] = NoRights;
15288 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15289 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15290 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15291 } else boards[0][CASTLING][5] = NoRights;
15292 if(gameInfo.variant == VariantSChess) {
15294 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15295 boards[0][VIRGIN][i] = 0;
15296 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15297 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15301 SendToProgram("force\n", &first);
15302 if (blackPlaysFirst) {
15303 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15304 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15305 currentMove = forwardMostMove = backwardMostMove = 1;
15306 CopyBoard(boards[1], boards[0]);
15308 currentMove = forwardMostMove = backwardMostMove = 0;
15310 SendBoard(&first, forwardMostMove);
15311 if (appData.debugMode) {
15312 fprintf(debugFP, "EditPosDone\n");
15315 DisplayMessage("", "");
15316 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15317 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15318 gameMode = EditGame;
15320 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15321 ClearHighlights(); /* [AS] */
15324 /* Pause for `ms' milliseconds */
15325 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15327 TimeDelay (long ms)
15334 } while (SubtractTimeMarks(&m2, &m1) < ms);
15337 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15339 SendMultiLineToICS (char *buf)
15341 char temp[MSG_SIZ+1], *p;
15348 strncpy(temp, buf, len);
15353 if (*p == '\n' || *p == '\r')
15358 strcat(temp, "\n");
15360 SendToPlayer(temp, strlen(temp));
15364 SetWhiteToPlayEvent ()
15366 if (gameMode == EditPosition) {
15367 blackPlaysFirst = FALSE;
15368 DisplayBothClocks(); /* works because currentMove is 0 */
15369 } else if (gameMode == IcsExamining) {
15370 SendToICS(ics_prefix);
15371 SendToICS("tomove white\n");
15376 SetBlackToPlayEvent ()
15378 if (gameMode == EditPosition) {
15379 blackPlaysFirst = TRUE;
15380 currentMove = 1; /* kludge */
15381 DisplayBothClocks();
15383 } else if (gameMode == IcsExamining) {
15384 SendToICS(ics_prefix);
15385 SendToICS("tomove black\n");
15390 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15393 ChessSquare piece = boards[0][y][x];
15394 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15395 static int lastVariant;
15397 if (gameMode != EditPosition && gameMode != IcsExamining) return;
15399 switch (selection) {
15401 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15402 MarkTargetSquares(1);
15403 CopyBoard(currentBoard, boards[0]);
15404 CopyBoard(menuBoard, initialPosition);
15405 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15406 SendToICS(ics_prefix);
15407 SendToICS("bsetup clear\n");
15408 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15409 SendToICS(ics_prefix);
15410 SendToICS("clearboard\n");
15413 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15414 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15415 for (y = 0; y < BOARD_HEIGHT; y++) {
15416 if (gameMode == IcsExamining) {
15417 if (boards[currentMove][y][x] != EmptySquare) {
15418 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15422 } else if(boards[0][y][x] != DarkSquare) {
15423 if(boards[0][y][x] != p) nonEmpty++;
15424 boards[0][y][x] = p;
15428 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15430 for(r = 0; r < BOARD_HEIGHT; r++) {
15431 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
15432 ChessSquare p = menuBoard[r][x];
15433 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15436 DisplayMessage("Clicking clock again restores position", "");
15437 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15438 if(!nonEmpty) { // asked to clear an empty board
15439 CopyBoard(boards[0], menuBoard);
15441 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15442 CopyBoard(boards[0], initialPosition);
15444 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15445 && !CompareBoards(nullBoard, erasedBoard)) {
15446 CopyBoard(boards[0], erasedBoard);
15448 CopyBoard(erasedBoard, currentBoard);
15452 if (gameMode == EditPosition) {
15453 DrawPosition(FALSE, boards[0]);
15458 SetWhiteToPlayEvent();
15462 SetBlackToPlayEvent();
15466 if (gameMode == IcsExamining) {
15467 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15468 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15471 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15472 if(x == BOARD_LEFT-2) {
15473 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15474 boards[0][y][1] = 0;
15476 if(x == BOARD_RGHT+1) {
15477 if(y >= gameInfo.holdingsSize) break;
15478 boards[0][y][BOARD_WIDTH-2] = 0;
15481 boards[0][y][x] = EmptySquare;
15482 DrawPosition(FALSE, boards[0]);
15487 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15488 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
15489 selection = (ChessSquare) (PROMOTED(piece));
15490 } else if(piece == EmptySquare) selection = WhiteSilver;
15491 else selection = (ChessSquare)((int)piece - 1);
15495 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15496 piece > (int)BlackMan && piece <= (int)BlackKing ) {
15497 selection = (ChessSquare) (DEMOTED(piece));
15498 } else if(piece == EmptySquare) selection = BlackSilver;
15499 else selection = (ChessSquare)((int)piece + 1);
15504 if(gameInfo.variant == VariantShatranj ||
15505 gameInfo.variant == VariantXiangqi ||
15506 gameInfo.variant == VariantCourier ||
15507 gameInfo.variant == VariantASEAN ||
15508 gameInfo.variant == VariantMakruk )
15509 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15514 if(gameInfo.variant == VariantXiangqi)
15515 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15516 if(gameInfo.variant == VariantKnightmate)
15517 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15520 if (gameMode == IcsExamining) {
15521 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15522 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15523 PieceToChar(selection), AAA + x, ONE + y);
15526 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15528 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15529 n = PieceToNumber(selection - BlackPawn);
15530 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15531 boards[0][BOARD_HEIGHT-1-n][0] = selection;
15532 boards[0][BOARD_HEIGHT-1-n][1]++;
15534 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15535 n = PieceToNumber(selection);
15536 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15537 boards[0][n][BOARD_WIDTH-1] = selection;
15538 boards[0][n][BOARD_WIDTH-2]++;
15541 boards[0][y][x] = selection;
15542 DrawPosition(TRUE, boards[0]);
15544 fromX = fromY = -1;
15552 DropMenuEvent (ChessSquare selection, int x, int y)
15554 ChessMove moveType;
15556 switch (gameMode) {
15557 case IcsPlayingWhite:
15558 case MachinePlaysBlack:
15559 if (!WhiteOnMove(currentMove)) {
15560 DisplayMoveError(_("It is Black's turn"));
15563 moveType = WhiteDrop;
15565 case IcsPlayingBlack:
15566 case MachinePlaysWhite:
15567 if (WhiteOnMove(currentMove)) {
15568 DisplayMoveError(_("It is White's turn"));
15571 moveType = BlackDrop;
15574 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15580 if (moveType == BlackDrop && selection < BlackPawn) {
15581 selection = (ChessSquare) ((int) selection
15582 + (int) BlackPawn - (int) WhitePawn);
15584 if (boards[currentMove][y][x] != EmptySquare) {
15585 DisplayMoveError(_("That square is occupied"));
15589 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15595 /* Accept a pending offer of any kind from opponent */
15597 if (appData.icsActive) {
15598 SendToICS(ics_prefix);
15599 SendToICS("accept\n");
15600 } else if (cmailMsgLoaded) {
15601 if (currentMove == cmailOldMove &&
15602 commentList[cmailOldMove] != NULL &&
15603 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15604 "Black offers a draw" : "White offers a draw")) {
15606 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15607 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15609 DisplayError(_("There is no pending offer on this move"), 0);
15610 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15613 /* Not used for offers from chess program */
15620 /* Decline a pending offer of any kind from opponent */
15622 if (appData.icsActive) {
15623 SendToICS(ics_prefix);
15624 SendToICS("decline\n");
15625 } else if (cmailMsgLoaded) {
15626 if (currentMove == cmailOldMove &&
15627 commentList[cmailOldMove] != NULL &&
15628 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15629 "Black offers a draw" : "White offers a draw")) {
15631 AppendComment(cmailOldMove, "Draw declined", TRUE);
15632 DisplayComment(cmailOldMove - 1, "Draw declined");
15635 DisplayError(_("There is no pending offer on this move"), 0);
15638 /* Not used for offers from chess program */
15645 /* Issue ICS rematch command */
15646 if (appData.icsActive) {
15647 SendToICS(ics_prefix);
15648 SendToICS("rematch\n");
15655 /* Call your opponent's flag (claim a win on time) */
15656 if (appData.icsActive) {
15657 SendToICS(ics_prefix);
15658 SendToICS("flag\n");
15660 switch (gameMode) {
15663 case MachinePlaysWhite:
15666 GameEnds(GameIsDrawn, "Both players ran out of time",
15669 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15671 DisplayError(_("Your opponent is not out of time"), 0);
15674 case MachinePlaysBlack:
15677 GameEnds(GameIsDrawn, "Both players ran out of time",
15680 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15682 DisplayError(_("Your opponent is not out of time"), 0);
15690 ClockClick (int which)
15691 { // [HGM] code moved to back-end from winboard.c
15692 if(which) { // black clock
15693 if (gameMode == EditPosition || gameMode == IcsExamining) {
15694 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15695 SetBlackToPlayEvent();
15696 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15697 gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15698 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15699 } else if (shiftKey) {
15700 AdjustClock(which, -1);
15701 } else if (gameMode == IcsPlayingWhite ||
15702 gameMode == MachinePlaysBlack) {
15705 } else { // white clock
15706 if (gameMode == EditPosition || gameMode == IcsExamining) {
15707 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15708 SetWhiteToPlayEvent();
15709 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15710 gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15711 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15712 } else if (shiftKey) {
15713 AdjustClock(which, -1);
15714 } else if (gameMode == IcsPlayingBlack ||
15715 gameMode == MachinePlaysWhite) {
15724 /* Offer draw or accept pending draw offer from opponent */
15726 if (appData.icsActive) {
15727 /* Note: tournament rules require draw offers to be
15728 made after you make your move but before you punch
15729 your clock. Currently ICS doesn't let you do that;
15730 instead, you immediately punch your clock after making
15731 a move, but you can offer a draw at any time. */
15733 SendToICS(ics_prefix);
15734 SendToICS("draw\n");
15735 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15736 } else if (cmailMsgLoaded) {
15737 if (currentMove == cmailOldMove &&
15738 commentList[cmailOldMove] != NULL &&
15739 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15740 "Black offers a draw" : "White offers a draw")) {
15741 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15742 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15743 } else if (currentMove == cmailOldMove + 1) {
15744 char *offer = WhiteOnMove(cmailOldMove) ?
15745 "White offers a draw" : "Black offers a draw";
15746 AppendComment(currentMove, offer, TRUE);
15747 DisplayComment(currentMove - 1, offer);
15748 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15750 DisplayError(_("You must make your move before offering a draw"), 0);
15751 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15753 } else if (first.offeredDraw) {
15754 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15756 if (first.sendDrawOffers) {
15757 SendToProgram("draw\n", &first);
15758 userOfferedDraw = TRUE;
15766 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15768 if (appData.icsActive) {
15769 SendToICS(ics_prefix);
15770 SendToICS("adjourn\n");
15772 /* Currently GNU Chess doesn't offer or accept Adjourns */
15780 /* Offer Abort or accept pending Abort offer from opponent */
15782 if (appData.icsActive) {
15783 SendToICS(ics_prefix);
15784 SendToICS("abort\n");
15786 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15793 /* Resign. You can do this even if it's not your turn. */
15795 if (appData.icsActive) {
15796 SendToICS(ics_prefix);
15797 SendToICS("resign\n");
15799 switch (gameMode) {
15800 case MachinePlaysWhite:
15801 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15803 case MachinePlaysBlack:
15804 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15807 if (cmailMsgLoaded) {
15809 if (WhiteOnMove(cmailOldMove)) {
15810 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15812 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15814 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15825 StopObservingEvent ()
15827 /* Stop observing current games */
15828 SendToICS(ics_prefix);
15829 SendToICS("unobserve\n");
15833 StopExaminingEvent ()
15835 /* Stop observing current game */
15836 SendToICS(ics_prefix);
15837 SendToICS("unexamine\n");
15841 ForwardInner (int target)
15843 int limit; int oldSeekGraphUp = seekGraphUp;
15845 if (appData.debugMode)
15846 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15847 target, currentMove, forwardMostMove);
15849 if (gameMode == EditPosition)
15852 seekGraphUp = FALSE;
15853 MarkTargetSquares(1);
15854 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15856 if (gameMode == PlayFromGameFile && !pausing)
15859 if (gameMode == IcsExamining && pausing)
15860 limit = pauseExamForwardMostMove;
15862 limit = forwardMostMove;
15864 if (target > limit) target = limit;
15866 if (target > 0 && moveList[target - 1][0]) {
15867 int fromX, fromY, toX, toY;
15868 toX = moveList[target - 1][2] - AAA;
15869 toY = moveList[target - 1][3] - ONE;
15870 if (moveList[target - 1][1] == '@') {
15871 if (appData.highlightLastMove) {
15872 SetHighlights(-1, -1, toX, toY);
15875 fromX = moveList[target - 1][0] - AAA;
15876 fromY = moveList[target - 1][1] - ONE;
15877 if (target == currentMove + 1) {
15878 if(moveList[target - 1][4] == ';') { // multi-leg
15879 killX = moveList[target - 1][5] - AAA;
15880 killY = moveList[target - 1][6] - ONE;
15882 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15883 killX = killY = -1;
15885 if (appData.highlightLastMove) {
15886 SetHighlights(fromX, fromY, toX, toY);
15890 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15891 gameMode == Training || gameMode == PlayFromGameFile ||
15892 gameMode == AnalyzeFile) {
15893 while (currentMove < target) {
15894 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15895 SendMoveToProgram(currentMove++, &first);
15898 currentMove = target;
15901 if (gameMode == EditGame || gameMode == EndOfGame) {
15902 whiteTimeRemaining = timeRemaining[0][currentMove];
15903 blackTimeRemaining = timeRemaining[1][currentMove];
15905 DisplayBothClocks();
15906 DisplayMove(currentMove - 1);
15907 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15908 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15909 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15910 DisplayComment(currentMove - 1, commentList[currentMove]);
15912 ClearMap(); // [HGM] exclude: invalidate map
15919 if (gameMode == IcsExamining && !pausing) {
15920 SendToICS(ics_prefix);
15921 SendToICS("forward\n");
15923 ForwardInner(currentMove + 1);
15930 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15931 /* to optimze, we temporarily turn off analysis mode while we feed
15932 * the remaining moves to the engine. Otherwise we get analysis output
15935 if (first.analysisSupport) {
15936 SendToProgram("exit\nforce\n", &first);
15937 first.analyzing = FALSE;
15941 if (gameMode == IcsExamining && !pausing) {
15942 SendToICS(ics_prefix);
15943 SendToICS("forward 999999\n");
15945 ForwardInner(forwardMostMove);
15948 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15949 /* we have fed all the moves, so reactivate analysis mode */
15950 SendToProgram("analyze\n", &first);
15951 first.analyzing = TRUE;
15952 /*first.maybeThinking = TRUE;*/
15953 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15958 BackwardInner (int target)
15960 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15962 if (appData.debugMode)
15963 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15964 target, currentMove, forwardMostMove);
15966 if (gameMode == EditPosition) return;
15967 seekGraphUp = FALSE;
15968 MarkTargetSquares(1);
15969 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15970 if (currentMove <= backwardMostMove) {
15972 DrawPosition(full_redraw, boards[currentMove]);
15975 if (gameMode == PlayFromGameFile && !pausing)
15978 if (moveList[target][0]) {
15979 int fromX, fromY, toX, toY;
15980 toX = moveList[target][2] - AAA;
15981 toY = moveList[target][3] - ONE;
15982 if (moveList[target][1] == '@') {
15983 if (appData.highlightLastMove) {
15984 SetHighlights(-1, -1, toX, toY);
15987 fromX = moveList[target][0] - AAA;
15988 fromY = moveList[target][1] - ONE;
15989 if (target == currentMove - 1) {
15990 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15992 if (appData.highlightLastMove) {
15993 SetHighlights(fromX, fromY, toX, toY);
15997 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15998 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15999 while (currentMove > target) {
16000 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16001 // null move cannot be undone. Reload program with move history before it.
16003 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16004 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16006 SendBoard(&first, i);
16007 if(second.analyzing) SendBoard(&second, i);
16008 for(currentMove=i; currentMove<target; currentMove++) {
16009 SendMoveToProgram(currentMove, &first);
16010 if(second.analyzing) SendMoveToProgram(currentMove, &second);
16014 SendToBoth("undo\n");
16018 currentMove = target;
16021 if (gameMode == EditGame || gameMode == EndOfGame) {
16022 whiteTimeRemaining = timeRemaining[0][currentMove];
16023 blackTimeRemaining = timeRemaining[1][currentMove];
16025 DisplayBothClocks();
16026 DisplayMove(currentMove - 1);
16027 DrawPosition(full_redraw, boards[currentMove]);
16028 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16029 // [HGM] PV info: routine tests if comment empty
16030 DisplayComment(currentMove - 1, commentList[currentMove]);
16031 ClearMap(); // [HGM] exclude: invalidate map
16037 if (gameMode == IcsExamining && !pausing) {
16038 SendToICS(ics_prefix);
16039 SendToICS("backward\n");
16041 BackwardInner(currentMove - 1);
16048 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16049 /* to optimize, we temporarily turn off analysis mode while we undo
16050 * all the moves. Otherwise we get analysis output after each undo.
16052 if (first.analysisSupport) {
16053 SendToProgram("exit\nforce\n", &first);
16054 first.analyzing = FALSE;
16058 if (gameMode == IcsExamining && !pausing) {
16059 SendToICS(ics_prefix);
16060 SendToICS("backward 999999\n");
16062 BackwardInner(backwardMostMove);
16065 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16066 /* we have fed all the moves, so reactivate analysis mode */
16067 SendToProgram("analyze\n", &first);
16068 first.analyzing = TRUE;
16069 /*first.maybeThinking = TRUE;*/
16070 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16077 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16078 if (to >= forwardMostMove) to = forwardMostMove;
16079 if (to <= backwardMostMove) to = backwardMostMove;
16080 if (to < currentMove) {
16088 RevertEvent (Boolean annotate)
16090 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16093 if (gameMode != IcsExamining) {
16094 DisplayError(_("You are not examining a game"), 0);
16098 DisplayError(_("You can't revert while pausing"), 0);
16101 SendToICS(ics_prefix);
16102 SendToICS("revert\n");
16106 RetractMoveEvent ()
16108 switch (gameMode) {
16109 case MachinePlaysWhite:
16110 case MachinePlaysBlack:
16111 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16112 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16115 if (forwardMostMove < 2) return;
16116 currentMove = forwardMostMove = forwardMostMove - 2;
16117 whiteTimeRemaining = timeRemaining[0][currentMove];
16118 blackTimeRemaining = timeRemaining[1][currentMove];
16119 DisplayBothClocks();
16120 DisplayMove(currentMove - 1);
16121 ClearHighlights();/*!! could figure this out*/
16122 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16123 SendToProgram("remove\n", &first);
16124 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16127 case BeginningOfGame:
16131 case IcsPlayingWhite:
16132 case IcsPlayingBlack:
16133 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16134 SendToICS(ics_prefix);
16135 SendToICS("takeback 2\n");
16137 SendToICS(ics_prefix);
16138 SendToICS("takeback 1\n");
16147 ChessProgramState *cps;
16149 switch (gameMode) {
16150 case MachinePlaysWhite:
16151 if (!WhiteOnMove(forwardMostMove)) {
16152 DisplayError(_("It is your turn"), 0);
16157 case MachinePlaysBlack:
16158 if (WhiteOnMove(forwardMostMove)) {
16159 DisplayError(_("It is your turn"), 0);
16164 case TwoMachinesPlay:
16165 if (WhiteOnMove(forwardMostMove) ==
16166 (first.twoMachinesColor[0] == 'w')) {
16172 case BeginningOfGame:
16176 SendToProgram("?\n", cps);
16180 TruncateGameEvent ()
16183 if (gameMode != EditGame) return;
16190 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16191 if (forwardMostMove > currentMove) {
16192 if (gameInfo.resultDetails != NULL) {
16193 free(gameInfo.resultDetails);
16194 gameInfo.resultDetails = NULL;
16195 gameInfo.result = GameUnfinished;
16197 forwardMostMove = currentMove;
16198 HistorySet(parseList, backwardMostMove, forwardMostMove,
16206 if (appData.noChessProgram) return;
16207 switch (gameMode) {
16208 case MachinePlaysWhite:
16209 if (WhiteOnMove(forwardMostMove)) {
16210 DisplayError(_("Wait until your turn."), 0);
16214 case BeginningOfGame:
16215 case MachinePlaysBlack:
16216 if (!WhiteOnMove(forwardMostMove)) {
16217 DisplayError(_("Wait until your turn."), 0);
16222 DisplayError(_("No hint available"), 0);
16225 SendToProgram("hint\n", &first);
16226 hintRequested = TRUE;
16230 SaveSelected (FILE *g, int dummy, char *dummy2)
16232 ListGame * lg = (ListGame *) gameList.head;
16236 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16237 DisplayError(_("Game list not loaded or empty"), 0);
16241 creatingBook = TRUE; // suppresses stuff during load game
16243 /* Get list size */
16244 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16245 if(lg->position >= 0) { // selected?
16246 LoadGame(f, nItem, "", TRUE);
16247 SaveGamePGN2(g); // leaves g open
16250 lg = (ListGame *) lg->node.succ;
16254 creatingBook = FALSE;
16262 ListGame * lg = (ListGame *) gameList.head;
16265 static int secondTime = FALSE;
16267 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16268 DisplayError(_("Game list not loaded or empty"), 0);
16272 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16275 DisplayNote(_("Book file exists! Try again for overwrite."));
16279 creatingBook = TRUE;
16280 secondTime = FALSE;
16282 /* Get list size */
16283 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16284 if(lg->position >= 0) {
16285 LoadGame(f, nItem, "", TRUE);
16286 AddGameToBook(TRUE);
16289 lg = (ListGame *) lg->node.succ;
16292 creatingBook = FALSE;
16299 if (appData.noChessProgram) return;
16300 switch (gameMode) {
16301 case MachinePlaysWhite:
16302 if (WhiteOnMove(forwardMostMove)) {
16303 DisplayError(_("Wait until your turn."), 0);
16307 case BeginningOfGame:
16308 case MachinePlaysBlack:
16309 if (!WhiteOnMove(forwardMostMove)) {
16310 DisplayError(_("Wait until your turn."), 0);
16315 EditPositionDone(TRUE);
16317 case TwoMachinesPlay:
16322 SendToProgram("bk\n", &first);
16323 bookOutput[0] = NULLCHAR;
16324 bookRequested = TRUE;
16330 char *tags = PGNTags(&gameInfo);
16331 TagsPopUp(tags, CmailMsg());
16335 /* end button procedures */
16338 PrintPosition (FILE *fp, int move)
16342 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16343 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16344 char c = PieceToChar(boards[move][i][j]);
16345 fputc(c == '?' ? '.' : c, fp);
16346 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16349 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16350 fprintf(fp, "white to play\n");
16352 fprintf(fp, "black to play\n");
16356 PrintOpponents (FILE *fp)
16358 if (gameInfo.white != NULL) {
16359 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16365 /* Find last component of program's own name, using some heuristics */
16367 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16370 int local = (strcmp(host, "localhost") == 0);
16371 while (!local && (p = strchr(prog, ';')) != NULL) {
16373 while (*p == ' ') p++;
16376 if (*prog == '"' || *prog == '\'') {
16377 q = strchr(prog + 1, *prog);
16379 q = strchr(prog, ' ');
16381 if (q == NULL) q = prog + strlen(prog);
16383 while (p >= prog && *p != '/' && *p != '\\') p--;
16385 if(p == prog && *p == '"') p++;
16387 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16388 memcpy(buf, p, q - p);
16389 buf[q - p] = NULLCHAR;
16397 TimeControlTagValue ()
16400 if (!appData.clockMode) {
16401 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16402 } else if (movesPerSession > 0) {
16403 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16404 } else if (timeIncrement == 0) {
16405 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16407 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16409 return StrSave(buf);
16415 /* This routine is used only for certain modes */
16416 VariantClass v = gameInfo.variant;
16417 ChessMove r = GameUnfinished;
16420 if(keepInfo) return;
16422 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16423 r = gameInfo.result;
16424 p = gameInfo.resultDetails;
16425 gameInfo.resultDetails = NULL;
16427 ClearGameInfo(&gameInfo);
16428 gameInfo.variant = v;
16430 switch (gameMode) {
16431 case MachinePlaysWhite:
16432 gameInfo.event = StrSave( appData.pgnEventHeader );
16433 gameInfo.site = StrSave(HostName());
16434 gameInfo.date = PGNDate();
16435 gameInfo.round = StrSave("-");
16436 gameInfo.white = StrSave(first.tidy);
16437 gameInfo.black = StrSave(UserName());
16438 gameInfo.timeControl = TimeControlTagValue();
16441 case MachinePlaysBlack:
16442 gameInfo.event = StrSave( appData.pgnEventHeader );
16443 gameInfo.site = StrSave(HostName());
16444 gameInfo.date = PGNDate();
16445 gameInfo.round = StrSave("-");
16446 gameInfo.white = StrSave(UserName());
16447 gameInfo.black = StrSave(first.tidy);
16448 gameInfo.timeControl = TimeControlTagValue();
16451 case TwoMachinesPlay:
16452 gameInfo.event = StrSave( appData.pgnEventHeader );
16453 gameInfo.site = StrSave(HostName());
16454 gameInfo.date = PGNDate();
16457 snprintf(buf, MSG_SIZ, "%d", roundNr);
16458 gameInfo.round = StrSave(buf);
16460 gameInfo.round = StrSave("-");
16462 if (first.twoMachinesColor[0] == 'w') {
16463 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16464 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16466 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16467 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16469 gameInfo.timeControl = TimeControlTagValue();
16473 gameInfo.event = StrSave("Edited game");
16474 gameInfo.site = StrSave(HostName());
16475 gameInfo.date = PGNDate();
16476 gameInfo.round = StrSave("-");
16477 gameInfo.white = StrSave("-");
16478 gameInfo.black = StrSave("-");
16479 gameInfo.result = r;
16480 gameInfo.resultDetails = p;
16484 gameInfo.event = StrSave("Edited position");
16485 gameInfo.site = StrSave(HostName());
16486 gameInfo.date = PGNDate();
16487 gameInfo.round = StrSave("-");
16488 gameInfo.white = StrSave("-");
16489 gameInfo.black = StrSave("-");
16492 case IcsPlayingWhite:
16493 case IcsPlayingBlack:
16498 case PlayFromGameFile:
16499 gameInfo.event = StrSave("Game from non-PGN file");
16500 gameInfo.site = StrSave(HostName());
16501 gameInfo.date = PGNDate();
16502 gameInfo.round = StrSave("-");
16503 gameInfo.white = StrSave("?");
16504 gameInfo.black = StrSave("?");
16513 ReplaceComment (int index, char *text)
16519 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16520 pvInfoList[index-1].depth == len &&
16521 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16522 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16523 while (*text == '\n') text++;
16524 len = strlen(text);
16525 while (len > 0 && text[len - 1] == '\n') len--;
16527 if (commentList[index] != NULL)
16528 free(commentList[index]);
16531 commentList[index] = NULL;
16534 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16535 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16536 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16537 commentList[index] = (char *) malloc(len + 2);
16538 strncpy(commentList[index], text, len);
16539 commentList[index][len] = '\n';
16540 commentList[index][len + 1] = NULLCHAR;
16542 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16544 commentList[index] = (char *) malloc(len + 7);
16545 safeStrCpy(commentList[index], "{\n", 3);
16546 safeStrCpy(commentList[index]+2, text, len+1);
16547 commentList[index][len+2] = NULLCHAR;
16548 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16549 strcat(commentList[index], "\n}\n");
16554 CrushCRs (char *text)
16562 if (ch == '\r') continue;
16564 } while (ch != '\0');
16568 AppendComment (int index, char *text, Boolean addBraces)
16569 /* addBraces tells if we should add {} */
16574 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16575 if(addBraces == 3) addBraces = 0; else // force appending literally
16576 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16579 while (*text == '\n') text++;
16580 len = strlen(text);
16581 while (len > 0 && text[len - 1] == '\n') len--;
16582 text[len] = NULLCHAR;
16584 if (len == 0) return;
16586 if (commentList[index] != NULL) {
16587 Boolean addClosingBrace = addBraces;
16588 old = commentList[index];
16589 oldlen = strlen(old);
16590 while(commentList[index][oldlen-1] == '\n')
16591 commentList[index][--oldlen] = NULLCHAR;
16592 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16593 safeStrCpy(commentList[index], old, oldlen + len + 6);
16595 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16596 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16597 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16598 while (*text == '\n') { text++; len--; }
16599 commentList[index][--oldlen] = NULLCHAR;
16601 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16602 else strcat(commentList[index], "\n");
16603 strcat(commentList[index], text);
16604 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16605 else strcat(commentList[index], "\n");
16607 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16609 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16610 else commentList[index][0] = NULLCHAR;
16611 strcat(commentList[index], text);
16612 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16613 if(addBraces == TRUE) strcat(commentList[index], "}\n");
16618 FindStr (char * text, char * sub_text)
16620 char * result = strstr( text, sub_text );
16622 if( result != NULL ) {
16623 result += strlen( sub_text );
16629 /* [AS] Try to extract PV info from PGN comment */
16630 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16632 GetInfoFromComment (int index, char * text)
16634 char * sep = text, *p;
16636 if( text != NULL && index > 0 ) {
16639 int time = -1, sec = 0, deci;
16640 char * s_eval = FindStr( text, "[%eval " );
16641 char * s_emt = FindStr( text, "[%emt " );
16643 if( s_eval != NULL || s_emt != NULL ) {
16645 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16650 if( s_eval != NULL ) {
16651 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16655 if( delim != ']' ) {
16660 if( s_emt != NULL ) {
16665 /* We expect something like: [+|-]nnn.nn/dd */
16668 if(*text != '{') return text; // [HGM] braces: must be normal comment
16670 sep = strchr( text, '/' );
16671 if( sep == NULL || sep < (text+4) ) {
16676 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16677 if(p[1] == '(') { // comment starts with PV
16678 p = strchr(p, ')'); // locate end of PV
16679 if(p == NULL || sep < p+5) return text;
16680 // at this point we have something like "{(.*) +0.23/6 ..."
16681 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16682 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16683 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16685 time = -1; sec = -1; deci = -1;
16686 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16687 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16688 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16689 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16693 if( score_lo < 0 || score_lo >= 100 ) {
16697 if(sec >= 0) time = 600*time + 10*sec; else
16698 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16700 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16702 /* [HGM] PV time: now locate end of PV info */
16703 while( *++sep >= '0' && *sep <= '9'); // strip depth
16705 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16707 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16709 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16710 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16721 pvInfoList[index-1].depth = depth;
16722 pvInfoList[index-1].score = score;
16723 pvInfoList[index-1].time = 10*time; // centi-sec
16724 if(*sep == '}') *sep = 0; else *--sep = '{';
16725 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16731 SendToProgram (char *message, ChessProgramState *cps)
16733 int count, outCount, error;
16736 if (cps->pr == NoProc) return;
16739 if (appData.debugMode) {
16742 fprintf(debugFP, "%ld >%-6s: %s",
16743 SubtractTimeMarks(&now, &programStartTime),
16744 cps->which, message);
16746 fprintf(serverFP, "%ld >%-6s: %s",
16747 SubtractTimeMarks(&now, &programStartTime),
16748 cps->which, message), fflush(serverFP);
16751 count = strlen(message);
16752 outCount = OutputToProcess(cps->pr, message, count, &error);
16753 if (outCount < count && !exiting
16754 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16755 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16756 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16757 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16758 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16759 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16760 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16761 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16763 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16764 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16765 gameInfo.result = res;
16767 gameInfo.resultDetails = StrSave(buf);
16769 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16770 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16775 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16779 ChessProgramState *cps = (ChessProgramState *)closure;
16781 if (isr != cps->isr) return; /* Killed intentionally */
16784 RemoveInputSource(cps->isr);
16785 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16786 _(cps->which), cps->program);
16787 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16788 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16789 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16790 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16791 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16792 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16794 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16795 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16796 gameInfo.result = res;
16798 gameInfo.resultDetails = StrSave(buf);
16800 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16801 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16803 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16804 _(cps->which), cps->program);
16805 RemoveInputSource(cps->isr);
16807 /* [AS] Program is misbehaving badly... kill it */
16808 if( count == -2 ) {
16809 DestroyChildProcess( cps->pr, 9 );
16813 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16818 if ((end_str = strchr(message, '\r')) != NULL)
16819 *end_str = NULLCHAR;
16820 if ((end_str = strchr(message, '\n')) != NULL)
16821 *end_str = NULLCHAR;
16823 if (appData.debugMode) {
16824 TimeMark now; int print = 1;
16825 char *quote = ""; char c; int i;
16827 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16828 char start = message[0];
16829 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16830 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16831 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16832 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16833 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16834 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16835 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16836 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16837 sscanf(message, "hint: %c", &c)!=1 &&
16838 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16839 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16840 print = (appData.engineComments >= 2);
16842 message[0] = start; // restore original message
16846 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16847 SubtractTimeMarks(&now, &programStartTime), cps->which,
16851 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16852 SubtractTimeMarks(&now, &programStartTime), cps->which,
16854 message), fflush(serverFP);
16858 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16859 if (appData.icsEngineAnalyze) {
16860 if (strstr(message, "whisper") != NULL ||
16861 strstr(message, "kibitz") != NULL ||
16862 strstr(message, "tellics") != NULL) return;
16865 HandleMachineMove(message, cps);
16870 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16875 if( timeControl_2 > 0 ) {
16876 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16877 tc = timeControl_2;
16880 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16881 inc /= cps->timeOdds;
16882 st /= cps->timeOdds;
16884 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16887 /* Set exact time per move, normally using st command */
16888 if (cps->stKludge) {
16889 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16891 if (seconds == 0) {
16892 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16894 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16897 snprintf(buf, MSG_SIZ, "st %d\n", st);
16900 /* Set conventional or incremental time control, using level command */
16901 if (seconds == 0) {
16902 /* Note old gnuchess bug -- minutes:seconds used to not work.
16903 Fixed in later versions, but still avoid :seconds
16904 when seconds is 0. */
16905 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16907 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16908 seconds, inc/1000.);
16911 SendToProgram(buf, cps);
16913 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16914 /* Orthogonally, limit search to given depth */
16916 if (cps->sdKludge) {
16917 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16919 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16921 SendToProgram(buf, cps);
16924 if(cps->nps >= 0) { /* [HGM] nps */
16925 if(cps->supportsNPS == FALSE)
16926 cps->nps = -1; // don't use if engine explicitly says not supported!
16928 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16929 SendToProgram(buf, cps);
16934 ChessProgramState *
16936 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16938 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16939 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16945 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16947 char message[MSG_SIZ];
16950 /* Note: this routine must be called when the clocks are stopped
16951 or when they have *just* been set or switched; otherwise
16952 it will be off by the time since the current tick started.
16954 if (machineWhite) {
16955 time = whiteTimeRemaining / 10;
16956 otime = blackTimeRemaining / 10;
16958 time = blackTimeRemaining / 10;
16959 otime = whiteTimeRemaining / 10;
16961 /* [HGM] translate opponent's time by time-odds factor */
16962 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16964 if (time <= 0) time = 1;
16965 if (otime <= 0) otime = 1;
16967 snprintf(message, MSG_SIZ, "time %ld\n", time);
16968 SendToProgram(message, cps);
16970 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16971 SendToProgram(message, cps);
16975 EngineDefinedVariant (ChessProgramState *cps, int n)
16976 { // return name of n-th unknown variant that engine supports
16977 static char buf[MSG_SIZ];
16978 char *p, *s = cps->variants;
16979 if(!s) return NULL;
16980 do { // parse string from variants feature
16982 p = strchr(s, ',');
16983 if(p) *p = NULLCHAR;
16984 v = StringToVariant(s);
16985 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16986 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16987 if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16988 !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16989 !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16990 !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16991 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16994 if(n < 0) return buf;
17000 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17003 int len = strlen(name);
17006 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17008 sscanf(*p, "%d", &val);
17010 while (**p && **p != ' ')
17012 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17013 SendToProgram(buf, cps);
17020 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17023 int len = strlen(name);
17024 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17026 sscanf(*p, "%d", loc);
17027 while (**p && **p != ' ') (*p)++;
17028 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17029 SendToProgram(buf, cps);
17036 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17039 int len = strlen(name);
17040 if (strncmp((*p), name, len) == 0
17041 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17043 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17044 sscanf(*p, "%[^\"]", *loc);
17045 while (**p && **p != '\"') (*p)++;
17046 if (**p == '\"') (*p)++;
17047 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17048 SendToProgram(buf, cps);
17055 ParseOption (Option *opt, ChessProgramState *cps)
17056 // [HGM] options: process the string that defines an engine option, and determine
17057 // name, type, default value, and allowed value range
17059 char *p, *q, buf[MSG_SIZ];
17060 int n, min = (-1)<<31, max = 1<<31, def;
17062 opt->target = &opt->value; // OK for spin/slider and checkbox
17063 if(p = strstr(opt->name, " -spin ")) {
17064 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17065 if(max < min) max = min; // enforce consistency
17066 if(def < min) def = min;
17067 if(def > max) def = max;
17072 } else if((p = strstr(opt->name, " -slider "))) {
17073 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17074 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17075 if(max < min) max = min; // enforce consistency
17076 if(def < min) def = min;
17077 if(def > max) def = max;
17081 opt->type = Spin; // Slider;
17082 } else if((p = strstr(opt->name, " -string "))) {
17083 opt->textValue = p+9;
17084 opt->type = TextBox;
17085 opt->target = &opt->textValue;
17086 } else if((p = strstr(opt->name, " -file "))) {
17087 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17088 opt->target = opt->textValue = p+7;
17089 opt->type = FileName; // FileName;
17090 opt->target = &opt->textValue;
17091 } else if((p = strstr(opt->name, " -path "))) {
17092 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17093 opt->target = opt->textValue = p+7;
17094 opt->type = PathName; // PathName;
17095 opt->target = &opt->textValue;
17096 } else if(p = strstr(opt->name, " -check ")) {
17097 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17098 opt->value = (def != 0);
17099 opt->type = CheckBox;
17100 } else if(p = strstr(opt->name, " -combo ")) {
17101 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17102 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17103 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17104 opt->value = n = 0;
17105 while(q = StrStr(q, " /// ")) {
17106 n++; *q = 0; // count choices, and null-terminate each of them
17108 if(*q == '*') { // remember default, which is marked with * prefix
17112 cps->comboList[cps->comboCnt++] = q;
17114 cps->comboList[cps->comboCnt++] = NULL;
17116 opt->type = ComboBox;
17117 } else if(p = strstr(opt->name, " -button")) {
17118 opt->type = Button;
17119 } else if(p = strstr(opt->name, " -save")) {
17120 opt->type = SaveButton;
17121 } else return FALSE;
17122 *p = 0; // terminate option name
17123 // now look if the command-line options define a setting for this engine option.
17124 if(cps->optionSettings && cps->optionSettings[0])
17125 p = strstr(cps->optionSettings, opt->name); else p = NULL;
17126 if(p && (p == cps->optionSettings || p[-1] == ',')) {
17127 snprintf(buf, MSG_SIZ, "option %s", p);
17128 if(p = strstr(buf, ",")) *p = 0;
17129 if(q = strchr(buf, '=')) switch(opt->type) {
17131 for(n=0; n<opt->max; n++)
17132 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17135 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17139 opt->value = atoi(q+1);
17144 SendToProgram(buf, cps);
17150 FeatureDone (ChessProgramState *cps, int val)
17152 DelayedEventCallback cb = GetDelayedEvent();
17153 if ((cb == InitBackEnd3 && cps == &first) ||
17154 (cb == SettingsMenuIfReady && cps == &second) ||
17155 (cb == LoadEngine) ||
17156 (cb == TwoMachinesEventIfReady)) {
17157 CancelDelayedEvent();
17158 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17159 } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17160 cps->initDone = val;
17161 if(val) cps->reload = FALSE, RefreshSettingsDialog(cps, val);
17164 /* Parse feature command from engine */
17166 ParseFeatures (char *args, ChessProgramState *cps)
17174 while (*p == ' ') p++;
17175 if (*p == NULLCHAR) return;
17177 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17178 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17179 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17180 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17181 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17182 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17183 if (BoolFeature(&p, "reuse", &val, cps)) {
17184 /* Engine can disable reuse, but can't enable it if user said no */
17185 if (!val) cps->reuse = FALSE;
17188 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17189 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17190 if (gameMode == TwoMachinesPlay) {
17191 DisplayTwoMachinesTitle();
17197 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17198 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17199 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17200 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17201 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17202 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17203 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17204 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17205 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17206 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17207 if (IntFeature(&p, "done", &val, cps)) {
17208 FeatureDone(cps, val);
17211 /* Added by Tord: */
17212 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17213 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17214 /* End of additions by Tord */
17216 /* [HGM] added features: */
17217 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17218 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17219 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17220 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17221 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17222 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17223 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17224 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17225 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17226 FREE(cps->option[cps->nrOptions].name);
17227 cps->option[cps->nrOptions].name = q; q = NULL;
17228 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17229 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17230 SendToProgram(buf, cps);
17233 if(cps->nrOptions >= MAX_OPTIONS) {
17235 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17236 DisplayError(buf, 0);
17240 /* End of additions by HGM */
17242 /* unknown feature: complain and skip */
17244 while (*q && *q != '=') q++;
17245 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17246 SendToProgram(buf, cps);
17252 while (*p && *p != '\"') p++;
17253 if (*p == '\"') p++;
17255 while (*p && *p != ' ') p++;
17263 PeriodicUpdatesEvent (int newState)
17265 if (newState == appData.periodicUpdates)
17268 appData.periodicUpdates=newState;
17270 /* Display type changes, so update it now */
17271 // DisplayAnalysis();
17273 /* Get the ball rolling again... */
17275 AnalysisPeriodicEvent(1);
17276 StartAnalysisClock();
17281 PonderNextMoveEvent (int newState)
17283 if (newState == appData.ponderNextMove) return;
17284 if (gameMode == EditPosition) EditPositionDone(TRUE);
17286 SendToProgram("hard\n", &first);
17287 if (gameMode == TwoMachinesPlay) {
17288 SendToProgram("hard\n", &second);
17291 SendToProgram("easy\n", &first);
17292 thinkOutput[0] = NULLCHAR;
17293 if (gameMode == TwoMachinesPlay) {
17294 SendToProgram("easy\n", &second);
17297 appData.ponderNextMove = newState;
17301 NewSettingEvent (int option, int *feature, char *command, int value)
17305 if (gameMode == EditPosition) EditPositionDone(TRUE);
17306 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17307 if(feature == NULL || *feature) SendToProgram(buf, &first);
17308 if (gameMode == TwoMachinesPlay) {
17309 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17314 ShowThinkingEvent ()
17315 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17317 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17318 int newState = appData.showThinking
17319 // [HGM] thinking: other features now need thinking output as well
17320 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17322 if (oldState == newState) return;
17323 oldState = newState;
17324 if (gameMode == EditPosition) EditPositionDone(TRUE);
17326 SendToProgram("post\n", &first);
17327 if (gameMode == TwoMachinesPlay) {
17328 SendToProgram("post\n", &second);
17331 SendToProgram("nopost\n", &first);
17332 thinkOutput[0] = NULLCHAR;
17333 if (gameMode == TwoMachinesPlay) {
17334 SendToProgram("nopost\n", &second);
17337 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17341 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17343 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17344 if (pr == NoProc) return;
17345 AskQuestion(title, question, replyPrefix, pr);
17349 TypeInEvent (char firstChar)
17351 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17352 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17353 gameMode == AnalyzeMode || gameMode == EditGame ||
17354 gameMode == EditPosition || gameMode == IcsExamining ||
17355 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17356 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17357 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17358 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
17359 gameMode == Training) PopUpMoveDialog(firstChar);
17363 TypeInDoneEvent (char *move)
17366 int n, fromX, fromY, toX, toY;
17368 ChessMove moveType;
17371 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17372 EditPositionPasteFEN(move);
17375 // [HGM] movenum: allow move number to be typed in any mode
17376 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17380 // undocumented kludge: allow command-line option to be typed in!
17381 // (potentially fatal, and does not implement the effect of the option.)
17382 // should only be used for options that are values on which future decisions will be made,
17383 // and definitely not on options that would be used during initialization.
17384 if(strstr(move, "!!! -") == move) {
17385 ParseArgsFromString(move+4);
17389 if (gameMode != EditGame && currentMove != forwardMostMove &&
17390 gameMode != Training) {
17391 DisplayMoveError(_("Displayed move is not current"));
17393 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17394 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17395 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17396 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17397 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17398 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17400 DisplayMoveError(_("Could not parse move"));
17406 DisplayMove (int moveNumber)
17408 char message[MSG_SIZ];
17410 char cpThinkOutput[MSG_SIZ];
17412 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17414 if (moveNumber == forwardMostMove - 1 ||
17415 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17417 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17419 if (strchr(cpThinkOutput, '\n')) {
17420 *strchr(cpThinkOutput, '\n') = NULLCHAR;
17423 *cpThinkOutput = NULLCHAR;
17426 /* [AS] Hide thinking from human user */
17427 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17428 *cpThinkOutput = NULLCHAR;
17429 if( thinkOutput[0] != NULLCHAR ) {
17432 for( i=0; i<=hiddenThinkOutputState; i++ ) {
17433 cpThinkOutput[i] = '.';
17435 cpThinkOutput[i] = NULLCHAR;
17436 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17440 if (moveNumber == forwardMostMove - 1 &&
17441 gameInfo.resultDetails != NULL) {
17442 if (gameInfo.resultDetails[0] == NULLCHAR) {
17443 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17445 snprintf(res, MSG_SIZ, " {%s} %s",
17446 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17452 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17453 DisplayMessage(res, cpThinkOutput);
17455 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17456 WhiteOnMove(moveNumber) ? " " : ".. ",
17457 parseList[moveNumber], res);
17458 DisplayMessage(message, cpThinkOutput);
17463 DisplayComment (int moveNumber, char *text)
17465 char title[MSG_SIZ];
17467 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17468 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17470 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17471 WhiteOnMove(moveNumber) ? " " : ".. ",
17472 parseList[moveNumber]);
17474 if (text != NULL && (appData.autoDisplayComment || commentUp))
17475 CommentPopUp(title, text);
17478 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17479 * might be busy thinking or pondering. It can be omitted if your
17480 * gnuchess is configured to stop thinking immediately on any user
17481 * input. However, that gnuchess feature depends on the FIONREAD
17482 * ioctl, which does not work properly on some flavors of Unix.
17485 Attention (ChessProgramState *cps)
17488 if (!cps->useSigint) return;
17489 if (appData.noChessProgram || (cps->pr == NoProc)) return;
17490 switch (gameMode) {
17491 case MachinePlaysWhite:
17492 case MachinePlaysBlack:
17493 case TwoMachinesPlay:
17494 case IcsPlayingWhite:
17495 case IcsPlayingBlack:
17498 /* Skip if we know it isn't thinking */
17499 if (!cps->maybeThinking) return;
17500 if (appData.debugMode)
17501 fprintf(debugFP, "Interrupting %s\n", cps->which);
17502 InterruptChildProcess(cps->pr);
17503 cps->maybeThinking = FALSE;
17508 #endif /*ATTENTION*/
17514 if (whiteTimeRemaining <= 0) {
17517 if (appData.icsActive) {
17518 if (appData.autoCallFlag &&
17519 gameMode == IcsPlayingBlack && !blackFlag) {
17520 SendToICS(ics_prefix);
17521 SendToICS("flag\n");
17525 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17527 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17528 if (appData.autoCallFlag) {
17529 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17536 if (blackTimeRemaining <= 0) {
17539 if (appData.icsActive) {
17540 if (appData.autoCallFlag &&
17541 gameMode == IcsPlayingWhite && !whiteFlag) {
17542 SendToICS(ics_prefix);
17543 SendToICS("flag\n");
17547 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17549 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17550 if (appData.autoCallFlag) {
17551 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17562 CheckTimeControl ()
17564 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17565 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17568 * add time to clocks when time control is achieved ([HGM] now also used for increment)
17570 if ( !WhiteOnMove(forwardMostMove) ) {
17571 /* White made time control */
17572 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17573 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17574 /* [HGM] time odds: correct new time quota for time odds! */
17575 / WhitePlayer()->timeOdds;
17576 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17578 lastBlack -= blackTimeRemaining;
17579 /* Black made time control */
17580 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17581 / WhitePlayer()->other->timeOdds;
17582 lastWhite = whiteTimeRemaining;
17587 DisplayBothClocks ()
17589 int wom = gameMode == EditPosition ?
17590 !blackPlaysFirst : WhiteOnMove(currentMove);
17591 DisplayWhiteClock(whiteTimeRemaining, wom);
17592 DisplayBlackClock(blackTimeRemaining, !wom);
17596 /* Timekeeping seems to be a portability nightmare. I think everyone
17597 has ftime(), but I'm really not sure, so I'm including some ifdefs
17598 to use other calls if you don't. Clocks will be less accurate if
17599 you have neither ftime nor gettimeofday.
17602 /* VS 2008 requires the #include outside of the function */
17603 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17604 #include <sys/timeb.h>
17607 /* Get the current time as a TimeMark */
17609 GetTimeMark (TimeMark *tm)
17611 #if HAVE_GETTIMEOFDAY
17613 struct timeval timeVal;
17614 struct timezone timeZone;
17616 gettimeofday(&timeVal, &timeZone);
17617 tm->sec = (long) timeVal.tv_sec;
17618 tm->ms = (int) (timeVal.tv_usec / 1000L);
17620 #else /*!HAVE_GETTIMEOFDAY*/
17623 // include <sys/timeb.h> / moved to just above start of function
17624 struct timeb timeB;
17627 tm->sec = (long) timeB.time;
17628 tm->ms = (int) timeB.millitm;
17630 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17631 tm->sec = (long) time(NULL);
17637 /* Return the difference in milliseconds between two
17638 time marks. We assume the difference will fit in a long!
17641 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17643 return 1000L*(tm2->sec - tm1->sec) +
17644 (long) (tm2->ms - tm1->ms);
17649 * Code to manage the game clocks.
17651 * In tournament play, black starts the clock and then white makes a move.
17652 * We give the human user a slight advantage if he is playing white---the
17653 * clocks don't run until he makes his first move, so it takes zero time.
17654 * Also, we don't account for network lag, so we could get out of sync
17655 * with GNU Chess's clock -- but then, referees are always right.
17658 static TimeMark tickStartTM;
17659 static long intendedTickLength;
17662 NextTickLength (long timeRemaining)
17664 long nominalTickLength, nextTickLength;
17666 if (timeRemaining > 0L && timeRemaining <= 10000L)
17667 nominalTickLength = 100L;
17669 nominalTickLength = 1000L;
17670 nextTickLength = timeRemaining % nominalTickLength;
17671 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17673 return nextTickLength;
17676 /* Adjust clock one minute up or down */
17678 AdjustClock (Boolean which, int dir)
17680 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17681 if(which) blackTimeRemaining += 60000*dir;
17682 else whiteTimeRemaining += 60000*dir;
17683 DisplayBothClocks();
17684 adjustedClock = TRUE;
17687 /* Stop clocks and reset to a fresh time control */
17691 (void) StopClockTimer();
17692 if (appData.icsActive) {
17693 whiteTimeRemaining = blackTimeRemaining = 0;
17694 } else if (searchTime) {
17695 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17696 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17697 } else { /* [HGM] correct new time quote for time odds */
17698 whiteTC = blackTC = fullTimeControlString;
17699 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17700 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17702 if (whiteFlag || blackFlag) {
17704 whiteFlag = blackFlag = FALSE;
17706 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17707 DisplayBothClocks();
17708 adjustedClock = FALSE;
17711 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17713 /* Decrement running clock by amount of time that has passed */
17717 long timeRemaining;
17718 long lastTickLength, fudge;
17721 if (!appData.clockMode) return;
17722 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17726 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17728 /* Fudge if we woke up a little too soon */
17729 fudge = intendedTickLength - lastTickLength;
17730 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17732 if (WhiteOnMove(forwardMostMove)) {
17733 if(whiteNPS >= 0) lastTickLength = 0;
17734 timeRemaining = whiteTimeRemaining -= lastTickLength;
17735 if(timeRemaining < 0 && !appData.icsActive) {
17736 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17737 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17738 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17739 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17742 DisplayWhiteClock(whiteTimeRemaining - fudge,
17743 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17745 if(blackNPS >= 0) lastTickLength = 0;
17746 timeRemaining = blackTimeRemaining -= lastTickLength;
17747 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17748 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17750 blackStartMove = forwardMostMove;
17751 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17754 DisplayBlackClock(blackTimeRemaining - fudge,
17755 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17757 if (CheckFlags()) return;
17759 if(twoBoards) { // count down secondary board's clocks as well
17760 activePartnerTime -= lastTickLength;
17762 if(activePartner == 'W')
17763 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17765 DisplayBlackClock(activePartnerTime, TRUE);
17770 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17771 StartClockTimer(intendedTickLength);
17773 /* if the time remaining has fallen below the alarm threshold, sound the
17774 * alarm. if the alarm has sounded and (due to a takeback or time control
17775 * with increment) the time remaining has increased to a level above the
17776 * threshold, reset the alarm so it can sound again.
17779 if (appData.icsActive && appData.icsAlarm) {
17781 /* make sure we are dealing with the user's clock */
17782 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17783 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17786 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17787 alarmSounded = FALSE;
17788 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17790 alarmSounded = TRUE;
17796 /* A player has just moved, so stop the previously running
17797 clock and (if in clock mode) start the other one.
17798 We redisplay both clocks in case we're in ICS mode, because
17799 ICS gives us an update to both clocks after every move.
17800 Note that this routine is called *after* forwardMostMove
17801 is updated, so the last fractional tick must be subtracted
17802 from the color that is *not* on move now.
17805 SwitchClocks (int newMoveNr)
17807 long lastTickLength;
17809 int flagged = FALSE;
17813 if (StopClockTimer() && appData.clockMode) {
17814 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17815 if (!WhiteOnMove(forwardMostMove)) {
17816 if(blackNPS >= 0) lastTickLength = 0;
17817 blackTimeRemaining -= lastTickLength;
17818 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17819 // if(pvInfoList[forwardMostMove].time == -1)
17820 pvInfoList[forwardMostMove].time = // use GUI time
17821 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17823 if(whiteNPS >= 0) lastTickLength = 0;
17824 whiteTimeRemaining -= lastTickLength;
17825 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17826 // if(pvInfoList[forwardMostMove].time == -1)
17827 pvInfoList[forwardMostMove].time =
17828 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17830 flagged = CheckFlags();
17832 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17833 CheckTimeControl();
17835 if (flagged || !appData.clockMode) return;
17837 switch (gameMode) {
17838 case MachinePlaysBlack:
17839 case MachinePlaysWhite:
17840 case BeginningOfGame:
17841 if (pausing) return;
17845 case PlayFromGameFile:
17853 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17854 if(WhiteOnMove(forwardMostMove))
17855 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17856 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17860 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17861 whiteTimeRemaining : blackTimeRemaining);
17862 StartClockTimer(intendedTickLength);
17866 /* Stop both clocks */
17870 long lastTickLength;
17873 if (!StopClockTimer()) return;
17874 if (!appData.clockMode) return;
17878 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17879 if (WhiteOnMove(forwardMostMove)) {
17880 if(whiteNPS >= 0) lastTickLength = 0;
17881 whiteTimeRemaining -= lastTickLength;
17882 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17884 if(blackNPS >= 0) lastTickLength = 0;
17885 blackTimeRemaining -= lastTickLength;
17886 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17891 /* Start clock of player on move. Time may have been reset, so
17892 if clock is already running, stop and restart it. */
17896 (void) StopClockTimer(); /* in case it was running already */
17897 DisplayBothClocks();
17898 if (CheckFlags()) return;
17900 if (!appData.clockMode) return;
17901 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17903 GetTimeMark(&tickStartTM);
17904 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17905 whiteTimeRemaining : blackTimeRemaining);
17907 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17908 whiteNPS = blackNPS = -1;
17909 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17910 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17911 whiteNPS = first.nps;
17912 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17913 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17914 blackNPS = first.nps;
17915 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17916 whiteNPS = second.nps;
17917 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17918 blackNPS = second.nps;
17919 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17921 StartClockTimer(intendedTickLength);
17925 TimeString (long ms)
17927 long second, minute, hour, day;
17929 static char buf[32];
17931 if (ms > 0 && ms <= 9900) {
17932 /* convert milliseconds to tenths, rounding up */
17933 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17935 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17939 /* convert milliseconds to seconds, rounding up */
17940 /* use floating point to avoid strangeness of integer division
17941 with negative dividends on many machines */
17942 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17949 day = second / (60 * 60 * 24);
17950 second = second % (60 * 60 * 24);
17951 hour = second / (60 * 60);
17952 second = second % (60 * 60);
17953 minute = second / 60;
17954 second = second % 60;
17957 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17958 sign, day, hour, minute, second);
17960 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17962 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17969 * This is necessary because some C libraries aren't ANSI C compliant yet.
17972 StrStr (char *string, char *match)
17976 length = strlen(match);
17978 for (i = strlen(string) - length; i >= 0; i--, string++)
17979 if (!strncmp(match, string, length))
17986 StrCaseStr (char *string, char *match)
17990 length = strlen(match);
17992 for (i = strlen(string) - length; i >= 0; i--, string++) {
17993 for (j = 0; j < length; j++) {
17994 if (ToLower(match[j]) != ToLower(string[j]))
17997 if (j == length) return string;
18005 StrCaseCmp (char *s1, char *s2)
18010 c1 = ToLower(*s1++);
18011 c2 = ToLower(*s2++);
18012 if (c1 > c2) return 1;
18013 if (c1 < c2) return -1;
18014 if (c1 == NULLCHAR) return 0;
18022 return isupper(c) ? tolower(c) : c;
18029 return islower(c) ? toupper(c) : c;
18031 #endif /* !_amigados */
18038 if ((ret = (char *) malloc(strlen(s) + 1)))
18040 safeStrCpy(ret, s, strlen(s)+1);
18046 StrSavePtr (char *s, char **savePtr)
18051 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18052 safeStrCpy(*savePtr, s, strlen(s)+1);
18064 clock = time((time_t *)NULL);
18065 tm = localtime(&clock);
18066 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18067 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18068 return StrSave(buf);
18073 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18075 int i, j, fromX, fromY, toX, toY;
18076 int whiteToPlay, haveRights = nrCastlingRights;
18082 whiteToPlay = (gameMode == EditPosition) ?
18083 !blackPlaysFirst : (move % 2 == 0);
18086 /* Piece placement data */
18087 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18088 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18090 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18091 if (boards[move][i][j] == EmptySquare) {
18093 } else { ChessSquare piece = boards[move][i][j];
18094 if (emptycount > 0) {
18095 if(emptycount<10) /* [HGM] can be >= 10 */
18096 *p++ = '0' + emptycount;
18097 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18100 if(PieceToChar(piece) == '+') {
18101 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18103 piece = (ChessSquare)(CHUDEMOTED(piece));
18105 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18106 if(*p = PieceSuffix(piece)) p++;
18108 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18109 p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18114 if (emptycount > 0) {
18115 if(emptycount<10) /* [HGM] can be >= 10 */
18116 *p++ = '0' + emptycount;
18117 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18124 /* [HGM] print Crazyhouse or Shogi holdings */
18125 if( gameInfo.holdingsWidth ) {
18126 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18128 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18129 piece = boards[move][i][BOARD_WIDTH-1];
18130 if( piece != EmptySquare )
18131 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18132 *p++ = PieceToChar(piece);
18134 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18135 piece = boards[move][BOARD_HEIGHT-i-1][0];
18136 if( piece != EmptySquare )
18137 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18138 *p++ = PieceToChar(piece);
18141 if( q == p ) *p++ = '-';
18147 *p++ = whiteToPlay ? 'w' : 'b';
18150 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18151 haveRights = 0; q = p;
18152 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18153 piece = boards[move][0][i];
18154 if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18155 if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18158 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18159 piece = boards[move][BOARD_HEIGHT-1][i];
18160 if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18161 if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18164 if(p == q) *p++ = '-';
18168 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18169 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18172 int handW=0, handB=0;
18173 if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18174 for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18175 for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18178 if(appData.fischerCastling) {
18179 if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18180 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18181 if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18183 /* [HGM] write directly from rights */
18184 if(boards[move][CASTLING][2] != NoRights &&
18185 boards[move][CASTLING][0] != NoRights )
18186 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18187 if(boards[move][CASTLING][2] != NoRights &&
18188 boards[move][CASTLING][1] != NoRights )
18189 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18192 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18193 if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18195 if(boards[move][CASTLING][5] != NoRights &&
18196 boards[move][CASTLING][3] != NoRights )
18197 *p++ = boards[move][CASTLING][3] + AAA;
18198 if(boards[move][CASTLING][5] != NoRights &&
18199 boards[move][CASTLING][4] != NoRights )
18200 *p++ = boards[move][CASTLING][4] + AAA;
18204 /* [HGM] write true castling rights */
18205 if( nrCastlingRights == 6 ) {
18207 if(boards[move][CASTLING][0] != NoRights &&
18208 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
18209 q = (boards[move][CASTLING][1] != NoRights &&
18210 boards[move][CASTLING][2] != NoRights );
18211 if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18212 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18213 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18214 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18218 if(boards[move][CASTLING][3] != NoRights &&
18219 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
18220 q = (boards[move][CASTLING][4] != NoRights &&
18221 boards[move][CASTLING][5] != NoRights );
18223 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18224 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18225 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18230 if (q == p) *p++ = '-'; /* No castling rights */
18234 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18235 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18236 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18237 /* En passant target square */
18238 if (move > backwardMostMove) {
18239 fromX = moveList[move - 1][0] - AAA;
18240 fromY = moveList[move - 1][1] - ONE;
18241 toX = moveList[move - 1][2] - AAA;
18242 toY = moveList[move - 1][3] - ONE;
18243 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18244 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18245 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18247 /* 2-square pawn move just happened */
18249 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18253 } else if(move == backwardMostMove) {
18254 // [HGM] perhaps we should always do it like this, and forget the above?
18255 if((signed char)boards[move][EP_STATUS] >= 0) {
18256 *p++ = boards[move][EP_STATUS] + AAA;
18257 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18269 { int i = 0, j=move;
18271 /* [HGM] find reversible plies */
18272 if (appData.debugMode) { int k;
18273 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18274 for(k=backwardMostMove; k<=forwardMostMove; k++)
18275 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18279 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18280 if( j == backwardMostMove ) i += initialRulePlies;
18281 sprintf(p, "%d ", i);
18282 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18284 /* Fullmove number */
18285 sprintf(p, "%d", (move / 2) + 1);
18286 } else *--p = NULLCHAR;
18288 return StrSave(buf);
18292 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18294 int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18296 int emptycount, virgin[BOARD_FILES];
18297 ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18301 /* Piece placement data */
18302 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18305 if (*p == '/' || *p == ' ' || *p == '[' ) {
18307 emptycount = gameInfo.boardWidth - j;
18308 while (emptycount--)
18309 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18310 if (*p == '/') p++;
18311 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18312 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18313 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18315 appData.NrRanks = gameInfo.boardHeight - i; i=0;
18318 #if(BOARD_FILES >= 10)*0
18319 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18320 p++; emptycount=10;
18321 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18322 while (emptycount--)
18323 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18325 } else if (*p == '*') {
18326 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18327 } else if (isdigit(*p)) {
18328 emptycount = *p++ - '0';
18329 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18330 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18331 while (emptycount--)
18332 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18333 } else if (*p == '<') {
18334 if(i == BOARD_HEIGHT-1) shuffle = 1;
18335 else if (i != 0 || !shuffle) return FALSE;
18337 } else if (shuffle && *p == '>') {
18338 p++; // for now ignore closing shuffle range, and assume rank-end
18339 } else if (*p == '?') {
18340 if (j >= gameInfo.boardWidth) return FALSE;
18341 if (i != 0 && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18342 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18343 } else if (*p == '+' || isalpha(*p)) {
18344 char *q, *s = SUFFIXES;
18345 if (j >= gameInfo.boardWidth) return FALSE;
18348 if(q = strchr(s, p[1])) p++;
18349 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18350 if(piece == EmptySquare) return FALSE; /* unknown piece */
18351 piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18352 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18355 if(q = strchr(s, *p)) p++;
18356 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18359 if(piece==EmptySquare) return FALSE; /* unknown piece */
18360 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18361 piece = (ChessSquare) (PROMOTED(piece));
18362 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18365 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18366 if(piece == king) wKingRank = i;
18367 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18373 while (*p == '/' || *p == ' ') p++;
18375 if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18377 /* [HGM] by default clear Crazyhouse holdings, if present */
18378 if(gameInfo.holdingsWidth) {
18379 for(i=0; i<BOARD_HEIGHT; i++) {
18380 board[i][0] = EmptySquare; /* black holdings */
18381 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18382 board[i][1] = (ChessSquare) 0; /* black counts */
18383 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18387 /* [HGM] look for Crazyhouse holdings here */
18388 while(*p==' ') p++;
18389 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18390 int swap=0, wcnt=0, bcnt=0;
18392 if(*p == '<') swap++, p++;
18393 if(*p == '-' ) p++; /* empty holdings */ else {
18394 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18395 /* if we would allow FEN reading to set board size, we would */
18396 /* have to add holdings and shift the board read so far here */
18397 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18399 if((int) piece >= (int) BlackPawn ) {
18400 i = (int)piece - (int)BlackPawn;
18401 i = PieceToNumber((ChessSquare)i);
18402 if( i >= gameInfo.holdingsSize ) return FALSE;
18403 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18404 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
18407 i = (int)piece - (int)WhitePawn;
18408 i = PieceToNumber((ChessSquare)i);
18409 if( i >= gameInfo.holdingsSize ) return FALSE;
18410 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
18411 board[i][BOARD_WIDTH-2]++; /* black holdings */
18415 if(subst) { // substitute back-rank question marks by holdings pieces
18416 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18417 int k, m, n = bcnt + 1;
18418 if(board[0][j] == ClearBoard) {
18419 if(!wcnt) return FALSE;
18421 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18422 board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18423 if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18427 if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18428 if(!bcnt) return FALSE;
18429 if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18430 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18431 board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18432 if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18443 if(subst) return FALSE; // substitution requested, but no holdings
18445 while(*p == ' ') p++;
18449 if(appData.colorNickNames) {
18450 if( c == appData.colorNickNames[0] ) c = 'w'; else
18451 if( c == appData.colorNickNames[1] ) c = 'b';
18455 *blackPlaysFirst = FALSE;
18458 *blackPlaysFirst = TRUE;
18464 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18465 /* return the extra info in global variiables */
18467 while(*p==' ') p++;
18469 if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18470 if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18471 if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18474 /* set defaults in case FEN is incomplete */
18475 board[EP_STATUS] = EP_UNKNOWN;
18476 board[TOUCHED_W] = board[TOUCHED_B] = 0;
18477 for(i=0; i<nrCastlingRights; i++ ) {
18478 board[CASTLING][i] =
18479 appData.fischerCastling ? NoRights : initialRights[i];
18480 } /* assume possible unless obviously impossible */
18481 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18482 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18483 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18484 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18485 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18486 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18487 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18488 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18491 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18494 while(isalpha(*p)) {
18495 if(isupper(*p)) w |= 1 << (*p++ - 'A');
18496 if(islower(*p)) b |= 1 << (*p++ - 'a');
18500 board[TOUCHED_W] = ~w;
18501 board[TOUCHED_B] = ~b;
18502 while(*p == ' ') p++;
18506 if(nrCastlingRights) {
18508 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18509 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18510 /* castling indicator present, so default becomes no castlings */
18511 for(i=0; i<nrCastlingRights; i++ ) {
18512 board[CASTLING][i] = NoRights;
18515 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18516 (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18517 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18518 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
18519 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18521 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18522 if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18523 if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18525 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18526 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18527 if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18528 && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18529 if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18530 && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18533 for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18534 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18535 board[CASTLING][2] = whiteKingFile;
18536 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18537 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18538 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18541 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18542 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18543 board[CASTLING][2] = whiteKingFile;
18544 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18545 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18546 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18549 for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18550 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18551 board[CASTLING][5] = blackKingFile;
18552 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18553 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18554 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18557 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18558 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18559 board[CASTLING][5] = blackKingFile;
18560 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18561 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18562 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18565 default: /* FRC castlings */
18566 if(c >= 'a') { /* black rights */
18567 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18568 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18569 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18570 if(i == BOARD_RGHT) break;
18571 board[CASTLING][5] = i;
18573 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
18574 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
18576 board[CASTLING][3] = c;
18578 board[CASTLING][4] = c;
18579 } else { /* white rights */
18580 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18581 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18582 if(board[0][i] == WhiteKing) break;
18583 if(i == BOARD_RGHT) break;
18584 board[CASTLING][2] = i;
18585 c -= AAA - 'a' + 'A';
18586 if(board[0][c] >= WhiteKing) break;
18588 board[CASTLING][0] = c;
18590 board[CASTLING][1] = c;
18594 for(i=0; i<nrCastlingRights; i++)
18595 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18596 if(gameInfo.variant == VariantSChess)
18597 for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18598 if(fischer && shuffle) appData.fischerCastling = TRUE;
18599 if (appData.debugMode) {
18600 fprintf(debugFP, "FEN castling rights:");
18601 for(i=0; i<nrCastlingRights; i++)
18602 fprintf(debugFP, " %d", board[CASTLING][i]);
18603 fprintf(debugFP, "\n");
18606 while(*p==' ') p++;
18609 if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18611 /* read e.p. field in games that know e.p. capture */
18612 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18613 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18614 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18616 p++; board[EP_STATUS] = EP_NONE;
18618 char c = *p++ - AAA;
18620 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18621 if(*p >= '0' && *p <='9') p++;
18622 board[EP_STATUS] = c;
18627 if(sscanf(p, "%d", &i) == 1) {
18628 FENrulePlies = i; /* 50-move ply counter */
18629 /* (The move number is still ignored) */
18636 EditPositionPasteFEN (char *fen)
18639 Board initial_position;
18641 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18642 DisplayError(_("Bad FEN position in clipboard"), 0);
18645 int savedBlackPlaysFirst = blackPlaysFirst;
18646 EditPositionEvent();
18647 blackPlaysFirst = savedBlackPlaysFirst;
18648 CopyBoard(boards[0], initial_position);
18649 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18650 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18651 DisplayBothClocks();
18652 DrawPosition(FALSE, boards[currentMove]);
18657 static char cseq[12] = "\\ ";
18660 set_cont_sequence (char *new_seq)
18665 // handle bad attempts to set the sequence
18667 return 0; // acceptable error - no debug
18669 len = strlen(new_seq);
18670 ret = (len > 0) && (len < sizeof(cseq));
18672 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18673 else if (appData.debugMode)
18674 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18679 reformat a source message so words don't cross the width boundary. internal
18680 newlines are not removed. returns the wrapped size (no null character unless
18681 included in source message). If dest is NULL, only calculate the size required
18682 for the dest buffer. lp argument indicats line position upon entry, and it's
18683 passed back upon exit.
18686 wrap (char *dest, char *src, int count, int width, int *lp)
18688 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18690 cseq_len = strlen(cseq);
18691 old_line = line = *lp;
18692 ansi = len = clen = 0;
18694 for (i=0; i < count; i++)
18696 if (src[i] == '\033')
18699 // if we hit the width, back up
18700 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18702 // store i & len in case the word is too long
18703 old_i = i, old_len = len;
18705 // find the end of the last word
18706 while (i && src[i] != ' ' && src[i] != '\n')
18712 // word too long? restore i & len before splitting it
18713 if ((old_i-i+clen) >= width)
18720 if (i && src[i-1] == ' ')
18723 if (src[i] != ' ' && src[i] != '\n')
18730 // now append the newline and continuation sequence
18735 strncpy(dest+len, cseq, cseq_len);
18743 dest[len] = src[i];
18747 if (src[i] == '\n')
18752 if (dest && appData.debugMode)
18754 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18755 count, width, line, len, *lp);
18756 show_bytes(debugFP, src, count);
18757 fprintf(debugFP, "\ndest: ");
18758 show_bytes(debugFP, dest, len);
18759 fprintf(debugFP, "\n");
18761 *lp = dest ? line : old_line;
18766 // [HGM] vari: routines for shelving variations
18767 Boolean modeRestore = FALSE;
18770 PushInner (int firstMove, int lastMove)
18772 int i, j, nrMoves = lastMove - firstMove;
18774 // push current tail of game on stack
18775 savedResult[storedGames] = gameInfo.result;
18776 savedDetails[storedGames] = gameInfo.resultDetails;
18777 gameInfo.resultDetails = NULL;
18778 savedFirst[storedGames] = firstMove;
18779 savedLast [storedGames] = lastMove;
18780 savedFramePtr[storedGames] = framePtr;
18781 framePtr -= nrMoves; // reserve space for the boards
18782 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18783 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18784 for(j=0; j<MOVE_LEN; j++)
18785 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18786 for(j=0; j<2*MOVE_LEN; j++)
18787 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18788 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18789 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18790 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18791 pvInfoList[firstMove+i-1].depth = 0;
18792 commentList[framePtr+i] = commentList[firstMove+i];
18793 commentList[firstMove+i] = NULL;
18797 forwardMostMove = firstMove; // truncate game so we can start variation
18801 PushTail (int firstMove, int lastMove)
18803 if(appData.icsActive) { // only in local mode
18804 forwardMostMove = currentMove; // mimic old ICS behavior
18807 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18809 PushInner(firstMove, lastMove);
18810 if(storedGames == 1) GreyRevert(FALSE);
18811 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18815 PopInner (Boolean annotate)
18818 char buf[8000], moveBuf[20];
18820 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18821 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18822 nrMoves = savedLast[storedGames] - currentMove;
18825 if(!WhiteOnMove(currentMove))
18826 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18827 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18828 for(i=currentMove; i<forwardMostMove; i++) {
18830 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18831 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18832 strcat(buf, moveBuf);
18833 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18834 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18838 for(i=1; i<=nrMoves; i++) { // copy last variation back
18839 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18840 for(j=0; j<MOVE_LEN; j++)
18841 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18842 for(j=0; j<2*MOVE_LEN; j++)
18843 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18844 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18845 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18846 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18847 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18848 commentList[currentMove+i] = commentList[framePtr+i];
18849 commentList[framePtr+i] = NULL;
18851 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18852 framePtr = savedFramePtr[storedGames];
18853 gameInfo.result = savedResult[storedGames];
18854 if(gameInfo.resultDetails != NULL) {
18855 free(gameInfo.resultDetails);
18857 gameInfo.resultDetails = savedDetails[storedGames];
18858 forwardMostMove = currentMove + nrMoves;
18862 PopTail (Boolean annotate)
18864 if(appData.icsActive) return FALSE; // only in local mode
18865 if(!storedGames) return FALSE; // sanity
18866 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18868 PopInner(annotate);
18869 if(currentMove < forwardMostMove) ForwardEvent(); else
18870 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18872 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18878 { // remove all shelved variations
18880 for(i=0; i<storedGames; i++) {
18881 if(savedDetails[i])
18882 free(savedDetails[i]);
18883 savedDetails[i] = NULL;
18885 for(i=framePtr; i<MAX_MOVES; i++) {
18886 if(commentList[i]) free(commentList[i]);
18887 commentList[i] = NULL;
18889 framePtr = MAX_MOVES-1;
18894 LoadVariation (int index, char *text)
18895 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18896 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18897 int level = 0, move;
18899 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18900 // first find outermost bracketing variation
18901 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18902 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18903 if(*p == '{') wait = '}'; else
18904 if(*p == '[') wait = ']'; else
18905 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18906 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18908 if(*p == wait) wait = NULLCHAR; // closing ]} found
18911 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18912 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18913 end[1] = NULLCHAR; // clip off comment beyond variation
18914 ToNrEvent(currentMove-1);
18915 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18916 // kludge: use ParsePV() to append variation to game
18917 move = currentMove;
18918 ParsePV(start, TRUE, TRUE);
18919 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18920 ClearPremoveHighlights();
18922 ToNrEvent(currentMove+1);
18928 char *p, *q, buf[MSG_SIZ];
18929 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18930 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18931 ParseArgsFromString(buf);
18932 ActivateTheme(TRUE); // also redo colors
18936 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18939 q = appData.themeNames;
18940 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18941 if(appData.useBitmaps) {
18942 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18943 appData.liteBackTextureFile, appData.darkBackTextureFile,
18944 appData.liteBackTextureMode,
18945 appData.darkBackTextureMode );
18947 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18948 Col2Text(2), // lightSquareColor
18949 Col2Text(3) ); // darkSquareColor
18951 if(appData.useBorder) {
18952 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18955 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18957 if(appData.useFont) {
18958 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18959 appData.renderPiecesWithFont,
18960 appData.fontToPieceTable,
18961 Col2Text(9), // appData.fontBackColorWhite
18962 Col2Text(10) ); // appData.fontForeColorBlack
18964 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18965 appData.pieceDirectory);
18966 if(!appData.pieceDirectory[0])
18967 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18968 Col2Text(0), // whitePieceColor
18969 Col2Text(1) ); // blackPieceColor
18971 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18972 Col2Text(4), // highlightSquareColor
18973 Col2Text(5) ); // premoveHighlightColor
18974 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18975 if(insert != q) insert[-1] = NULLCHAR;
18976 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18979 ActivateTheme(FALSE);