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;
6463 CopyBoard(boards[0], initialPosition);
6465 if(oldx != gameInfo.boardWidth ||
6466 oldy != gameInfo.boardHeight ||
6467 oldv != gameInfo.variant ||
6468 oldh != gameInfo.holdingsWidth
6470 InitDrawingSizes(-2 ,0);
6472 oldv = gameInfo.variant;
6474 DrawPosition(TRUE, boards[currentMove]);
6478 SendBoard (ChessProgramState *cps, int moveNum)
6480 char message[MSG_SIZ];
6482 if (cps->useSetboard) {
6483 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6484 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6485 SendToProgram(message, cps);
6490 int i, j, left=0, right=BOARD_WIDTH;
6491 /* Kludge to set black to move, avoiding the troublesome and now
6492 * deprecated "black" command.
6494 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6495 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6497 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6499 SendToProgram("edit\n", cps);
6500 SendToProgram("#\n", cps);
6501 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6502 bp = &boards[moveNum][i][left];
6503 for (j = left; j < right; j++, bp++) {
6504 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6505 if ((int) *bp < (int) BlackPawn) {
6506 if(j == BOARD_RGHT+1)
6507 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6508 else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6509 if(message[0] == '+' || message[0] == '~') {
6510 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6511 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6512 AAA + j, ONE + i - '0');
6514 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6515 message[1] = BOARD_RGHT - 1 - j + '1';
6516 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6518 SendToProgram(message, cps);
6523 SendToProgram("c\n", cps);
6524 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6525 bp = &boards[moveNum][i][left];
6526 for (j = left; j < right; j++, bp++) {
6527 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6528 if (((int) *bp != (int) EmptySquare)
6529 && ((int) *bp >= (int) BlackPawn)) {
6530 if(j == BOARD_LEFT-2)
6531 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6532 else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6533 AAA + j, ONE + i - '0');
6534 if(message[0] == '+' || message[0] == '~') {
6535 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6536 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6537 AAA + j, ONE + i - '0');
6539 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6540 message[1] = BOARD_RGHT - 1 - j + '1';
6541 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6543 SendToProgram(message, cps);
6548 SendToProgram(".\n", cps);
6550 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6553 char exclusionHeader[MSG_SIZ];
6554 int exCnt, excludePtr;
6555 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6556 static Exclusion excluTab[200];
6557 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6563 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6564 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6570 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6571 excludePtr = 24; exCnt = 0;
6576 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6577 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6578 char buf[2*MOVE_LEN], *p;
6579 Exclusion *e = excluTab;
6581 for(i=0; i<exCnt; i++)
6582 if(e[i].ff == fromX && e[i].fr == fromY &&
6583 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6584 if(i == exCnt) { // was not in exclude list; add it
6585 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6586 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6587 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6590 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6591 excludePtr++; e[i].mark = excludePtr++;
6592 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6595 exclusionHeader[e[i].mark] = state;
6599 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6600 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6604 if((signed char)promoChar == -1) { // kludge to indicate best move
6605 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6606 return 1; // if unparsable, abort
6608 // update exclusion map (resolving toggle by consulting existing state)
6609 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6611 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6612 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6613 excludeMap[k] |= 1<<j;
6614 else excludeMap[k] &= ~(1<<j);
6616 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6618 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6619 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6621 return (state == '+');
6625 ExcludeClick (int index)
6628 Exclusion *e = excluTab;
6629 if(index < 25) { // none, best or tail clicked
6630 if(index < 13) { // none: include all
6631 WriteMap(0); // clear map
6632 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6633 SendToBoth("include all\n"); // and inform engine
6634 } else if(index > 18) { // tail
6635 if(exclusionHeader[19] == '-') { // tail was excluded
6636 SendToBoth("include all\n");
6637 WriteMap(0); // clear map completely
6638 // now re-exclude selected moves
6639 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6640 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6641 } else { // tail was included or in mixed state
6642 SendToBoth("exclude all\n");
6643 WriteMap(0xFF); // fill map completely
6644 // now re-include selected moves
6645 j = 0; // count them
6646 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6647 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6648 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6651 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6654 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6655 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6656 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6663 DefaultPromoChoice (int white)
6666 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6667 gameInfo.variant == VariantMakruk)
6668 result = WhiteFerz; // no choice
6669 else if(gameInfo.variant == VariantASEAN)
6670 result = WhiteRook; // no choice
6671 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6672 result= WhiteKing; // in Suicide Q is the last thing we want
6673 else if(gameInfo.variant == VariantSpartan)
6674 result = white ? WhiteQueen : WhiteAngel;
6675 else result = WhiteQueen;
6676 if(!white) result = WHITE_TO_BLACK result;
6680 static int autoQueen; // [HGM] oneclick
6683 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6685 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6686 /* [HGM] add Shogi promotions */
6687 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6688 ChessSquare piece, partner;
6692 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6693 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6695 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6696 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6699 piece = boards[currentMove][fromY][fromX];
6700 if(gameInfo.variant == VariantChu) {
6701 promotionZoneSize = BOARD_HEIGHT/3;
6702 highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6703 } else if(gameInfo.variant == VariantShogi) {
6704 promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6705 highestPromotingPiece = (int)WhiteAlfil;
6706 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6707 promotionZoneSize = 3;
6710 // Treat Lance as Pawn when it is not representing Amazon or Lance
6711 if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6712 if(piece == WhiteLance) piece = WhitePawn; else
6713 if(piece == BlackLance) piece = BlackPawn;
6716 // next weed out all moves that do not touch the promotion zone at all
6717 if((int)piece >= BlackPawn) {
6718 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6720 if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6721 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6723 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6724 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6725 if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6729 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6731 // weed out mandatory Shogi promotions
6732 if(gameInfo.variant == VariantShogi) {
6733 if(piece >= BlackPawn) {
6734 if(toY == 0 && piece == BlackPawn ||
6735 toY == 0 && piece == BlackQueen ||
6736 toY <= 1 && piece == BlackKnight) {
6741 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6742 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6743 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6750 // weed out obviously illegal Pawn moves
6751 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6752 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6753 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6754 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6755 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6756 // note we are not allowed to test for valid (non-)capture, due to premove
6759 // we either have a choice what to promote to, or (in Shogi) whether to promote
6760 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6761 gameInfo.variant == VariantMakruk) {
6762 ChessSquare p=BlackFerz; // no choice
6763 while(p < EmptySquare) { //but make sure we use piece that exists
6764 *promoChoice = PieceToChar(p++);
6765 if(*promoChoice != '.') break;
6767 if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6769 // no sense asking what we must promote to if it is going to explode...
6770 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6771 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6774 // give caller the default choice even if we will not make it
6775 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6776 partner = piece; // pieces can promote if the pieceToCharTable says so
6777 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6778 else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6779 if( sweepSelect && gameInfo.variant != VariantGreat
6780 && gameInfo.variant != VariantGrand
6781 && gameInfo.variant != VariantSuper) return FALSE;
6782 if(autoQueen) return FALSE; // predetermined
6784 // suppress promotion popup on illegal moves that are not premoves
6785 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6786 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6787 if(appData.testLegality && !premove) {
6788 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6789 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6790 if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6791 if(moveType != WhitePromotion && moveType != BlackPromotion)
6799 InPalace (int row, int column)
6800 { /* [HGM] for Xiangqi */
6801 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6802 column < (BOARD_WIDTH + 4)/2 &&
6803 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6808 PieceForSquare (int x, int y)
6810 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6813 return boards[currentMove][y][x];
6817 OKToStartUserMove (int x, int y)
6819 ChessSquare from_piece;
6822 if (matchMode) return FALSE;
6823 if (gameMode == EditPosition) return TRUE;
6825 if (x >= 0 && y >= 0)
6826 from_piece = boards[currentMove][y][x];
6828 from_piece = EmptySquare;
6830 if (from_piece == EmptySquare) return FALSE;
6832 white_piece = (int)from_piece >= (int)WhitePawn &&
6833 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6837 case TwoMachinesPlay:
6845 case MachinePlaysWhite:
6846 case IcsPlayingBlack:
6847 if (appData.zippyPlay) return FALSE;
6849 DisplayMoveError(_("You are playing Black"));
6854 case MachinePlaysBlack:
6855 case IcsPlayingWhite:
6856 if (appData.zippyPlay) return FALSE;
6858 DisplayMoveError(_("You are playing White"));
6863 case PlayFromGameFile:
6864 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6867 if (!white_piece && WhiteOnMove(currentMove)) {
6868 DisplayMoveError(_("It is White's turn"));
6871 if (white_piece && !WhiteOnMove(currentMove)) {
6872 DisplayMoveError(_("It is Black's turn"));
6875 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6876 /* Editing correspondence game history */
6877 /* Could disallow this or prompt for confirmation */
6882 case BeginningOfGame:
6883 if (appData.icsActive) return FALSE;
6884 if (!appData.noChessProgram) {
6886 DisplayMoveError(_("You are playing White"));
6893 if (!white_piece && WhiteOnMove(currentMove)) {
6894 DisplayMoveError(_("It is White's turn"));
6897 if (white_piece && !WhiteOnMove(currentMove)) {
6898 DisplayMoveError(_("It is Black's turn"));
6907 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6908 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6909 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6910 && gameMode != AnalyzeFile && gameMode != Training) {
6911 DisplayMoveError(_("Displayed position is not current"));
6918 OnlyMove (int *x, int *y, Boolean captures)
6920 DisambiguateClosure cl;
6921 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6923 case MachinePlaysBlack:
6924 case IcsPlayingWhite:
6925 case BeginningOfGame:
6926 if(!WhiteOnMove(currentMove)) return FALSE;
6928 case MachinePlaysWhite:
6929 case IcsPlayingBlack:
6930 if(WhiteOnMove(currentMove)) return FALSE;
6937 cl.pieceIn = EmptySquare;
6942 cl.promoCharIn = NULLCHAR;
6943 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6944 if( cl.kind == NormalMove ||
6945 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6946 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6947 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6954 if(cl.kind != ImpossibleMove) return FALSE;
6955 cl.pieceIn = EmptySquare;
6960 cl.promoCharIn = NULLCHAR;
6961 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6962 if( cl.kind == NormalMove ||
6963 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6964 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6965 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6970 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6976 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6977 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6978 int lastLoadGameUseList = FALSE;
6979 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6980 ChessMove lastLoadGameStart = EndOfFile;
6982 Boolean addToBookFlag;
6985 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6989 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6991 /* Check if the user is playing in turn. This is complicated because we
6992 let the user "pick up" a piece before it is his turn. So the piece he
6993 tried to pick up may have been captured by the time he puts it down!
6994 Therefore we use the color the user is supposed to be playing in this
6995 test, not the color of the piece that is currently on the starting
6996 square---except in EditGame mode, where the user is playing both
6997 sides; fortunately there the capture race can't happen. (It can
6998 now happen in IcsExamining mode, but that's just too bad. The user
6999 will get a somewhat confusing message in that case.)
7004 case TwoMachinesPlay:
7008 /* We switched into a game mode where moves are not accepted,
7009 perhaps while the mouse button was down. */
7012 case MachinePlaysWhite:
7013 /* User is moving for Black */
7014 if (WhiteOnMove(currentMove)) {
7015 DisplayMoveError(_("It is White's turn"));
7020 case MachinePlaysBlack:
7021 /* User is moving for White */
7022 if (!WhiteOnMove(currentMove)) {
7023 DisplayMoveError(_("It is Black's turn"));
7028 case PlayFromGameFile:
7029 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7032 case BeginningOfGame:
7035 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7036 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7037 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7038 /* User is moving for Black */
7039 if (WhiteOnMove(currentMove)) {
7040 DisplayMoveError(_("It is White's turn"));
7044 /* User is moving for White */
7045 if (!WhiteOnMove(currentMove)) {
7046 DisplayMoveError(_("It is Black's turn"));
7052 case IcsPlayingBlack:
7053 /* User is moving for Black */
7054 if (WhiteOnMove(currentMove)) {
7055 if (!appData.premove) {
7056 DisplayMoveError(_("It is White's turn"));
7057 } else if (toX >= 0 && toY >= 0) {
7060 premoveFromX = fromX;
7061 premoveFromY = fromY;
7062 premovePromoChar = promoChar;
7064 if (appData.debugMode)
7065 fprintf(debugFP, "Got premove: fromX %d,"
7066 "fromY %d, toX %d, toY %d\n",
7067 fromX, fromY, toX, toY);
7069 DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7074 case IcsPlayingWhite:
7075 /* User is moving for White */
7076 if (!WhiteOnMove(currentMove)) {
7077 if (!appData.premove) {
7078 DisplayMoveError(_("It is Black's turn"));
7079 } else if (toX >= 0 && toY >= 0) {
7082 premoveFromX = fromX;
7083 premoveFromY = fromY;
7084 premovePromoChar = promoChar;
7086 if (appData.debugMode)
7087 fprintf(debugFP, "Got premove: fromX %d,"
7088 "fromY %d, toX %d, toY %d\n",
7089 fromX, fromY, toX, toY);
7091 DrawPosition(TRUE, boards[currentMove]);
7100 /* EditPosition, empty square, or different color piece;
7101 click-click move is possible */
7102 if (toX == -2 || toY == -2) {
7103 boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7104 DrawPosition(FALSE, boards[currentMove]);
7106 } else if (toX >= 0 && toY >= 0) {
7107 if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7108 ChessSquare p = boards[0][rf][ff];
7109 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7110 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p);
7112 boards[0][toY][toX] = boards[0][fromY][fromX];
7113 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7114 if(boards[0][fromY][0] != EmptySquare) {
7115 if(boards[0][fromY][1]) boards[0][fromY][1]--;
7116 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
7119 if(fromX == BOARD_RGHT+1) {
7120 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7121 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7122 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7125 boards[0][fromY][fromX] = gatingPiece;
7127 DrawPosition(FALSE, boards[currentMove]);
7133 if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7134 pup = boards[currentMove][toY][toX];
7136 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7137 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7138 if( pup != EmptySquare ) return;
7139 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7140 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7141 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7142 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7143 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7144 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7145 while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7149 /* [HGM] always test for legality, to get promotion info */
7150 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7151 fromY, fromX, toY, toX, promoChar);
7153 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7155 if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7157 /* [HGM] but possibly ignore an IllegalMove result */
7158 if (appData.testLegality) {
7159 if (moveType == IllegalMove || moveType == ImpossibleMove) {
7160 DisplayMoveError(_("Illegal move"));
7165 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7166 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7167 ClearPremoveHighlights(); // was included
7168 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
7172 if(addToBookFlag) { // adding moves to book
7173 char buf[MSG_SIZ], move[MSG_SIZ];
7174 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7175 if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7176 killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7177 snprintf(buf, MSG_SIZ, " 0.0%% 1 %s\n", move);
7179 addToBookFlag = FALSE;
7184 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7187 /* Common tail of UserMoveEvent and DropMenuEvent */
7189 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7193 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7194 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7195 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7196 if(WhiteOnMove(currentMove)) {
7197 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7199 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7203 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7204 move type in caller when we know the move is a legal promotion */
7205 if(moveType == NormalMove && promoChar)
7206 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7208 /* [HGM] <popupFix> The following if has been moved here from
7209 UserMoveEvent(). Because it seemed to belong here (why not allow
7210 piece drops in training games?), and because it can only be
7211 performed after it is known to what we promote. */
7212 if (gameMode == Training) {
7213 /* compare the move played on the board to the next move in the
7214 * game. If they match, display the move and the opponent's response.
7215 * If they don't match, display an error message.
7219 CopyBoard(testBoard, boards[currentMove]);
7220 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7222 if (CompareBoards(testBoard, boards[currentMove+1])) {
7223 ForwardInner(currentMove+1);
7225 /* Autoplay the opponent's response.
7226 * if appData.animate was TRUE when Training mode was entered,
7227 * the response will be animated.
7229 saveAnimate = appData.animate;
7230 appData.animate = animateTraining;
7231 ForwardInner(currentMove+1);
7232 appData.animate = saveAnimate;
7234 /* check for the end of the game */
7235 if (currentMove >= forwardMostMove) {
7236 gameMode = PlayFromGameFile;
7238 SetTrainingModeOff();
7239 DisplayInformation(_("End of game"));
7242 DisplayError(_("Incorrect move"), 0);
7247 /* Ok, now we know that the move is good, so we can kill
7248 the previous line in Analysis Mode */
7249 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7250 && currentMove < forwardMostMove) {
7251 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7252 else forwardMostMove = currentMove;
7257 /* If we need the chess program but it's dead, restart it */
7258 ResurrectChessProgram();
7260 /* A user move restarts a paused game*/
7264 thinkOutput[0] = NULLCHAR;
7266 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7268 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7269 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7273 if (gameMode == BeginningOfGame) {
7274 if (appData.noChessProgram) {
7275 gameMode = EditGame;
7279 gameMode = MachinePlaysBlack;
7282 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7284 if (first.sendName) {
7285 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7286 SendToProgram(buf, &first);
7293 /* Relay move to ICS or chess engine */
7294 if (appData.icsActive) {
7295 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7296 gameMode == IcsExamining) {
7297 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7298 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7300 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7302 // also send plain move, in case ICS does not understand atomic claims
7303 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7307 if (first.sendTime && (gameMode == BeginningOfGame ||
7308 gameMode == MachinePlaysWhite ||
7309 gameMode == MachinePlaysBlack)) {
7310 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7312 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7313 // [HGM] book: if program might be playing, let it use book
7314 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7315 first.maybeThinking = TRUE;
7316 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7317 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7318 SendBoard(&first, currentMove+1);
7319 if(second.analyzing) {
7320 if(!second.useSetboard) SendToProgram("undo\n", &second);
7321 SendBoard(&second, currentMove+1);
7324 SendMoveToProgram(forwardMostMove-1, &first);
7325 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7327 if (currentMove == cmailOldMove + 1) {
7328 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7332 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7336 if(appData.testLegality)
7337 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7343 if (WhiteOnMove(currentMove)) {
7344 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7346 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7350 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7355 case MachinePlaysBlack:
7356 case MachinePlaysWhite:
7357 /* disable certain menu options while machine is thinking */
7358 SetMachineThinkingEnables();
7365 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7366 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7368 if(bookHit) { // [HGM] book: simulate book reply
7369 static char bookMove[MSG_SIZ]; // a bit generous?
7371 programStats.nodes = programStats.depth = programStats.time =
7372 programStats.score = programStats.got_only_move = 0;
7373 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7375 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7376 strcat(bookMove, bookHit);
7377 HandleMachineMove(bookMove, &first);
7383 MarkByFEN(char *fen)
7386 if(!appData.markers || !appData.highlightDragging) return;
7387 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7388 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7392 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7393 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7394 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7395 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7396 if(*fen == 'T') marker[r][f++] = 0; else
7397 if(*fen == 'Y') marker[r][f++] = 1; else
7398 if(*fen == 'G') marker[r][f++] = 3; else
7399 if(*fen == 'B') marker[r][f++] = 4; else
7400 if(*fen == 'C') marker[r][f++] = 5; else
7401 if(*fen == 'M') marker[r][f++] = 6; else
7402 if(*fen == 'W') marker[r][f++] = 7; else
7403 if(*fen == 'D') marker[r][f++] = 8; else
7404 if(*fen == 'R') marker[r][f++] = 2; else {
7405 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7408 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7412 DrawPosition(TRUE, NULL);
7415 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7418 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7420 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7421 Markers *m = (Markers *) closure;
7422 if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7423 kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7424 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7425 || kind == WhiteCapturesEnPassant
7426 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7427 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7430 static int hoverSavedValid;
7433 MarkTargetSquares (int clear)
7436 if(clear) { // no reason to ever suppress clearing
7437 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7438 hoverSavedValid = 0;
7439 if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7442 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7443 !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7444 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7445 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7446 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7448 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7451 DrawPosition(FALSE, NULL);
7455 Explode (Board board, int fromX, int fromY, int toX, int toY)
7457 if(gameInfo.variant == VariantAtomic &&
7458 (board[toY][toX] != EmptySquare || // capture?
7459 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7460 board[fromY][fromX] == BlackPawn )
7462 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7468 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7471 CanPromote (ChessSquare piece, int y)
7473 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7474 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7475 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7476 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7477 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7478 (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7479 gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7480 return (piece == BlackPawn && y <= zone ||
7481 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7482 piece == BlackLance && y <= zone ||
7483 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7487 HoverEvent (int xPix, int yPix, int x, int y)
7489 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7491 if(!first.highlight) return;
7492 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7493 if(x == oldX && y == oldY) return; // only do something if we enter new square
7494 oldFromX = fromX; oldFromY = fromY;
7495 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7496 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7497 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7498 hoverSavedValid = 1;
7499 } else if(oldX != x || oldY != y) {
7500 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7501 if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7502 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7503 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7504 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7506 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7507 SendToProgram(buf, &first);
7510 // SetHighlights(fromX, fromY, x, y);
7514 void ReportClick(char *action, int x, int y)
7516 char buf[MSG_SIZ]; // Inform engine of what user does
7518 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7519 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7520 legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7521 if(!first.highlight || gameMode == EditPosition) return;
7522 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7523 SendToProgram(buf, &first);
7526 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7529 LeftClick (ClickType clickType, int xPix, int yPix)
7532 Boolean saveAnimate;
7533 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7534 char promoChoice = NULLCHAR;
7536 static TimeMark lastClickTime, prevClickTime;
7538 if(flashing) return;
7540 x = EventToSquare(xPix, BOARD_WIDTH);
7541 y = EventToSquare(yPix, BOARD_HEIGHT);
7542 if (!flipView && y >= 0) {
7543 y = BOARD_HEIGHT - 1 - y;
7545 if (flipView && x >= 0) {
7546 x = BOARD_WIDTH - 1 - x;
7549 if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7551 RightClick(clickType, xPix, yPix, &dummy, &dummy);
7556 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7558 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7560 if (clickType == Press) ErrorPopDown();
7561 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7563 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7564 defaultPromoChoice = promoSweep;
7565 promoSweep = EmptySquare; // terminate sweep
7566 promoDefaultAltered = TRUE;
7567 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7570 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7571 if(clickType == Release) return; // ignore upclick of click-click destination
7572 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7573 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7574 if(gameInfo.holdingsWidth &&
7575 (WhiteOnMove(currentMove)
7576 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7577 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7578 // click in right holdings, for determining promotion piece
7579 ChessSquare p = boards[currentMove][y][x];
7580 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7581 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7582 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7583 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7588 DrawPosition(FALSE, boards[currentMove]);
7592 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7593 if(clickType == Press
7594 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7595 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7596 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7599 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7600 // could be static click on premove from-square: abort premove
7602 ClearPremoveHighlights();
7605 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7606 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7608 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7609 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7610 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7611 defaultPromoChoice = DefaultPromoChoice(side);
7614 autoQueen = appData.alwaysPromoteToQueen;
7618 gatingPiece = EmptySquare;
7619 if (clickType != Press) {
7620 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7621 DragPieceEnd(xPix, yPix); dragging = 0;
7622 DrawPosition(FALSE, NULL);
7626 doubleClick = FALSE;
7627 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7628 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7630 fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7631 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7632 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7633 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7635 if (OKToStartUserMove(fromX, fromY)) {
7637 ReportClick("lift", x, y);
7638 MarkTargetSquares(0);
7639 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7640 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7641 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7642 promoSweep = defaultPromoChoice;
7643 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7644 Sweep(0); // Pawn that is going to promote: preview promotion piece
7645 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7647 if (appData.highlightDragging) {
7648 SetHighlights(fromX, fromY, -1, -1);
7652 } else fromX = fromY = -1;
7658 if (clickType == Press && gameMode != EditPosition) {
7663 // ignore off-board to clicks
7664 if(y < 0 || x < 0) return;
7666 /* Check if clicking again on the same color piece */
7667 fromP = boards[currentMove][fromY][fromX];
7668 toP = boards[currentMove][y][x];
7669 frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7670 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7671 marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7672 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7673 WhitePawn <= toP && toP <= WhiteKing &&
7674 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7675 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7676 (BlackPawn <= fromP && fromP <= BlackKing &&
7677 BlackPawn <= toP && toP <= BlackKing &&
7678 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7679 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7680 /* Clicked again on same color piece -- changed his mind */
7681 second = (x == fromX && y == fromY);
7682 killX = killY = kill2X = kill2Y = -1;
7683 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7684 second = FALSE; // first double-click rather than scond click
7685 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7687 promoDefaultAltered = FALSE;
7688 if(!second) MarkTargetSquares(1);
7689 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7690 if (appData.highlightDragging) {
7691 SetHighlights(x, y, -1, -1);
7695 if (OKToStartUserMove(x, y)) {
7696 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7697 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7698 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7699 gatingPiece = boards[currentMove][fromY][fromX];
7700 else gatingPiece = doubleClick ? fromP : EmptySquare;
7702 fromY = y; dragging = 1;
7703 if(!second) ReportClick("lift", x, y);
7704 MarkTargetSquares(0);
7705 DragPieceBegin(xPix, yPix, FALSE);
7706 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7707 promoSweep = defaultPromoChoice;
7708 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7709 Sweep(0); // Pawn that is going to promote: preview promotion piece
7713 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7716 // ignore clicks on holdings
7717 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7720 if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7721 gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7722 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7726 if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7727 DragPieceEnd(xPix, yPix); dragging = 0;
7729 // a deferred attempt to click-click move an empty square on top of a piece
7730 boards[currentMove][y][x] = EmptySquare;
7732 DrawPosition(FALSE, boards[currentMove]);
7733 fromX = fromY = -1; clearFlag = 0;
7736 if (appData.animateDragging) {
7737 /* Undo animation damage if any */
7738 DrawPosition(FALSE, NULL);
7741 /* Second up/down in same square; just abort move */
7744 gatingPiece = EmptySquare;
7747 ClearPremoveHighlights();
7748 MarkTargetSquares(-1);
7749 DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7751 /* First upclick in same square; start click-click mode */
7752 SetHighlights(x, y, -1, -1);
7759 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7760 fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7761 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7762 DisplayMessage(_("only marked squares are legal"),"");
7763 DrawPosition(TRUE, NULL);
7764 return; // ignore to-click
7767 /* we now have a different from- and (possibly off-board) to-square */
7768 /* Completed move */
7769 if(!sweepSelecting) {
7774 piece = boards[currentMove][fromY][fromX];
7776 saveAnimate = appData.animate;
7777 if (clickType == Press) {
7778 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7779 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7780 // must be Edit Position mode with empty-square selected
7781 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7782 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7785 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7788 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7789 killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7791 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7792 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7793 if(appData.sweepSelect) {
7794 promoSweep = defaultPromoChoice;
7795 if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7796 selectFlag = 0; lastX = xPix; lastY = yPix;
7797 ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7798 saveFlash = appData.flashCount; appData.flashCount = 0;
7799 Sweep(0); // Pawn that is going to promote: preview promotion piece
7801 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7802 MarkTargetSquares(1);
7804 return; // promo popup appears on up-click
7806 /* Finish clickclick move */
7807 if (appData.animate || appData.highlightLastMove) {
7808 SetHighlights(fromX, fromY, toX, toY);
7812 MarkTargetSquares(1);
7813 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7814 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7815 *promoRestrict = 0; appData.flashCount = saveFlash;
7816 if (appData.animate || appData.highlightLastMove) {
7817 SetHighlights(fromX, fromY, toX, toY);
7821 MarkTargetSquares(1);
7824 // [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
7825 /* Finish drag move */
7826 if (appData.highlightLastMove) {
7827 SetHighlights(fromX, fromY, toX, toY);
7832 if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7833 defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7834 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7835 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7836 dragging *= 2; // flag button-less dragging if we are dragging
7837 MarkTargetSquares(1);
7838 if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7840 kill2X = killX; kill2Y = killY;
7841 killX = x; killY = y; // remember this square as intermediate
7842 ReportClick("put", x, y); // and inform engine
7843 ReportClick("lift", x, y);
7844 MarkTargetSquares(0);
7848 DragPieceEnd(xPix, yPix); dragging = 0;
7849 /* Don't animate move and drag both */
7850 appData.animate = FALSE;
7851 MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7854 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7855 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7856 ChessSquare piece = boards[currentMove][fromY][fromX];
7857 if(gameMode == EditPosition && piece != EmptySquare &&
7858 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7861 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7862 n = PieceToNumber(piece - (int)BlackPawn);
7863 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7864 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7865 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7867 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7868 n = PieceToNumber(piece);
7869 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7870 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7871 boards[currentMove][n][BOARD_WIDTH-2]++;
7873 boards[currentMove][fromY][fromX] = EmptySquare;
7877 MarkTargetSquares(1);
7878 DrawPosition(TRUE, boards[currentMove]);
7882 // off-board moves should not be highlighted
7883 if(x < 0 || y < 0) ClearHighlights();
7884 else ReportClick("put", x, y);
7886 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7888 if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7890 if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7891 SetHighlights(fromX, fromY, toX, toY);
7892 MarkTargetSquares(1);
7893 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7894 // [HGM] super: promotion to captured piece selected from holdings
7895 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7896 promotionChoice = TRUE;
7897 // kludge follows to temporarily execute move on display, without promoting yet
7898 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7899 boards[currentMove][toY][toX] = p;
7900 DrawPosition(FALSE, boards[currentMove]);
7901 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7902 boards[currentMove][toY][toX] = q;
7903 DisplayMessage("Click in holdings to choose piece", "");
7906 PromotionPopUp(promoChoice);
7908 int oldMove = currentMove;
7909 flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7910 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7911 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7912 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7913 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7914 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7915 DrawPosition(TRUE, boards[currentMove]);
7919 appData.animate = saveAnimate;
7920 if (appData.animate || appData.animateDragging) {
7921 /* Undo animation damage if needed */
7922 // DrawPosition(FALSE, NULL);
7927 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7928 { // front-end-free part taken out of PieceMenuPopup
7929 int whichMenu; int xSqr, ySqr;
7931 if(seekGraphUp) { // [HGM] seekgraph
7932 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7933 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7937 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7938 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7939 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7940 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7941 if(action == Press) {
7942 originalFlip = flipView;
7943 flipView = !flipView; // temporarily flip board to see game from partners perspective
7944 DrawPosition(TRUE, partnerBoard);
7945 DisplayMessage(partnerStatus, "");
7947 } else if(action == Release) {
7948 flipView = originalFlip;
7949 DrawPosition(TRUE, boards[currentMove]);
7955 xSqr = EventToSquare(x, BOARD_WIDTH);
7956 ySqr = EventToSquare(y, BOARD_HEIGHT);
7957 if (action == Release) {
7958 if(pieceSweep != EmptySquare) {
7959 EditPositionMenuEvent(pieceSweep, toX, toY);
7960 pieceSweep = EmptySquare;
7961 } else UnLoadPV(); // [HGM] pv
7963 if (action != Press) return -2; // return code to be ignored
7966 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7968 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7969 if (xSqr < 0 || ySqr < 0) return -1;
7970 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7971 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7972 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7973 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7977 if(!appData.icsEngineAnalyze) return -1;
7978 case IcsPlayingWhite:
7979 case IcsPlayingBlack:
7980 if(!appData.zippyPlay) goto noZip;
7983 case MachinePlaysWhite:
7984 case MachinePlaysBlack:
7985 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7986 if (!appData.dropMenu) {
7988 return 2; // flag front-end to grab mouse events
7990 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7991 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7994 if (xSqr < 0 || ySqr < 0) return -1;
7995 if (!appData.dropMenu || appData.testLegality &&
7996 gameInfo.variant != VariantBughouse &&
7997 gameInfo.variant != VariantCrazyhouse) return -1;
7998 whichMenu = 1; // drop menu
8004 if (((*fromX = xSqr) < 0) ||
8005 ((*fromY = ySqr) < 0)) {
8006 *fromX = *fromY = -1;
8010 *fromX = BOARD_WIDTH - 1 - *fromX;
8012 *fromY = BOARD_HEIGHT - 1 - *fromY;
8018 Wheel (int dir, int x, int y)
8020 if(gameMode == EditPosition) {
8021 int xSqr = EventToSquare(x, BOARD_WIDTH);
8022 int ySqr = EventToSquare(y, BOARD_HEIGHT);
8023 if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8024 if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8026 boards[currentMove][ySqr][xSqr] += dir;
8027 if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8028 if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8029 } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8030 DrawPosition(FALSE, boards[currentMove]);
8031 } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8035 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8037 // char * hint = lastHint;
8038 FrontEndProgramStats stats;
8040 stats.which = cps == &first ? 0 : 1;
8041 stats.depth = cpstats->depth;
8042 stats.nodes = cpstats->nodes;
8043 stats.score = cpstats->score;
8044 stats.time = cpstats->time;
8045 stats.pv = cpstats->movelist;
8046 stats.hint = lastHint;
8047 stats.an_move_index = 0;
8048 stats.an_move_count = 0;
8050 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8051 stats.hint = cpstats->move_name;
8052 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8053 stats.an_move_count = cpstats->nr_moves;
8056 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
8058 SetProgramStats( &stats );
8062 ClearEngineOutputPane (int which)
8064 static FrontEndProgramStats dummyStats;
8065 dummyStats.which = which;
8066 dummyStats.pv = "#";
8067 SetProgramStats( &dummyStats );
8070 #define MAXPLAYERS 500
8073 TourneyStandings (int display)
8075 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8076 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8077 char result, *p, *names[MAXPLAYERS];
8079 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8080 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8081 names[0] = p = strdup(appData.participants);
8082 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8084 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8086 while(result = appData.results[nr]) {
8087 color = Pairing(nr, nPlayers, &w, &b, &dummy);
8088 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8089 wScore = bScore = 0;
8091 case '+': wScore = 2; break;
8092 case '-': bScore = 2; break;
8093 case '=': wScore = bScore = 1; break;
8095 case '*': return strdup("busy"); // tourney not finished
8103 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8104 for(w=0; w<nPlayers; w++) {
8106 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8107 ranking[w] = b; points[w] = bScore; score[b] = -2;
8109 p = malloc(nPlayers*34+1);
8110 for(w=0; w<nPlayers && w<display; w++)
8111 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8117 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8118 { // count all piece types
8120 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8121 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8122 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8125 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8126 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8127 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8128 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8129 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
8130 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8135 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8137 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8138 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8140 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8141 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8142 if(myPawns == 2 && nMine == 3) // KPP
8143 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8144 if(myPawns == 1 && nMine == 2) // KP
8145 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8146 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8147 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8148 if(myPawns) return FALSE;
8149 if(pCnt[WhiteRook+side])
8150 return pCnt[BlackRook-side] ||
8151 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8152 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8153 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8154 if(pCnt[WhiteCannon+side]) {
8155 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8156 return majorDefense || pCnt[BlackAlfil-side] >= 2;
8158 if(pCnt[WhiteKnight+side])
8159 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8164 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8166 VariantClass v = gameInfo.variant;
8168 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8169 if(v == VariantShatranj) return TRUE; // always winnable through baring
8170 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8171 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8173 if(v == VariantXiangqi) {
8174 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8176 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8177 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8178 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8179 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8180 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8181 if(stale) // we have at least one last-rank P plus perhaps C
8182 return majors // KPKX
8183 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8185 return pCnt[WhiteFerz+side] // KCAK
8186 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8187 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8188 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8190 } else if(v == VariantKnightmate) {
8191 if(nMine == 1) return FALSE;
8192 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8193 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8194 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8196 if(nMine == 1) return FALSE; // bare King
8197 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
8198 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8199 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8200 // by now we have King + 1 piece (or multiple Bishops on the same color)
8201 if(pCnt[WhiteKnight+side])
8202 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8203 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8204 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8206 return (pCnt[BlackKnight-side]); // KBKN, KFKN
8207 if(pCnt[WhiteAlfil+side])
8208 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8209 if(pCnt[WhiteWazir+side])
8210 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8217 CompareWithRights (Board b1, Board b2)
8220 if(!CompareBoards(b1, b2)) return FALSE;
8221 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8222 /* compare castling rights */
8223 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8224 rights++; /* King lost rights, while rook still had them */
8225 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8226 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8227 rights++; /* but at least one rook lost them */
8229 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8231 if( b1[CASTLING][5] != NoRights ) {
8232 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8239 Adjudicate (ChessProgramState *cps)
8240 { // [HGM] some adjudications useful with buggy engines
8241 // [HGM] adjudicate: made into separate routine, which now can be called after every move
8242 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8243 // Actually ending the game is now based on the additional internal condition canAdjudicate.
8244 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8245 int k, drop, count = 0; static int bare = 1;
8246 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8247 Boolean canAdjudicate = !appData.icsActive;
8249 // most tests only when we understand the game, i.e. legality-checking on
8250 if( appData.testLegality )
8251 { /* [HGM] Some more adjudications for obstinate engines */
8252 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8253 static int moveCount = 6;
8255 char *reason = NULL;
8257 /* Count what is on board. */
8258 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8260 /* Some material-based adjudications that have to be made before stalemate test */
8261 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8262 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8263 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8264 if(canAdjudicate && appData.checkMates) {
8266 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8267 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8268 "Xboard adjudication: King destroyed", GE_XBOARD );
8273 /* Bare King in Shatranj (loses) or Losers (wins) */
8274 if( nrW == 1 || nrB == 1) {
8275 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8276 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
8277 if(canAdjudicate && appData.checkMates) {
8279 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8280 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8281 "Xboard adjudication: Bare king", GE_XBOARD );
8285 if( gameInfo.variant == VariantShatranj && --bare < 0)
8287 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8288 if(canAdjudicate && appData.checkMates) {
8289 /* but only adjudicate if adjudication enabled */
8291 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8292 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8293 "Xboard adjudication: Bare king", GE_XBOARD );
8300 // don't wait for engine to announce game end if we can judge ourselves
8301 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8303 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8304 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
8305 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8306 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8309 reason = "Xboard adjudication: 3rd check";
8310 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8321 reason = "Xboard adjudication: Stalemate";
8322 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8323 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8324 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8325 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8326 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8327 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8328 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8329 EP_CHECKMATE : EP_WINS);
8330 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8331 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8335 reason = "Xboard adjudication: Checkmate";
8336 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8337 if(gameInfo.variant == VariantShogi) {
8338 if(forwardMostMove > backwardMostMove
8339 && moveList[forwardMostMove-1][1] == '@'
8340 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8341 reason = "XBoard adjudication: pawn-drop mate";
8342 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8348 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8350 result = GameIsDrawn; break;
8352 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8354 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8358 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8360 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8361 GameEnds( result, reason, GE_XBOARD );
8365 /* Next absolutely insufficient mating material. */
8366 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8367 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8368 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8370 /* always flag draws, for judging claims */
8371 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8373 if(canAdjudicate && appData.materialDraws) {
8374 /* but only adjudicate them if adjudication enabled */
8375 if(engineOpponent) {
8376 SendToProgram("force\n", engineOpponent); // suppress reply
8377 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8379 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8384 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8385 if(gameInfo.variant == VariantXiangqi ?
8386 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8388 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8389 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8390 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8391 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8393 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8394 { /* if the first 3 moves do not show a tactical win, declare draw */
8395 if(engineOpponent) {
8396 SendToProgram("force\n", engineOpponent); // suppress reply
8397 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8399 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8402 } else moveCount = 6;
8405 // Repetition draws and 50-move rule can be applied independently of legality testing
8407 /* Check for rep-draws */
8409 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8410 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8411 for(k = forwardMostMove-2;
8412 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8413 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8414 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8417 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8418 /* compare castling rights */
8419 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8420 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8421 rights++; /* King lost rights, while rook still had them */
8422 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8423 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8424 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8425 rights++; /* but at least one rook lost them */
8427 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8428 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8430 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8431 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8432 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8435 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8436 && appData.drawRepeats > 1) {
8437 /* adjudicate after user-specified nr of repeats */
8438 int result = GameIsDrawn;
8439 char *details = "XBoard adjudication: repetition draw";
8440 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8441 // [HGM] xiangqi: check for forbidden perpetuals
8442 int m, ourPerpetual = 1, hisPerpetual = 1;
8443 for(m=forwardMostMove; m>k; m-=2) {
8444 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8445 ourPerpetual = 0; // the current mover did not always check
8446 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8447 hisPerpetual = 0; // the opponent did not always check
8449 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8450 ourPerpetual, hisPerpetual);
8451 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8452 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8453 details = "Xboard adjudication: perpetual checking";
8455 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8456 break; // (or we would have caught him before). Abort repetition-checking loop.
8458 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8459 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8461 details = "Xboard adjudication: repetition";
8463 } else // it must be XQ
8464 // Now check for perpetual chases
8465 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8466 hisPerpetual = PerpetualChase(k, forwardMostMove);
8467 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8468 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8469 static char resdet[MSG_SIZ];
8470 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8472 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8474 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8475 break; // Abort repetition-checking loop.
8477 // if neither of us is checking or chasing all the time, or both are, it is draw
8479 if(engineOpponent) {
8480 SendToProgram("force\n", engineOpponent); // suppress reply
8481 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8483 GameEnds( result, details, GE_XBOARD );
8486 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8487 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8491 /* Now we test for 50-move draws. Determine ply count */
8492 count = forwardMostMove;
8493 /* look for last irreversble move */
8494 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8496 /* if we hit starting position, add initial plies */
8497 if( count == backwardMostMove )
8498 count -= initialRulePlies;
8499 count = forwardMostMove - count;
8500 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8501 // adjust reversible move counter for checks in Xiangqi
8502 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8503 if(i < backwardMostMove) i = backwardMostMove;
8504 while(i <= forwardMostMove) {
8505 lastCheck = inCheck; // check evasion does not count
8506 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8507 if(inCheck || lastCheck) count--; // check does not count
8512 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8513 /* this is used to judge if draw claims are legal */
8514 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8515 if(engineOpponent) {
8516 SendToProgram("force\n", engineOpponent); // suppress reply
8517 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8519 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8523 /* if draw offer is pending, treat it as a draw claim
8524 * when draw condition present, to allow engines a way to
8525 * claim draws before making their move to avoid a race
8526 * condition occurring after their move
8528 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8530 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8531 p = "Draw claim: 50-move rule";
8532 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8533 p = "Draw claim: 3-fold repetition";
8534 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8535 p = "Draw claim: insufficient mating material";
8536 if( p != NULL && canAdjudicate) {
8537 if(engineOpponent) {
8538 SendToProgram("force\n", engineOpponent); // suppress reply
8539 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8541 GameEnds( GameIsDrawn, p, GE_XBOARD );
8546 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8547 if(engineOpponent) {
8548 SendToProgram("force\n", engineOpponent); // suppress reply
8549 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8551 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8557 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8558 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8559 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8564 int pieces[10], squares[10], cnt=0, r, f, res;
8566 static PPROBE_EGBB probeBB;
8567 if(!appData.testLegality) return 10;
8568 if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8569 if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8570 if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8571 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8572 ChessSquare piece = boards[forwardMostMove][r][f];
8573 int black = (piece >= BlackPawn);
8574 int type = piece - black*BlackPawn;
8575 if(piece == EmptySquare) continue;
8576 if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8577 if(type == WhiteKing) type = WhiteQueen + 1;
8578 type = egbbCode[type];
8579 squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8580 pieces[cnt] = type + black*6;
8581 if(++cnt > 5) return 11;
8583 pieces[cnt] = squares[cnt] = 0;
8585 if(loaded == 2) return 13; // loading failed before
8587 char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8590 loaded = 2; // prepare for failure
8591 if(!path) return 13; // no egbb installed
8592 strncpy(buf, path + 8, MSG_SIZ);
8593 if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8594 snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8595 lib = LoadLibrary(buf);
8596 if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8597 loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8598 probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8599 if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8600 p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8601 loaded = 1; // success!
8603 res = probeBB(forwardMostMove & 1, pieces, squares);
8604 return res > 0 ? 1 : res < 0 ? -1 : 0;
8608 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8609 { // [HGM] book: this routine intercepts moves to simulate book replies
8610 char *bookHit = NULL;
8612 if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8614 snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8615 SendToProgram(buf, cps);
8617 //first determine if the incoming move brings opponent into his book
8618 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8619 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8620 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8621 if(bookHit != NULL && !cps->bookSuspend) {
8622 // make sure opponent is not going to reply after receiving move to book position
8623 SendToProgram("force\n", cps);
8624 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8626 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8627 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8628 // now arrange restart after book miss
8630 // after a book hit we never send 'go', and the code after the call to this routine
8631 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8632 char buf[MSG_SIZ], *move = bookHit;
8634 int fromX, fromY, toX, toY;
8638 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8639 &fromX, &fromY, &toX, &toY, &promoChar)) {
8640 (void) CoordsToAlgebraic(boards[forwardMostMove],
8641 PosFlags(forwardMostMove),
8642 fromY, fromX, toY, toX, promoChar, move);
8644 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8648 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8649 SendToProgram(buf, cps);
8650 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8651 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8652 SendToProgram("go\n", cps);
8653 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8654 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8655 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8656 SendToProgram("go\n", cps);
8657 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8659 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8663 LoadError (char *errmess, ChessProgramState *cps)
8664 { // unloads engine and switches back to -ncp mode if it was first
8665 if(cps->initDone) return FALSE;
8666 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8667 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8670 appData.noChessProgram = TRUE;
8671 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8672 gameMode = BeginningOfGame; ModeHighlight();
8675 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8676 DisplayMessage("", ""); // erase waiting message
8677 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8682 ChessProgramState *savedState;
8684 DeferredBookMove (void)
8686 if(savedState->lastPing != savedState->lastPong)
8687 ScheduleDelayedEvent(DeferredBookMove, 10);
8689 HandleMachineMove(savedMessage, savedState);
8692 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8693 static ChessProgramState *stalledEngine;
8694 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8697 HandleMachineMove (char *message, ChessProgramState *cps)
8699 static char firstLeg[20], legs;
8700 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8701 char realname[MSG_SIZ];
8702 int fromX, fromY, toX, toY;
8704 char promoChar, roar;
8709 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8710 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8711 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8712 DisplayError(_("Invalid pairing from pairing engine"), 0);
8715 pairingReceived = 1;
8717 return; // Skim the pairing messages here.
8720 oldError = cps->userError; cps->userError = 0;
8722 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8724 * Kludge to ignore BEL characters
8726 while (*message == '\007') message++;
8729 * [HGM] engine debug message: ignore lines starting with '#' character
8731 if(cps->debug && *message == '#') return;
8734 * Look for book output
8736 if (cps == &first && bookRequested) {
8737 if (message[0] == '\t' || message[0] == ' ') {
8738 /* Part of the book output is here; append it */
8739 strcat(bookOutput, message);
8740 strcat(bookOutput, " \n");
8742 } else if (bookOutput[0] != NULLCHAR) {
8743 /* All of book output has arrived; display it */
8744 char *p = bookOutput;
8745 while (*p != NULLCHAR) {
8746 if (*p == '\t') *p = ' ';
8749 DisplayInformation(bookOutput);
8750 bookRequested = FALSE;
8751 /* Fall through to parse the current output */
8756 * Look for machine move.
8758 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8759 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8761 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8762 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8763 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8764 stalledEngine = cps;
8765 if(appData.ponderNextMove) { // bring opponent out of ponder
8766 if(gameMode == TwoMachinesPlay) {
8767 if(cps->other->pause)
8768 PauseEngine(cps->other);
8770 SendToProgram("easy\n", cps->other);
8779 /* This method is only useful on engines that support ping */
8780 if(abortEngineThink) {
8781 if (appData.debugMode) {
8782 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8784 SendToProgram("undo\n", cps);
8788 if (cps->lastPing != cps->lastPong) {
8789 /* Extra move from before last new; ignore */
8790 if (appData.debugMode) {
8791 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8798 int machineWhite = FALSE;
8801 case BeginningOfGame:
8802 /* Extra move from before last reset; ignore */
8803 if (appData.debugMode) {
8804 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8811 /* Extra move after we tried to stop. The mode test is
8812 not a reliable way of detecting this problem, but it's
8813 the best we can do on engines that don't support ping.
8815 if (appData.debugMode) {
8816 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8817 cps->which, gameMode);
8819 SendToProgram("undo\n", cps);
8822 case MachinePlaysWhite:
8823 case IcsPlayingWhite:
8824 machineWhite = TRUE;
8827 case MachinePlaysBlack:
8828 case IcsPlayingBlack:
8829 machineWhite = FALSE;
8832 case TwoMachinesPlay:
8833 machineWhite = (cps->twoMachinesColor[0] == 'w');
8836 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8837 if (appData.debugMode) {
8839 "Ignoring move out of turn by %s, gameMode %d"
8840 ", forwardMost %d\n",
8841 cps->which, gameMode, forwardMostMove);
8847 if(cps->alphaRank) AlphaRank(machineMove, 4);
8849 // [HGM] lion: (some very limited) support for Alien protocol
8850 killX = killY = kill2X = kill2Y = -1;
8851 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8852 if(legs++) return; // middle leg contains only redundant info, ignore (but count it)
8853 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8856 if(p = strchr(machineMove, ',')) { // we got both legs in one (happens on book move)
8857 char *q = strchr(p+1, ','); // second comma?
8858 safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8859 if(q) legs = 2, p = q; else legs = 1; // with 3-leg move we clipof first two legs!
8860 safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8862 if(firstLeg[0]) { // there was a previous leg;
8863 // only support case where same piece makes two step
8864 char buf[20], *p = machineMove+1, *q = buf+1, f;
8865 safeStrCpy(buf, machineMove, 20);
8866 while(isdigit(*q)) q++; // find start of to-square
8867 safeStrCpy(machineMove, firstLeg, 20);
8868 while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8869 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
8870 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)
8871 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8872 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8873 firstLeg[0] = NULLCHAR; legs = 0;
8876 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8877 &fromX, &fromY, &toX, &toY, &promoChar)) {
8878 /* Machine move could not be parsed; ignore it. */
8879 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8880 machineMove, _(cps->which));
8881 DisplayMoveError(buf1);
8882 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8883 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8884 if (gameMode == TwoMachinesPlay) {
8885 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8891 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8892 /* So we have to redo legality test with true e.p. status here, */
8893 /* to make sure an illegal e.p. capture does not slip through, */
8894 /* to cause a forfeit on a justified illegal-move complaint */
8895 /* of the opponent. */
8896 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8898 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8899 fromY, fromX, toY, toX, promoChar);
8900 if(moveType == IllegalMove) {
8901 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8902 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8903 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8906 } else if(!appData.fischerCastling)
8907 /* [HGM] Kludge to handle engines that send FRC-style castling
8908 when they shouldn't (like TSCP-Gothic) */
8910 case WhiteASideCastleFR:
8911 case BlackASideCastleFR:
8913 currentMoveString[2]++;
8915 case WhiteHSideCastleFR:
8916 case BlackHSideCastleFR:
8918 currentMoveString[2]--;
8920 default: ; // nothing to do, but suppresses warning of pedantic compilers
8923 hintRequested = FALSE;
8924 lastHint[0] = NULLCHAR;
8925 bookRequested = FALSE;
8926 /* Program may be pondering now */
8927 cps->maybeThinking = TRUE;
8928 if (cps->sendTime == 2) cps->sendTime = 1;
8929 if (cps->offeredDraw) cps->offeredDraw--;
8931 /* [AS] Save move info*/
8932 pvInfoList[ forwardMostMove ].score = programStats.score;
8933 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8934 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8936 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8938 /* Test suites abort the 'game' after one move */
8939 if(*appData.finger) {
8941 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8942 if(!f) f = fopen(appData.finger, "w");
8943 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8944 else { DisplayFatalError("Bad output file", errno, 0); return; }
8946 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8949 if(solvingTime >= 0) {
8950 snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8951 totalTime += solvingTime; first.matchWins++;
8953 snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8956 OutputKibitz(2, buf1);
8957 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8960 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8961 if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8964 while( count < adjudicateLossPlies ) {
8965 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8968 score = -score; /* Flip score for winning side */
8971 if( score > appData.adjudicateLossThreshold ) {
8978 if( count >= adjudicateLossPlies ) {
8979 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8981 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8982 "Xboard adjudication",
8989 if(Adjudicate(cps)) {
8990 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8991 return; // [HGM] adjudicate: for all automatic game ends
8995 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8997 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8998 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9000 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9002 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9004 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9005 char buf[3*MSG_SIZ];
9007 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9008 programStats.score / 100.,
9010 programStats.time / 100.,
9011 (unsigned int)programStats.nodes,
9012 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9013 programStats.movelist);
9019 /* [AS] Clear stats for next move */
9020 ClearProgramStats();
9021 thinkOutput[0] = NULLCHAR;
9022 hiddenThinkOutputState = 0;
9025 if (gameMode == TwoMachinesPlay) {
9026 /* [HGM] relaying draw offers moved to after reception of move */
9027 /* and interpreting offer as claim if it brings draw condition */
9028 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9029 SendToProgram("draw\n", cps->other);
9031 if (cps->other->sendTime) {
9032 SendTimeRemaining(cps->other,
9033 cps->other->twoMachinesColor[0] == 'w');
9035 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9036 if (firstMove && !bookHit) {
9038 if (cps->other->useColors) {
9039 SendToProgram(cps->other->twoMachinesColor, cps->other);
9041 SendToProgram("go\n", cps->other);
9043 cps->other->maybeThinking = TRUE;
9046 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9048 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9050 if (!pausing && appData.ringBellAfterMoves) {
9051 if(!roar) RingBell();
9055 * Reenable menu items that were disabled while
9056 * machine was thinking
9058 if (gameMode != TwoMachinesPlay)
9059 SetUserThinkingEnables();
9061 // [HGM] book: after book hit opponent has received move and is now in force mode
9062 // force the book reply into it, and then fake that it outputted this move by jumping
9063 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9065 static char bookMove[MSG_SIZ]; // a bit generous?
9067 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9068 strcat(bookMove, bookHit);
9071 programStats.nodes = programStats.depth = programStats.time =
9072 programStats.score = programStats.got_only_move = 0;
9073 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9075 if(cps->lastPing != cps->lastPong) {
9076 savedMessage = message; // args for deferred call
9078 ScheduleDelayedEvent(DeferredBookMove, 10);
9087 /* Set special modes for chess engines. Later something general
9088 * could be added here; for now there is just one kludge feature,
9089 * needed because Crafty 15.10 and earlier don't ignore SIGINT
9090 * when "xboard" is given as an interactive command.
9092 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9093 cps->useSigint = FALSE;
9094 cps->useSigterm = FALSE;
9096 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9097 ParseFeatures(message+8, cps);
9098 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9101 if (!strncmp(message, "setup ", 6) &&
9102 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9103 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9104 ) { // [HGM] allow first engine to define opening position
9105 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9106 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9108 if(sscanf(message, "setup (%s", buf) == 1) {
9109 s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9110 ASSIGN(appData.pieceToCharTable, buf);
9112 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9114 while(message[s] && message[s++] != ' ');
9115 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9116 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9117 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9118 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
9119 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9120 if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9121 startedFromSetupPosition = FALSE;
9124 if(startedFromSetupPosition) return;
9125 ParseFEN(boards[0], &dummy, message+s, FALSE);
9126 DrawPosition(TRUE, boards[0]);
9127 CopyBoard(initialPosition, boards[0]);
9128 startedFromSetupPosition = TRUE;
9131 if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9132 ChessSquare piece = WhitePawn;
9133 char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9134 if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9135 if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9136 piece += CharToPiece(ID & 255) - WhitePawn;
9137 if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9138 /* always accept definition of */ && piece != WhiteFalcon && piece != BlackFalcon
9139 /* wild-card pieces. */ && piece != WhiteCobra && piece != BlackCobra
9140 /* For variants we don't have */ && gameInfo.variant != VariantBerolina
9141 /* correct rules for, we cannot */ && gameInfo.variant != VariantCylinder
9142 /* enforce legality on our own! */ && gameInfo.variant != VariantUnknown
9143 && gameInfo.variant != VariantGreat
9144 && gameInfo.variant != VariantFairy ) return;
9145 if(piece < EmptySquare) {
9147 ASSIGN(pieceDesc[piece], buf1);
9148 if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9152 if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9153 promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9157 /* [HGM] Allow engine to set up a position. Don't ask me why one would
9158 * want this, I was asked to put it in, and obliged.
9160 if (!strncmp(message, "setboard ", 9)) {
9161 Board initial_position;
9163 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9165 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9166 DisplayError(_("Bad FEN received from engine"), 0);
9170 CopyBoard(boards[0], initial_position);
9171 initialRulePlies = FENrulePlies;
9172 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9173 else gameMode = MachinePlaysBlack;
9174 DrawPosition(FALSE, boards[currentMove]);
9180 * Look for communication commands
9182 if (!strncmp(message, "telluser ", 9)) {
9183 if(message[9] == '\\' && message[10] == '\\')
9184 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9186 DisplayNote(message + 9);
9189 if (!strncmp(message, "tellusererror ", 14)) {
9191 if(message[14] == '\\' && message[15] == '\\')
9192 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9194 DisplayError(message + 14, 0);
9197 if (!strncmp(message, "tellopponent ", 13)) {
9198 if (appData.icsActive) {
9200 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9204 DisplayNote(message + 13);
9208 if (!strncmp(message, "tellothers ", 11)) {
9209 if (appData.icsActive) {
9211 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9214 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9217 if (!strncmp(message, "tellall ", 8)) {
9218 if (appData.icsActive) {
9220 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9224 DisplayNote(message + 8);
9228 if (strncmp(message, "warning", 7) == 0) {
9229 /* Undocumented feature, use tellusererror in new code */
9230 DisplayError(message, 0);
9233 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9234 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9235 strcat(realname, " query");
9236 AskQuestion(realname, buf2, buf1, cps->pr);
9239 /* Commands from the engine directly to ICS. We don't allow these to be
9240 * sent until we are logged on. Crafty kibitzes have been known to
9241 * interfere with the login process.
9244 if (!strncmp(message, "tellics ", 8)) {
9245 SendToICS(message + 8);
9249 if (!strncmp(message, "tellicsnoalias ", 15)) {
9250 SendToICS(ics_prefix);
9251 SendToICS(message + 15);
9255 /* The following are for backward compatibility only */
9256 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9257 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9258 SendToICS(ics_prefix);
9264 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9265 if(initPing == cps->lastPong) {
9266 if(gameInfo.variant == VariantUnknown) {
9267 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9268 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9269 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9273 if(cps->lastPing == cps->lastPong && abortEngineThink) {
9274 abortEngineThink = FALSE;
9275 DisplayMessage("", "");
9280 if(!strncmp(message, "highlight ", 10)) {
9281 if(appData.testLegality && !*engineVariant && appData.markers) return;
9282 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9285 if(!strncmp(message, "click ", 6)) {
9286 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9287 if(appData.testLegality || !appData.oneClick) return;
9288 sscanf(message+6, "%c%d%c", &f, &y, &c);
9289 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9290 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9291 x = x*squareSize + (x+1)*lineGap + squareSize/2;
9292 y = y*squareSize + (y+1)*lineGap + squareSize/2;
9293 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9294 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9295 LeftClick(Release, lastLeftX, lastLeftY);
9296 controlKey = (c == ',');
9297 LeftClick(Press, x, y);
9298 LeftClick(Release, x, y);
9299 first.highlight = f;
9303 * If the move is illegal, cancel it and redraw the board.
9304 * Also deal with other error cases. Matching is rather loose
9305 * here to accommodate engines written before the spec.
9307 if (strncmp(message + 1, "llegal move", 11) == 0 ||
9308 strncmp(message, "Error", 5) == 0) {
9309 if (StrStr(message, "name") ||
9310 StrStr(message, "rating") || StrStr(message, "?") ||
9311 StrStr(message, "result") || StrStr(message, "board") ||
9312 StrStr(message, "bk") || StrStr(message, "computer") ||
9313 StrStr(message, "variant") || StrStr(message, "hint") ||
9314 StrStr(message, "random") || StrStr(message, "depth") ||
9315 StrStr(message, "accepted")) {
9318 if (StrStr(message, "protover")) {
9319 /* Program is responding to input, so it's apparently done
9320 initializing, and this error message indicates it is
9321 protocol version 1. So we don't need to wait any longer
9322 for it to initialize and send feature commands. */
9323 FeatureDone(cps, 1);
9324 cps->protocolVersion = 1;
9327 cps->maybeThinking = FALSE;
9329 if (StrStr(message, "draw")) {
9330 /* Program doesn't have "draw" command */
9331 cps->sendDrawOffers = 0;
9334 if (cps->sendTime != 1 &&
9335 (StrStr(message, "time") || StrStr(message, "otim"))) {
9336 /* Program apparently doesn't have "time" or "otim" command */
9340 if (StrStr(message, "analyze")) {
9341 cps->analysisSupport = FALSE;
9342 cps->analyzing = FALSE;
9343 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9344 EditGameEvent(); // [HGM] try to preserve loaded game
9345 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9346 DisplayError(buf2, 0);
9349 if (StrStr(message, "(no matching move)st")) {
9350 /* Special kludge for GNU Chess 4 only */
9351 cps->stKludge = TRUE;
9352 SendTimeControl(cps, movesPerSession, timeControl,
9353 timeIncrement, appData.searchDepth,
9357 if (StrStr(message, "(no matching move)sd")) {
9358 /* Special kludge for GNU Chess 4 only */
9359 cps->sdKludge = TRUE;
9360 SendTimeControl(cps, movesPerSession, timeControl,
9361 timeIncrement, appData.searchDepth,
9365 if (!StrStr(message, "llegal")) {
9368 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9369 gameMode == IcsIdle) return;
9370 if (forwardMostMove <= backwardMostMove) return;
9371 if (pausing) PauseEvent();
9372 if(appData.forceIllegal) {
9373 // [HGM] illegal: machine refused move; force position after move into it
9374 SendToProgram("force\n", cps);
9375 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9376 // we have a real problem now, as SendBoard will use the a2a3 kludge
9377 // when black is to move, while there might be nothing on a2 or black
9378 // might already have the move. So send the board as if white has the move.
9379 // But first we must change the stm of the engine, as it refused the last move
9380 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9381 if(WhiteOnMove(forwardMostMove)) {
9382 SendToProgram("a7a6\n", cps); // for the engine black still had the move
9383 SendBoard(cps, forwardMostMove); // kludgeless board
9385 SendToProgram("a2a3\n", cps); // for the engine white still had the move
9386 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9387 SendBoard(cps, forwardMostMove+1); // kludgeless board
9389 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9390 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9391 gameMode == TwoMachinesPlay)
9392 SendToProgram("go\n", cps);
9395 if (gameMode == PlayFromGameFile) {
9396 /* Stop reading this game file */
9397 gameMode = EditGame;
9400 /* [HGM] illegal-move claim should forfeit game when Xboard */
9401 /* only passes fully legal moves */
9402 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9403 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9404 "False illegal-move claim", GE_XBOARD );
9405 return; // do not take back move we tested as valid
9407 currentMove = forwardMostMove-1;
9408 DisplayMove(currentMove-1); /* before DisplayMoveError */
9409 SwitchClocks(forwardMostMove-1); // [HGM] race
9410 DisplayBothClocks();
9411 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9412 parseList[currentMove], _(cps->which));
9413 DisplayMoveError(buf1);
9414 DrawPosition(FALSE, boards[currentMove]);
9416 SetUserThinkingEnables();
9419 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9420 /* Program has a broken "time" command that
9421 outputs a string not ending in newline.
9425 if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9426 if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9427 sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1 ) return;
9431 * If chess program startup fails, exit with an error message.
9432 * Attempts to recover here are futile. [HGM] Well, we try anyway
9434 if ((StrStr(message, "unknown host") != NULL)
9435 || (StrStr(message, "No remote directory") != NULL)
9436 || (StrStr(message, "not found") != NULL)
9437 || (StrStr(message, "No such file") != NULL)
9438 || (StrStr(message, "can't alloc") != NULL)
9439 || (StrStr(message, "Permission denied") != NULL)) {
9441 cps->maybeThinking = FALSE;
9442 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9443 _(cps->which), cps->program, cps->host, message);
9444 RemoveInputSource(cps->isr);
9445 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9446 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9447 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9453 * Look for hint output
9455 if (sscanf(message, "Hint: %s", buf1) == 1) {
9456 if (cps == &first && hintRequested) {
9457 hintRequested = FALSE;
9458 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9459 &fromX, &fromY, &toX, &toY, &promoChar)) {
9460 (void) CoordsToAlgebraic(boards[forwardMostMove],
9461 PosFlags(forwardMostMove),
9462 fromY, fromX, toY, toX, promoChar, buf1);
9463 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9464 DisplayInformation(buf2);
9466 /* Hint move could not be parsed!? */
9467 snprintf(buf2, sizeof(buf2),
9468 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9469 buf1, _(cps->which));
9470 DisplayError(buf2, 0);
9473 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9479 * Ignore other messages if game is not in progress
9481 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9482 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9485 * look for win, lose, draw, or draw offer
9487 if (strncmp(message, "1-0", 3) == 0) {
9488 char *p, *q, *r = "";
9489 p = strchr(message, '{');
9497 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9499 } else if (strncmp(message, "0-1", 3) == 0) {
9500 char *p, *q, *r = "";
9501 p = strchr(message, '{');
9509 /* Kludge for Arasan 4.1 bug */
9510 if (strcmp(r, "Black resigns") == 0) {
9511 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9514 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9516 } else if (strncmp(message, "1/2", 3) == 0) {
9517 char *p, *q, *r = "";
9518 p = strchr(message, '{');
9527 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9530 } else if (strncmp(message, "White resign", 12) == 0) {
9531 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9533 } else if (strncmp(message, "Black resign", 12) == 0) {
9534 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9536 } else if (strncmp(message, "White matches", 13) == 0 ||
9537 strncmp(message, "Black matches", 13) == 0 ) {
9538 /* [HGM] ignore GNUShogi noises */
9540 } else if (strncmp(message, "White", 5) == 0 &&
9541 message[5] != '(' &&
9542 StrStr(message, "Black") == NULL) {
9543 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9545 } else if (strncmp(message, "Black", 5) == 0 &&
9546 message[5] != '(') {
9547 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9549 } else if (strcmp(message, "resign") == 0 ||
9550 strcmp(message, "computer resigns") == 0) {
9552 case MachinePlaysBlack:
9553 case IcsPlayingBlack:
9554 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9556 case MachinePlaysWhite:
9557 case IcsPlayingWhite:
9558 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9560 case TwoMachinesPlay:
9561 if (cps->twoMachinesColor[0] == 'w')
9562 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9564 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9571 } else if (strncmp(message, "opponent mates", 14) == 0) {
9573 case MachinePlaysBlack:
9574 case IcsPlayingBlack:
9575 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9577 case MachinePlaysWhite:
9578 case IcsPlayingWhite:
9579 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9581 case TwoMachinesPlay:
9582 if (cps->twoMachinesColor[0] == 'w')
9583 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9585 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9592 } else if (strncmp(message, "computer mates", 14) == 0) {
9594 case MachinePlaysBlack:
9595 case IcsPlayingBlack:
9596 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9598 case MachinePlaysWhite:
9599 case IcsPlayingWhite:
9600 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9602 case TwoMachinesPlay:
9603 if (cps->twoMachinesColor[0] == 'w')
9604 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9606 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9613 } else if (strncmp(message, "checkmate", 9) == 0) {
9614 if (WhiteOnMove(forwardMostMove)) {
9615 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9617 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9620 } else if (strstr(message, "Draw") != NULL ||
9621 strstr(message, "game is a draw") != NULL) {
9622 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9624 } else if (strstr(message, "offer") != NULL &&
9625 strstr(message, "draw") != NULL) {
9627 if (appData.zippyPlay && first.initDone) {
9628 /* Relay offer to ICS */
9629 SendToICS(ics_prefix);
9630 SendToICS("draw\n");
9633 cps->offeredDraw = 2; /* valid until this engine moves twice */
9634 if (gameMode == TwoMachinesPlay) {
9635 if (cps->other->offeredDraw) {
9636 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9637 /* [HGM] in two-machine mode we delay relaying draw offer */
9638 /* until after we also have move, to see if it is really claim */
9640 } else if (gameMode == MachinePlaysWhite ||
9641 gameMode == MachinePlaysBlack) {
9642 if (userOfferedDraw) {
9643 DisplayInformation(_("Machine accepts your draw offer"));
9644 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9646 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9653 * Look for thinking output
9655 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9656 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9658 int plylev, mvleft, mvtot, curscore, time;
9659 char mvname[MOVE_LEN];
9663 int prefixHint = FALSE;
9664 mvname[0] = NULLCHAR;
9667 case MachinePlaysBlack:
9668 case IcsPlayingBlack:
9669 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9671 case MachinePlaysWhite:
9672 case IcsPlayingWhite:
9673 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9678 case IcsObserving: /* [DM] icsEngineAnalyze */
9679 if (!appData.icsEngineAnalyze) ignore = TRUE;
9681 case TwoMachinesPlay:
9682 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9692 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9694 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9695 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9696 char score_buf[MSG_SIZ];
9698 if(nodes>>32 == u64Const(0xFFFFFFFF)) // [HGM] negative node count read
9699 nodes += u64Const(0x100000000);
9701 if (plyext != ' ' && plyext != '\t') {
9705 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9706 if( cps->scoreIsAbsolute &&
9707 ( gameMode == MachinePlaysBlack ||
9708 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9709 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9710 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9711 !WhiteOnMove(currentMove)
9714 curscore = -curscore;
9717 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9719 if(*bestMove) { // rememer time best EPD move was first found
9720 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9722 int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9723 ok &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9724 solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9727 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9730 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9731 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9732 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9733 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9734 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9735 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9739 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9740 DisplayError(_("failed writing PV"), 0);
9743 tempStats.depth = plylev;
9744 tempStats.nodes = nodes;
9745 tempStats.time = time;
9746 tempStats.score = curscore;
9747 tempStats.got_only_move = 0;
9749 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9752 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9753 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9754 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9755 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9756 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9757 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9758 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9759 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9762 /* Buffer overflow protection */
9763 if (pv[0] != NULLCHAR) {
9764 if (strlen(pv) >= sizeof(tempStats.movelist)
9765 && appData.debugMode) {
9767 "PV is too long; using the first %u bytes.\n",
9768 (unsigned) sizeof(tempStats.movelist) - 1);
9771 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9773 sprintf(tempStats.movelist, " no PV\n");
9776 if (tempStats.seen_stat) {
9777 tempStats.ok_to_send = 1;
9780 if (strchr(tempStats.movelist, '(') != NULL) {
9781 tempStats.line_is_book = 1;
9782 tempStats.nr_moves = 0;
9783 tempStats.moves_left = 0;
9785 tempStats.line_is_book = 0;
9788 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9789 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9791 SendProgramStatsToFrontend( cps, &tempStats );
9794 [AS] Protect the thinkOutput buffer from overflow... this
9795 is only useful if buf1 hasn't overflowed first!
9797 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9798 if(curscore >= MATE_SCORE)
9799 snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9800 else if(curscore <= -MATE_SCORE)
9801 snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9803 snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9804 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9806 (gameMode == TwoMachinesPlay ?
9807 ToUpper(cps->twoMachinesColor[0]) : ' '),
9809 prefixHint ? lastHint : "",
9810 prefixHint ? " " : "" );
9812 if( buf1[0] != NULLCHAR ) {
9813 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9815 if( strlen(pv) > max_len ) {
9816 if( appData.debugMode) {
9817 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9819 pv[max_len+1] = '\0';
9822 strcat( thinkOutput, pv);
9825 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9826 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9827 DisplayMove(currentMove - 1);
9831 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9832 /* crafty (9.25+) says "(only move) <move>"
9833 * if there is only 1 legal move
9835 sscanf(p, "(only move) %s", buf1);
9836 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9837 sprintf(programStats.movelist, "%s (only move)", buf1);
9838 programStats.depth = 1;
9839 programStats.nr_moves = 1;
9840 programStats.moves_left = 1;
9841 programStats.nodes = 1;
9842 programStats.time = 1;
9843 programStats.got_only_move = 1;
9845 /* Not really, but we also use this member to
9846 mean "line isn't going to change" (Crafty
9847 isn't searching, so stats won't change) */
9848 programStats.line_is_book = 1;
9850 SendProgramStatsToFrontend( cps, &programStats );
9852 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9853 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9854 DisplayMove(currentMove - 1);
9857 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9858 &time, &nodes, &plylev, &mvleft,
9859 &mvtot, mvname) >= 5) {
9860 /* The stat01: line is from Crafty (9.29+) in response
9861 to the "." command */
9862 programStats.seen_stat = 1;
9863 cps->maybeThinking = TRUE;
9865 if (programStats.got_only_move || !appData.periodicUpdates)
9868 programStats.depth = plylev;
9869 programStats.time = time;
9870 programStats.nodes = nodes;
9871 programStats.moves_left = mvleft;
9872 programStats.nr_moves = mvtot;
9873 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9874 programStats.ok_to_send = 1;
9875 programStats.movelist[0] = '\0';
9877 SendProgramStatsToFrontend( cps, &programStats );
9881 } else if (strncmp(message,"++",2) == 0) {
9882 /* Crafty 9.29+ outputs this */
9883 programStats.got_fail = 2;
9886 } else if (strncmp(message,"--",2) == 0) {
9887 /* Crafty 9.29+ outputs this */
9888 programStats.got_fail = 1;
9891 } else if (thinkOutput[0] != NULLCHAR &&
9892 strncmp(message, " ", 4) == 0) {
9893 unsigned message_len;
9896 while (*p && *p == ' ') p++;
9898 message_len = strlen( p );
9900 /* [AS] Avoid buffer overflow */
9901 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9902 strcat(thinkOutput, " ");
9903 strcat(thinkOutput, p);
9906 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9907 strcat(programStats.movelist, " ");
9908 strcat(programStats.movelist, p);
9911 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9912 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9913 DisplayMove(currentMove - 1);
9921 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9922 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9924 ChessProgramStats cpstats;
9926 if (plyext != ' ' && plyext != '\t') {
9930 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9931 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9932 curscore = -curscore;
9935 cpstats.depth = plylev;
9936 cpstats.nodes = nodes;
9937 cpstats.time = time;
9938 cpstats.score = curscore;
9939 cpstats.got_only_move = 0;
9940 cpstats.movelist[0] = '\0';
9942 if (buf1[0] != NULLCHAR) {
9943 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9946 cpstats.ok_to_send = 0;
9947 cpstats.line_is_book = 0;
9948 cpstats.nr_moves = 0;
9949 cpstats.moves_left = 0;
9951 SendProgramStatsToFrontend( cps, &cpstats );
9958 /* Parse a game score from the character string "game", and
9959 record it as the history of the current game. The game
9960 score is NOT assumed to start from the standard position.
9961 The display is not updated in any way.
9964 ParseGameHistory (char *game)
9967 int fromX, fromY, toX, toY, boardIndex;
9972 if (appData.debugMode)
9973 fprintf(debugFP, "Parsing game history: %s\n", game);
9975 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9976 gameInfo.site = StrSave(appData.icsHost);
9977 gameInfo.date = PGNDate();
9978 gameInfo.round = StrSave("-");
9980 /* Parse out names of players */
9981 while (*game == ' ') game++;
9983 while (*game != ' ') *p++ = *game++;
9985 gameInfo.white = StrSave(buf);
9986 while (*game == ' ') game++;
9988 while (*game != ' ' && *game != '\n') *p++ = *game++;
9990 gameInfo.black = StrSave(buf);
9993 boardIndex = blackPlaysFirst ? 1 : 0;
9996 yyboardindex = boardIndex;
9997 moveType = (ChessMove) Myylex();
9999 case IllegalMove: /* maybe suicide chess, etc. */
10000 if (appData.debugMode) {
10001 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10002 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10003 setbuf(debugFP, NULL);
10005 case WhitePromotion:
10006 case BlackPromotion:
10007 case WhiteNonPromotion:
10008 case BlackNonPromotion:
10011 case WhiteCapturesEnPassant:
10012 case BlackCapturesEnPassant:
10013 case WhiteKingSideCastle:
10014 case WhiteQueenSideCastle:
10015 case BlackKingSideCastle:
10016 case BlackQueenSideCastle:
10017 case WhiteKingSideCastleWild:
10018 case WhiteQueenSideCastleWild:
10019 case BlackKingSideCastleWild:
10020 case BlackQueenSideCastleWild:
10022 case WhiteHSideCastleFR:
10023 case WhiteASideCastleFR:
10024 case BlackHSideCastleFR:
10025 case BlackASideCastleFR:
10027 fromX = currentMoveString[0] - AAA;
10028 fromY = currentMoveString[1] - ONE;
10029 toX = currentMoveString[2] - AAA;
10030 toY = currentMoveString[3] - ONE;
10031 promoChar = currentMoveString[4];
10035 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10036 fromX = moveType == WhiteDrop ?
10037 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10038 (int) CharToPiece(ToLower(currentMoveString[0]));
10040 toX = currentMoveString[2] - AAA;
10041 toY = currentMoveString[3] - ONE;
10042 promoChar = NULLCHAR;
10044 case AmbiguousMove:
10046 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10047 if (appData.debugMode) {
10048 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10049 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10050 setbuf(debugFP, NULL);
10052 DisplayError(buf, 0);
10054 case ImpossibleMove:
10056 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10057 if (appData.debugMode) {
10058 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10059 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10060 setbuf(debugFP, NULL);
10062 DisplayError(buf, 0);
10065 if (boardIndex < backwardMostMove) {
10066 /* Oops, gap. How did that happen? */
10067 DisplayError(_("Gap in move list"), 0);
10070 backwardMostMove = blackPlaysFirst ? 1 : 0;
10071 if (boardIndex > forwardMostMove) {
10072 forwardMostMove = boardIndex;
10076 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10077 strcat(parseList[boardIndex-1], " ");
10078 strcat(parseList[boardIndex-1], yy_text);
10090 case GameUnfinished:
10091 if (gameMode == IcsExamining) {
10092 if (boardIndex < backwardMostMove) {
10093 /* Oops, gap. How did that happen? */
10096 backwardMostMove = blackPlaysFirst ? 1 : 0;
10099 gameInfo.result = moveType;
10100 p = strchr(yy_text, '{');
10101 if (p == NULL) p = strchr(yy_text, '(');
10104 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10106 q = strchr(p, *p == '{' ? '}' : ')');
10107 if (q != NULL) *q = NULLCHAR;
10110 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10111 gameInfo.resultDetails = StrSave(p);
10114 if (boardIndex >= forwardMostMove &&
10115 !(gameMode == IcsObserving && ics_gamenum == -1)) {
10116 backwardMostMove = blackPlaysFirst ? 1 : 0;
10119 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10120 fromY, fromX, toY, toX, promoChar,
10121 parseList[boardIndex]);
10122 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10123 /* currentMoveString is set as a side-effect of yylex */
10124 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10125 strcat(moveList[boardIndex], "\n");
10127 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10128 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10134 if(!IS_SHOGI(gameInfo.variant))
10135 strcat(parseList[boardIndex - 1], "+");
10139 strcat(parseList[boardIndex - 1], "#");
10146 /* Apply a move to the given board */
10148 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10150 ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10151 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10153 /* [HGM] compute & store e.p. status and castling rights for new position */
10154 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10156 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10157 oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10158 board[EP_STATUS] = EP_NONE;
10159 board[EP_FILE] = board[EP_RANK] = 100;
10161 if (fromY == DROP_RANK) {
10162 /* must be first */
10163 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10164 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10167 piece = board[toY][toX] = (ChessSquare) fromX;
10169 // ChessSquare victim;
10172 if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10173 // victim = board[killY][killX],
10174 killed = board[killY][killX],
10175 board[killY][killX] = EmptySquare,
10176 board[EP_STATUS] = EP_CAPTURE;
10177 if( kill2X >= 0 && kill2Y >= 0)
10178 killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10181 if( board[toY][toX] != EmptySquare ) {
10182 board[EP_STATUS] = EP_CAPTURE;
10183 if( (fromX != toX || fromY != toY) && // not igui!
10184 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10185 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
10186 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10190 pawn = board[fromY][fromX];
10191 if( pawn == WhiteLance || pawn == BlackLance ) {
10192 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10193 if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10194 else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10197 if( pawn == WhitePawn ) {
10198 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10199 board[EP_STATUS] = EP_PAWN_MOVE;
10200 if( toY-fromY>=2) {
10201 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10202 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
10203 gameInfo.variant != VariantBerolina || toX < fromX)
10204 board[EP_STATUS] = toX | berolina;
10205 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10206 gameInfo.variant != VariantBerolina || toX > fromX)
10207 board[EP_STATUS] = toX;
10210 if( pawn == BlackPawn ) {
10211 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10212 board[EP_STATUS] = EP_PAWN_MOVE;
10213 if( toY-fromY<= -2) {
10214 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10215 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
10216 gameInfo.variant != VariantBerolina || toX < fromX)
10217 board[EP_STATUS] = toX | berolina;
10218 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10219 gameInfo.variant != VariantBerolina || toX > fromX)
10220 board[EP_STATUS] = toX;
10224 if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10225 if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10226 if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10227 if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10229 for(i=0; i<nrCastlingRights; i++) {
10230 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10231 board[CASTLING][i] == toX && castlingRank[i] == toY
10232 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10235 if(gameInfo.variant == VariantSChess) { // update virginity
10236 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10237 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10238 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
10239 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
10242 if (fromX == toX && fromY == toY && killX < 0) return;
10244 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10245 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10246 if(gameInfo.variant == VariantKnightmate)
10247 king += (int) WhiteUnicorn - (int) WhiteKing;
10249 if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10250 && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) { // and tramples own
10251 board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10252 board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10253 board[EP_STATUS] = EP_NONE; // capture was fake!
10255 if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10256 board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10257 board[toY][toX] = piece;
10258 board[EP_STATUS] = EP_NONE; // capture was fake!
10260 /* Code added by Tord: */
10261 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10262 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10263 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10264 board[EP_STATUS] = EP_NONE; // capture was fake!
10265 board[fromY][fromX] = EmptySquare;
10266 board[toY][toX] = EmptySquare;
10267 if((toX > fromX) != (piece == WhiteRook)) {
10268 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10270 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10272 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10273 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10274 board[EP_STATUS] = EP_NONE;
10275 board[fromY][fromX] = EmptySquare;
10276 board[toY][toX] = EmptySquare;
10277 if((toX > fromX) != (piece == BlackRook)) {
10278 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10280 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10282 /* End of code added by Tord */
10284 } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10285 board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10286 board[toY][toX] = piece;
10287 } else if (board[fromY][fromX] == king
10288 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10289 && toY == fromY && toX > fromX+1) {
10290 for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10291 board[fromY][toX-1] = board[fromY][rookX];
10292 board[fromY][rookX] = EmptySquare;
10293 board[fromY][fromX] = EmptySquare;
10294 board[toY][toX] = king;
10295 } else if (board[fromY][fromX] == king
10296 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10297 && toY == fromY && toX < fromX-1) {
10298 for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10299 board[fromY][toX+1] = board[fromY][rookX];
10300 board[fromY][rookX] = EmptySquare;
10301 board[fromY][fromX] = EmptySquare;
10302 board[toY][toX] = king;
10303 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10304 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10305 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10307 /* white pawn promotion */
10308 board[toY][toX] = CharToPiece(ToUpper(promoChar));
10309 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10310 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10311 board[fromY][fromX] = EmptySquare;
10312 } else if ((fromY >= BOARD_HEIGHT>>1)
10313 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10315 && gameInfo.variant != VariantXiangqi
10316 && gameInfo.variant != VariantBerolina
10317 && (pawn == WhitePawn)
10318 && (board[toY][toX] == EmptySquare)) {
10319 board[fromY][fromX] = EmptySquare;
10320 board[toY][toX] = piece;
10321 if(toY == epRank - 128 + 1)
10322 captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10324 captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10325 } else if ((fromY == BOARD_HEIGHT-4)
10327 && gameInfo.variant == VariantBerolina
10328 && (board[fromY][fromX] == WhitePawn)
10329 && (board[toY][toX] == EmptySquare)) {
10330 board[fromY][fromX] = EmptySquare;
10331 board[toY][toX] = WhitePawn;
10332 if(oldEP & EP_BEROLIN_A) {
10333 captured = board[fromY][fromX-1];
10334 board[fromY][fromX-1] = EmptySquare;
10335 }else{ captured = board[fromY][fromX+1];
10336 board[fromY][fromX+1] = EmptySquare;
10338 } else if (board[fromY][fromX] == king
10339 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10340 && toY == fromY && toX > fromX+1) {
10341 for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10342 board[fromY][toX-1] = board[fromY][rookX];
10343 board[fromY][rookX] = EmptySquare;
10344 board[fromY][fromX] = EmptySquare;
10345 board[toY][toX] = king;
10346 } else if (board[fromY][fromX] == king
10347 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10348 && toY == fromY && toX < fromX-1) {
10349 for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10350 board[fromY][toX+1] = board[fromY][rookX];
10351 board[fromY][rookX] = EmptySquare;
10352 board[fromY][fromX] = EmptySquare;
10353 board[toY][toX] = king;
10354 } else if (fromY == 7 && fromX == 3
10355 && board[fromY][fromX] == BlackKing
10356 && toY == 7 && toX == 5) {
10357 board[fromY][fromX] = EmptySquare;
10358 board[toY][toX] = BlackKing;
10359 board[fromY][7] = EmptySquare;
10360 board[toY][4] = BlackRook;
10361 } else if (fromY == 7 && fromX == 3
10362 && board[fromY][fromX] == BlackKing
10363 && toY == 7 && toX == 1) {
10364 board[fromY][fromX] = EmptySquare;
10365 board[toY][toX] = BlackKing;
10366 board[fromY][0] = EmptySquare;
10367 board[toY][2] = BlackRook;
10368 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10369 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10370 && toY < promoRank && promoChar
10372 /* black pawn promotion */
10373 board[toY][toX] = CharToPiece(ToLower(promoChar));
10374 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10375 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10376 board[fromY][fromX] = EmptySquare;
10377 } else if ((fromY < BOARD_HEIGHT>>1)
10378 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10380 && gameInfo.variant != VariantXiangqi
10381 && gameInfo.variant != VariantBerolina
10382 && (pawn == BlackPawn)
10383 && (board[toY][toX] == EmptySquare)) {
10384 board[fromY][fromX] = EmptySquare;
10385 board[toY][toX] = piece;
10386 if(toY == epRank - 128 - 1)
10387 captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10389 captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10390 } else if ((fromY == 3)
10392 && gameInfo.variant == VariantBerolina
10393 && (board[fromY][fromX] == BlackPawn)
10394 && (board[toY][toX] == EmptySquare)) {
10395 board[fromY][fromX] = EmptySquare;
10396 board[toY][toX] = BlackPawn;
10397 if(oldEP & EP_BEROLIN_A) {
10398 captured = board[fromY][fromX-1];
10399 board[fromY][fromX-1] = EmptySquare;
10400 }else{ captured = board[fromY][fromX+1];
10401 board[fromY][fromX+1] = EmptySquare;
10404 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10405 board[fromY][fromX] = EmptySquare;
10406 board[toY][toX] = piece;
10410 if (gameInfo.holdingsWidth != 0) {
10412 /* !!A lot more code needs to be written to support holdings */
10413 /* [HGM] OK, so I have written it. Holdings are stored in the */
10414 /* penultimate board files, so they are automaticlly stored */
10415 /* in the game history. */
10416 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10417 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10418 /* Delete from holdings, by decreasing count */
10419 /* and erasing image if necessary */
10420 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10421 if(p < (int) BlackPawn) { /* white drop */
10422 p -= (int)WhitePawn;
10423 p = PieceToNumber((ChessSquare)p);
10424 if(p >= gameInfo.holdingsSize) p = 0;
10425 if(--board[p][BOARD_WIDTH-2] <= 0)
10426 board[p][BOARD_WIDTH-1] = EmptySquare;
10427 if((int)board[p][BOARD_WIDTH-2] < 0)
10428 board[p][BOARD_WIDTH-2] = 0;
10429 } else { /* black drop */
10430 p -= (int)BlackPawn;
10431 p = PieceToNumber((ChessSquare)p);
10432 if(p >= gameInfo.holdingsSize) p = 0;
10433 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10434 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10435 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10436 board[BOARD_HEIGHT-1-p][1] = 0;
10439 if (captured != EmptySquare && gameInfo.holdingsSize > 0
10440 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
10441 /* [HGM] holdings: Add to holdings, if holdings exist */
10442 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10443 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10444 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10446 p = (int) captured;
10447 if (p >= (int) BlackPawn) {
10448 p -= (int)BlackPawn;
10449 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10450 /* Restore shogi-promoted piece to its original first */
10451 captured = (ChessSquare) (DEMOTED(captured));
10454 p = PieceToNumber((ChessSquare)p);
10455 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10456 board[p][BOARD_WIDTH-2]++;
10457 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10459 p -= (int)WhitePawn;
10460 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10461 captured = (ChessSquare) (DEMOTED(captured));
10464 p = PieceToNumber((ChessSquare)p);
10465 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10466 board[BOARD_HEIGHT-1-p][1]++;
10467 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10470 } else if (gameInfo.variant == VariantAtomic) {
10471 if (captured != EmptySquare) {
10473 for (y = toY-1; y <= toY+1; y++) {
10474 for (x = toX-1; x <= toX+1; x++) {
10475 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10476 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10477 board[y][x] = EmptySquare;
10481 board[toY][toX] = EmptySquare;
10485 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10486 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10488 if(promoChar == '+') {
10489 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10490 board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10491 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10492 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10493 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10494 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10495 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10496 && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10497 board[toY][toX] = newPiece;
10499 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10500 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10501 // [HGM] superchess: take promotion piece out of holdings
10502 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10503 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10504 if(!--board[k][BOARD_WIDTH-2])
10505 board[k][BOARD_WIDTH-1] = EmptySquare;
10507 if(!--board[BOARD_HEIGHT-1-k][1])
10508 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10513 /* Updates forwardMostMove */
10515 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10517 int x = toX, y = toY;
10518 char *s = parseList[forwardMostMove];
10519 ChessSquare p = boards[forwardMostMove][toY][toX];
10520 // forwardMostMove++; // [HGM] bare: moved downstream
10522 if(kill2X >= 0) x = kill2X, y = kill2Y; else
10523 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10524 (void) CoordsToAlgebraic(boards[forwardMostMove],
10525 PosFlags(forwardMostMove),
10526 fromY, fromX, y, x, (killX < 0)*promoChar,
10528 if(kill2X >= 0 && kill2Y >= 0)
10529 sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10530 if(killX >= 0 && killY >= 0)
10531 sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10532 toX + AAA, toY + ONE - '0', promoChar);
10534 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10535 int timeLeft; static int lastLoadFlag=0; int king, piece;
10536 piece = boards[forwardMostMove][fromY][fromX];
10537 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10538 if(gameInfo.variant == VariantKnightmate)
10539 king += (int) WhiteUnicorn - (int) WhiteKing;
10540 if(forwardMostMove == 0) {
10541 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10542 fprintf(serverMoves, "%s;", UserName());
10543 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10544 fprintf(serverMoves, "%s;", second.tidy);
10545 fprintf(serverMoves, "%s;", first.tidy);
10546 if(gameMode == MachinePlaysWhite)
10547 fprintf(serverMoves, "%s;", UserName());
10548 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10549 fprintf(serverMoves, "%s;", second.tidy);
10550 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10551 lastLoadFlag = loadFlag;
10553 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10554 // print castling suffix
10555 if( toY == fromY && piece == king ) {
10557 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10559 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10562 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10563 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10564 boards[forwardMostMove][toY][toX] == EmptySquare
10565 && fromX != toX && fromY != toY)
10566 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10567 // promotion suffix
10568 if(promoChar != NULLCHAR) {
10569 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10570 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10571 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10572 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10575 char buf[MOVE_LEN*2], *p; int len;
10576 fprintf(serverMoves, "/%d/%d",
10577 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10578 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10579 else timeLeft = blackTimeRemaining/1000;
10580 fprintf(serverMoves, "/%d", timeLeft);
10581 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10582 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10583 if(p = strchr(buf, '=')) *p = NULLCHAR;
10584 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10585 fprintf(serverMoves, "/%s", buf);
10587 fflush(serverMoves);
10590 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10591 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10594 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10595 if (commentList[forwardMostMove+1] != NULL) {
10596 free(commentList[forwardMostMove+1]);
10597 commentList[forwardMostMove+1] = NULL;
10599 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10600 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10601 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10602 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10603 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10604 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10605 adjustedClock = FALSE;
10606 gameInfo.result = GameUnfinished;
10607 if (gameInfo.resultDetails != NULL) {
10608 free(gameInfo.resultDetails);
10609 gameInfo.resultDetails = NULL;
10611 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10612 moveList[forwardMostMove - 1]);
10613 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10619 if(!IS_SHOGI(gameInfo.variant))
10620 strcat(parseList[forwardMostMove - 1], "+");
10624 strcat(parseList[forwardMostMove - 1], "#");
10629 /* Updates currentMove if not pausing */
10631 ShowMove (int fromX, int fromY, int toX, int toY)
10633 int instant = (gameMode == PlayFromGameFile) ?
10634 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10635 if(appData.noGUI) return;
10636 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10638 if (forwardMostMove == currentMove + 1) {
10639 AnimateMove(boards[forwardMostMove - 1],
10640 fromX, fromY, toX, toY);
10643 currentMove = forwardMostMove;
10646 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10648 if (instant) return;
10650 DisplayMove(currentMove - 1);
10651 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10652 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10653 SetHighlights(fromX, fromY, toX, toY);
10656 DrawPosition(FALSE, boards[currentMove]);
10657 DisplayBothClocks();
10658 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10662 SendEgtPath (ChessProgramState *cps)
10663 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10664 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10666 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10669 char c, *q = name+1, *r, *s;
10671 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10672 while(*p && *p != ',') *q++ = *p++;
10673 *q++ = ':'; *q = 0;
10674 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10675 strcmp(name, ",nalimov:") == 0 ) {
10676 // take nalimov path from the menu-changeable option first, if it is defined
10677 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10678 SendToProgram(buf,cps); // send egtbpath command for nalimov
10680 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10681 (s = StrStr(appData.egtFormats, name)) != NULL) {
10682 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10683 s = r = StrStr(s, ":") + 1; // beginning of path info
10684 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10685 c = *r; *r = 0; // temporarily null-terminate path info
10686 *--q = 0; // strip of trailig ':' from name
10687 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10689 SendToProgram(buf,cps); // send egtbpath command for this format
10691 if(*p == ',') p++; // read away comma to position for next format name
10696 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10698 int width = 8, height = 8, holdings = 0; // most common sizes
10699 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10700 // correct the deviations default for each variant
10701 if( v == VariantXiangqi ) width = 9, height = 10;
10702 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10703 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10704 if( v == VariantCapablanca || v == VariantCapaRandom ||
10705 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10707 if( v == VariantCourier ) width = 12;
10708 if( v == VariantSuper ) holdings = 8;
10709 if( v == VariantGreat ) width = 10, holdings = 8;
10710 if( v == VariantSChess ) holdings = 7;
10711 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10712 if( v == VariantChuChess) width = 10, height = 10;
10713 if( v == VariantChu ) width = 12, height = 12;
10714 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10715 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10716 holdingsSize >= 0 && holdingsSize != holdings;
10719 char variantError[MSG_SIZ];
10722 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10723 { // returns error message (recognizable by upper-case) if engine does not support the variant
10724 char *p, *variant = VariantName(v);
10725 static char b[MSG_SIZ];
10726 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10727 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10728 holdingsSize, variant); // cook up sized variant name
10729 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10730 if(StrStr(list, b) == NULL) {
10731 // specific sized variant not known, check if general sizing allowed
10732 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10733 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10734 boardWidth, boardHeight, holdingsSize, engine);
10737 /* [HGM] here we really should compare with the maximum supported board size */
10739 } else snprintf(b, MSG_SIZ,"%s", variant);
10740 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10741 p = StrStr(list, b);
10742 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10744 // occurs not at all in list, or only as sub-string
10745 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10746 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10747 int l = strlen(variantError);
10749 while(p != list && p[-1] != ',') p--;
10750 q = strchr(p, ',');
10751 if(q) *q = NULLCHAR;
10752 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10761 InitChessProgram (ChessProgramState *cps, int setup)
10762 /* setup needed to setup FRC opening position */
10764 char buf[MSG_SIZ], *b;
10765 if (appData.noChessProgram) return;
10766 hintRequested = FALSE;
10767 bookRequested = FALSE;
10769 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10770 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10771 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10772 if(cps->memSize) { /* [HGM] memory */
10773 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10774 SendToProgram(buf, cps);
10776 SendEgtPath(cps); /* [HGM] EGT */
10777 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10778 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10779 SendToProgram(buf, cps);
10782 setboardSpoiledMachineBlack = FALSE;
10783 SendToProgram(cps->initString, cps);
10784 if (gameInfo.variant != VariantNormal &&
10785 gameInfo.variant != VariantLoadable
10786 /* [HGM] also send variant if board size non-standard */
10787 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10789 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10790 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10793 char c, *q = cps->variants, *p = strchr(q, ',');
10794 if(p) *p = NULLCHAR;
10795 v = StringToVariant(q);
10796 DisplayError(variantError, 0);
10797 if(v != VariantUnknown && cps == &first) {
10799 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10800 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10801 ASSIGN(appData.variant, q);
10802 Reset(TRUE, FALSE);
10808 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10809 SendToProgram(buf, cps);
10811 currentlyInitializedVariant = gameInfo.variant;
10813 /* [HGM] send opening position in FRC to first engine */
10815 SendToProgram("force\n", cps);
10817 /* engine is now in force mode! Set flag to wake it up after first move. */
10818 setboardSpoiledMachineBlack = 1;
10821 if (cps->sendICS) {
10822 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10823 SendToProgram(buf, cps);
10825 cps->maybeThinking = FALSE;
10826 cps->offeredDraw = 0;
10827 if (!appData.icsActive) {
10828 SendTimeControl(cps, movesPerSession, timeControl,
10829 timeIncrement, appData.searchDepth,
10832 if (appData.showThinking
10833 // [HGM] thinking: four options require thinking output to be sent
10834 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10836 SendToProgram("post\n", cps);
10838 SendToProgram("hard\n", cps);
10839 if (!appData.ponderNextMove) {
10840 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10841 it without being sure what state we are in first. "hard"
10842 is not a toggle, so that one is OK.
10844 SendToProgram("easy\n", cps);
10846 if (cps->usePing) {
10847 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10848 SendToProgram(buf, cps);
10850 cps->initDone = TRUE;
10851 ClearEngineOutputPane(cps == &second);
10856 ResendOptions (ChessProgramState *cps)
10857 { // send the stored value of the options
10860 Option *opt = cps->option;
10861 for(i=0; i<cps->nrOptions; i++, opt++) {
10862 switch(opt->type) {
10866 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10869 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10872 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10878 SendToProgram(buf, cps);
10883 StartChessProgram (ChessProgramState *cps)
10888 if (appData.noChessProgram) return;
10889 cps->initDone = FALSE;
10891 if (strcmp(cps->host, "localhost") == 0) {
10892 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10893 } else if (*appData.remoteShell == NULLCHAR) {
10894 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10896 if (*appData.remoteUser == NULLCHAR) {
10897 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10900 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10901 cps->host, appData.remoteUser, cps->program);
10903 err = StartChildProcess(buf, "", &cps->pr);
10907 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10908 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10909 if(cps != &first) return;
10910 appData.noChessProgram = TRUE;
10913 // DisplayFatalError(buf, err, 1);
10914 // cps->pr = NoProc;
10915 // cps->isr = NULL;
10919 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10920 if (cps->protocolVersion > 1) {
10921 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10922 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10923 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10924 cps->comboCnt = 0; // and values of combo boxes
10926 SendToProgram(buf, cps);
10927 if(cps->reload) ResendOptions(cps);
10929 SendToProgram("xboard\n", cps);
10934 TwoMachinesEventIfReady P((void))
10936 static int curMess = 0;
10937 if (first.lastPing != first.lastPong) {
10938 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10939 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10942 if (second.lastPing != second.lastPong) {
10943 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10944 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10947 DisplayMessage("", ""); curMess = 0;
10948 TwoMachinesEvent();
10952 MakeName (char *template)
10956 static char buf[MSG_SIZ];
10960 clock = time((time_t *)NULL);
10961 tm = localtime(&clock);
10963 while(*p++ = *template++) if(p[-1] == '%') {
10964 switch(*template++) {
10965 case 0: *p = 0; return buf;
10966 case 'Y': i = tm->tm_year+1900; break;
10967 case 'y': i = tm->tm_year-100; break;
10968 case 'M': i = tm->tm_mon+1; break;
10969 case 'd': i = tm->tm_mday; break;
10970 case 'h': i = tm->tm_hour; break;
10971 case 'm': i = tm->tm_min; break;
10972 case 's': i = tm->tm_sec; break;
10975 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10981 CountPlayers (char *p)
10984 while(p = strchr(p, '\n')) p++, n++; // count participants
10989 WriteTourneyFile (char *results, FILE *f)
10990 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10991 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10992 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10993 // create a file with tournament description
10994 fprintf(f, "-participants {%s}\n", appData.participants);
10995 fprintf(f, "-seedBase %d\n", appData.seedBase);
10996 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10997 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10998 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10999 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11000 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11001 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11002 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11003 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11004 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11005 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11006 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11007 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11008 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11009 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11010 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11011 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11012 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11013 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11014 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11015 fprintf(f, "-smpCores %d\n", appData.smpCores);
11017 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11019 fprintf(f, "-mps %d\n", appData.movesPerSession);
11020 fprintf(f, "-tc %s\n", appData.timeControl);
11021 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11023 fprintf(f, "-results \"%s\"\n", results);
11028 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11031 Substitute (char *participants, int expunge)
11033 int i, changed, changes=0, nPlayers=0;
11034 char *p, *q, *r, buf[MSG_SIZ];
11035 if(participants == NULL) return;
11036 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11037 r = p = participants; q = appData.participants;
11038 while(*p && *p == *q) {
11039 if(*p == '\n') r = p+1, nPlayers++;
11042 if(*p) { // difference
11043 while(*p && *p++ != '\n');
11044 while(*q && *q++ != '\n');
11045 changed = nPlayers;
11046 changes = 1 + (strcmp(p, q) != 0);
11048 if(changes == 1) { // a single engine mnemonic was changed
11049 q = r; while(*q) nPlayers += (*q++ == '\n');
11050 p = buf; while(*r && (*p = *r++) != '\n') p++;
11052 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11053 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11054 if(mnemonic[i]) { // The substitute is valid
11056 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11057 flock(fileno(f), LOCK_EX);
11058 ParseArgsFromFile(f);
11059 fseek(f, 0, SEEK_SET);
11060 FREE(appData.participants); appData.participants = participants;
11061 if(expunge) { // erase results of replaced engine
11062 int len = strlen(appData.results), w, b, dummy;
11063 for(i=0; i<len; i++) {
11064 Pairing(i, nPlayers, &w, &b, &dummy);
11065 if((w == changed || b == changed) && appData.results[i] == '*') {
11066 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11071 for(i=0; i<len; i++) {
11072 Pairing(i, nPlayers, &w, &b, &dummy);
11073 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11076 WriteTourneyFile(appData.results, f);
11077 fclose(f); // release lock
11080 } else DisplayError(_("No engine with the name you gave is installed"), 0);
11082 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11083 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
11084 free(participants);
11089 CheckPlayers (char *participants)
11092 char buf[MSG_SIZ], *p;
11093 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11094 while(p = strchr(participants, '\n')) {
11096 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11098 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11100 DisplayError(buf, 0);
11104 participants = p + 1;
11110 CreateTourney (char *name)
11113 if(matchMode && strcmp(name, appData.tourneyFile)) {
11114 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11116 if(name[0] == NULLCHAR) {
11117 if(appData.participants[0])
11118 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11121 f = fopen(name, "r");
11122 if(f) { // file exists
11123 ASSIGN(appData.tourneyFile, name);
11124 ParseArgsFromFile(f); // parse it
11126 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11127 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11128 DisplayError(_("Not enough participants"), 0);
11131 if(CheckPlayers(appData.participants)) return 0;
11132 ASSIGN(appData.tourneyFile, name);
11133 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11134 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11137 appData.noChessProgram = FALSE;
11138 appData.clockMode = TRUE;
11144 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11146 char buf[MSG_SIZ], *p, *q;
11147 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11148 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11149 skip = !all && group[0]; // if group requested, we start in skip mode
11150 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11151 p = names; q = buf; header = 0;
11152 while(*p && *p != '\n') *q++ = *p++;
11154 if(*p == '\n') p++;
11155 if(buf[0] == '#') {
11156 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11157 depth++; // we must be entering a new group
11158 if(all) continue; // suppress printing group headers when complete list requested
11160 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11162 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11163 if(engineList[i]) free(engineList[i]);
11164 engineList[i] = strdup(buf);
11165 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11166 if(engineMnemonic[i]) free(engineMnemonic[i]);
11167 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11169 sscanf(q + 8, "%s", buf + strlen(buf));
11172 engineMnemonic[i] = strdup(buf);
11175 engineList[i] = engineMnemonic[i] = NULL;
11179 // following implemented as macro to avoid type limitations
11180 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11183 SwapEngines (int n)
11184 { // swap settings for first engine and other engine (so far only some selected options)
11189 SWAP(chessProgram, p)
11191 SWAP(hasOwnBookUCI, h)
11192 SWAP(protocolVersion, h)
11194 SWAP(scoreIsAbsolute, h)
11199 SWAP(engOptions, p)
11200 SWAP(engInitString, p)
11201 SWAP(computerString, p)
11203 SWAP(fenOverride, p)
11205 SWAP(accumulateTC, h)
11212 GetEngineLine (char *s, int n)
11216 extern char *icsNames;
11217 if(!s || !*s) return 0;
11218 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11219 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11220 if(!mnemonic[i]) return 0;
11221 if(n == 11) return 1; // just testing if there was a match
11222 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11223 if(n == 1) SwapEngines(n);
11224 ParseArgsFromString(buf);
11225 if(n == 1) SwapEngines(n);
11226 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11227 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11228 ParseArgsFromString(buf);
11234 SetPlayer (int player, char *p)
11235 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
11237 char buf[MSG_SIZ], *engineName;
11238 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11239 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11240 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11242 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11243 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11244 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11245 ParseArgsFromString(buf);
11246 } else { // no engine with this nickname is installed!
11247 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11248 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11249 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11251 DisplayError(buf, 0);
11258 char *recentEngines;
11261 RecentEngineEvent (int nr)
11264 // SwapEngines(1); // bump first to second
11265 // ReplaceEngine(&second, 1); // and load it there
11266 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11267 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11268 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11269 ReplaceEngine(&first, 0);
11270 FloatToFront(&appData.recentEngineList, command[n]);
11275 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11276 { // determine players from game number
11277 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11279 if(appData.tourneyType == 0) {
11280 roundsPerCycle = (nPlayers - 1) | 1;
11281 pairingsPerRound = nPlayers / 2;
11282 } else if(appData.tourneyType > 0) {
11283 roundsPerCycle = nPlayers - appData.tourneyType;
11284 pairingsPerRound = appData.tourneyType;
11286 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11287 gamesPerCycle = gamesPerRound * roundsPerCycle;
11288 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11289 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11290 curRound = nr / gamesPerRound; nr %= gamesPerRound;
11291 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11292 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11293 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11295 if(appData.cycleSync) *syncInterval = gamesPerCycle;
11296 if(appData.roundSync) *syncInterval = gamesPerRound;
11298 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11300 if(appData.tourneyType == 0) {
11301 if(curPairing == (nPlayers-1)/2 ) {
11302 *whitePlayer = curRound;
11303 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11305 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11306 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11307 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11308 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11310 } else if(appData.tourneyType > 1) {
11311 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11312 *whitePlayer = curRound + appData.tourneyType;
11313 } else if(appData.tourneyType > 0) {
11314 *whitePlayer = curPairing;
11315 *blackPlayer = curRound + appData.tourneyType;
11318 // take care of white/black alternation per round.
11319 // For cycles and games this is already taken care of by default, derived from matchGame!
11320 return curRound & 1;
11324 NextTourneyGame (int nr, int *swapColors)
11325 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11327 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11329 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11330 tf = fopen(appData.tourneyFile, "r");
11331 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11332 ParseArgsFromFile(tf); fclose(tf);
11333 InitTimeControls(); // TC might be altered from tourney file
11335 nPlayers = CountPlayers(appData.participants); // count participants
11336 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11337 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11340 p = q = appData.results;
11341 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11342 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11343 DisplayMessage(_("Waiting for other game(s)"),"");
11344 waitingForGame = TRUE;
11345 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11348 waitingForGame = FALSE;
11351 if(appData.tourneyType < 0) {
11352 if(nr>=0 && !pairingReceived) {
11354 if(pairing.pr == NoProc) {
11355 if(!appData.pairingEngine[0]) {
11356 DisplayFatalError(_("No pairing engine specified"), 0, 1);
11359 StartChessProgram(&pairing); // starts the pairing engine
11361 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11362 SendToProgram(buf, &pairing);
11363 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11364 SendToProgram(buf, &pairing);
11365 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11367 pairingReceived = 0; // ... so we continue here
11369 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11370 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11371 matchGame = 1; roundNr = nr / syncInterval + 1;
11374 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11376 // redefine engines, engine dir, etc.
11377 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11378 if(first.pr == NoProc) {
11379 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11380 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
11382 if(second.pr == NoProc) {
11384 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11385 SwapEngines(1); // and make that valid for second engine by swapping
11386 InitEngine(&second, 1);
11388 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
11389 UpdateLogos(FALSE); // leave display to ModeHiglight()
11395 { // performs game initialization that does not invoke engines, and then tries to start the game
11396 int res, firstWhite, swapColors = 0;
11397 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11398 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
11400 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11401 if(strcmp(buf, currentDebugFile)) { // name has changed
11402 FILE *f = fopen(buf, "w");
11403 if(f) { // if opening the new file failed, just keep using the old one
11404 ASSIGN(currentDebugFile, buf);
11408 if(appData.serverFileName) {
11409 if(serverFP) fclose(serverFP);
11410 serverFP = fopen(appData.serverFileName, "w");
11411 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11412 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11416 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11417 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11418 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11419 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11420 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11421 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11422 Reset(FALSE, first.pr != NoProc);
11423 res = LoadGameOrPosition(matchGame); // setup game
11424 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11425 if(!res) return; // abort when bad game/pos file
11426 TwoMachinesEvent();
11430 UserAdjudicationEvent (int result)
11432 ChessMove gameResult = GameIsDrawn;
11435 gameResult = WhiteWins;
11437 else if( result < 0 ) {
11438 gameResult = BlackWins;
11441 if( gameMode == TwoMachinesPlay ) {
11442 GameEnds( gameResult, "User adjudication", GE_XBOARD );
11447 // [HGM] save: calculate checksum of game to make games easily identifiable
11449 StringCheckSum (char *s)
11452 if(s==NULL) return 0;
11453 while(*s) i = i*259 + *s++;
11461 for(i=backwardMostMove; i<forwardMostMove; i++) {
11462 sum += pvInfoList[i].depth;
11463 sum += StringCheckSum(parseList[i]);
11464 sum += StringCheckSum(commentList[i]);
11467 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11468 return sum + StringCheckSum(commentList[i]);
11469 } // end of save patch
11472 GameEnds (ChessMove result, char *resultDetails, int whosays)
11474 GameMode nextGameMode;
11476 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11478 if(endingGame) return; /* [HGM] crash: forbid recursion */
11480 if(twoBoards) { // [HGM] dual: switch back to one board
11481 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11482 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11484 if (appData.debugMode) {
11485 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11486 result, resultDetails ? resultDetails : "(null)", whosays);
11489 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11491 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11493 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11494 /* If we are playing on ICS, the server decides when the
11495 game is over, but the engine can offer to draw, claim
11499 if (appData.zippyPlay && first.initDone) {
11500 if (result == GameIsDrawn) {
11501 /* In case draw still needs to be claimed */
11502 SendToICS(ics_prefix);
11503 SendToICS("draw\n");
11504 } else if (StrCaseStr(resultDetails, "resign")) {
11505 SendToICS(ics_prefix);
11506 SendToICS("resign\n");
11510 endingGame = 0; /* [HGM] crash */
11514 /* If we're loading the game from a file, stop */
11515 if (whosays == GE_FILE) {
11516 (void) StopLoadGameTimer();
11520 /* Cancel draw offers */
11521 first.offeredDraw = second.offeredDraw = 0;
11523 /* If this is an ICS game, only ICS can really say it's done;
11524 if not, anyone can. */
11525 isIcsGame = (gameMode == IcsPlayingWhite ||
11526 gameMode == IcsPlayingBlack ||
11527 gameMode == IcsObserving ||
11528 gameMode == IcsExamining);
11530 if (!isIcsGame || whosays == GE_ICS) {
11531 /* OK -- not an ICS game, or ICS said it was done */
11533 if (!isIcsGame && !appData.noChessProgram)
11534 SetUserThinkingEnables();
11536 /* [HGM] if a machine claims the game end we verify this claim */
11537 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11538 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11540 ChessMove trueResult = (ChessMove) -1;
11542 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11543 first.twoMachinesColor[0] :
11544 second.twoMachinesColor[0] ;
11546 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11547 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11548 /* [HGM] verify: engine mate claims accepted if they were flagged */
11549 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11551 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11552 /* [HGM] verify: engine mate claims accepted if they were flagged */
11553 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11555 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11556 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11559 // now verify win claims, but not in drop games, as we don't understand those yet
11560 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11561 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11562 (result == WhiteWins && claimer == 'w' ||
11563 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11564 if (appData.debugMode) {
11565 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11566 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11568 if(result != trueResult) {
11569 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11570 result = claimer == 'w' ? BlackWins : WhiteWins;
11571 resultDetails = buf;
11574 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11575 && (forwardMostMove <= backwardMostMove ||
11576 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11577 (claimer=='b')==(forwardMostMove&1))
11579 /* [HGM] verify: draws that were not flagged are false claims */
11580 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11581 result = claimer == 'w' ? BlackWins : WhiteWins;
11582 resultDetails = buf;
11584 /* (Claiming a loss is accepted no questions asked!) */
11585 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11586 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11587 result = GameUnfinished;
11588 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11590 /* [HGM] bare: don't allow bare King to win */
11591 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11592 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11593 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11594 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11595 && result != GameIsDrawn)
11596 { int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11597 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11598 int p = (signed char)boards[forwardMostMove][i][j] - color;
11599 if(p >= 0 && p <= (int)WhiteKing) k++;
11600 oppoKings += (p + color == WhiteKing + BlackPawn - color);
11602 if (appData.debugMode) {
11603 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11604 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11606 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11607 result = GameIsDrawn;
11608 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11609 resultDetails = buf;
11615 if(serverMoves != NULL && !loadFlag) { char c = '=';
11616 if(result==WhiteWins) c = '+';
11617 if(result==BlackWins) c = '-';
11618 if(resultDetails != NULL)
11619 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11621 if (resultDetails != NULL) {
11622 gameInfo.result = result;
11623 gameInfo.resultDetails = StrSave(resultDetails);
11625 /* display last move only if game was not loaded from file */
11626 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11627 DisplayMove(currentMove - 1);
11629 if (forwardMostMove != 0) {
11630 if (gameMode != PlayFromGameFile && gameMode != EditGame
11631 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11633 if (*appData.saveGameFile != NULLCHAR) {
11634 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11635 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11637 SaveGameToFile(appData.saveGameFile, TRUE);
11638 } else if (appData.autoSaveGames) {
11639 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11641 if (*appData.savePositionFile != NULLCHAR) {
11642 SavePositionToFile(appData.savePositionFile);
11644 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11648 /* Tell program how game ended in case it is learning */
11649 /* [HGM] Moved this to after saving the PGN, just in case */
11650 /* engine died and we got here through time loss. In that */
11651 /* case we will get a fatal error writing the pipe, which */
11652 /* would otherwise lose us the PGN. */
11653 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11654 /* output during GameEnds should never be fatal anymore */
11655 if (gameMode == MachinePlaysWhite ||
11656 gameMode == MachinePlaysBlack ||
11657 gameMode == TwoMachinesPlay ||
11658 gameMode == IcsPlayingWhite ||
11659 gameMode == IcsPlayingBlack ||
11660 gameMode == BeginningOfGame) {
11662 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11664 if (first.pr != NoProc) {
11665 SendToProgram(buf, &first);
11667 if (second.pr != NoProc &&
11668 gameMode == TwoMachinesPlay) {
11669 SendToProgram(buf, &second);
11674 if (appData.icsActive) {
11675 if (appData.quietPlay &&
11676 (gameMode == IcsPlayingWhite ||
11677 gameMode == IcsPlayingBlack)) {
11678 SendToICS(ics_prefix);
11679 SendToICS("set shout 1\n");
11681 nextGameMode = IcsIdle;
11682 ics_user_moved = FALSE;
11683 /* clean up premove. It's ugly when the game has ended and the
11684 * premove highlights are still on the board.
11687 gotPremove = FALSE;
11688 ClearPremoveHighlights();
11689 DrawPosition(FALSE, boards[currentMove]);
11691 if (whosays == GE_ICS) {
11694 if (gameMode == IcsPlayingWhite)
11696 else if(gameMode == IcsPlayingBlack)
11697 PlayIcsLossSound();
11700 if (gameMode == IcsPlayingBlack)
11702 else if(gameMode == IcsPlayingWhite)
11703 PlayIcsLossSound();
11706 PlayIcsDrawSound();
11709 PlayIcsUnfinishedSound();
11712 if(appData.quitNext) { ExitEvent(0); return; }
11713 } else if (gameMode == EditGame ||
11714 gameMode == PlayFromGameFile ||
11715 gameMode == AnalyzeMode ||
11716 gameMode == AnalyzeFile) {
11717 nextGameMode = gameMode;
11719 nextGameMode = EndOfGame;
11724 nextGameMode = gameMode;
11727 if (appData.noChessProgram) {
11728 gameMode = nextGameMode;
11730 endingGame = 0; /* [HGM] crash */
11735 /* Put first chess program into idle state */
11736 if (first.pr != NoProc &&
11737 (gameMode == MachinePlaysWhite ||
11738 gameMode == MachinePlaysBlack ||
11739 gameMode == TwoMachinesPlay ||
11740 gameMode == IcsPlayingWhite ||
11741 gameMode == IcsPlayingBlack ||
11742 gameMode == BeginningOfGame)) {
11743 SendToProgram("force\n", &first);
11744 if (first.usePing) {
11746 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11747 SendToProgram(buf, &first);
11750 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11751 /* Kill off first chess program */
11752 if (first.isr != NULL)
11753 RemoveInputSource(first.isr);
11756 if (first.pr != NoProc) {
11758 DoSleep( appData.delayBeforeQuit );
11759 SendToProgram("quit\n", &first);
11760 DestroyChildProcess(first.pr, 4 + first.useSigterm);
11761 first.reload = TRUE;
11765 if (second.reuse) {
11766 /* Put second chess program into idle state */
11767 if (second.pr != NoProc &&
11768 gameMode == TwoMachinesPlay) {
11769 SendToProgram("force\n", &second);
11770 if (second.usePing) {
11772 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11773 SendToProgram(buf, &second);
11776 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11777 /* Kill off second chess program */
11778 if (second.isr != NULL)
11779 RemoveInputSource(second.isr);
11782 if (second.pr != NoProc) {
11783 DoSleep( appData.delayBeforeQuit );
11784 SendToProgram("quit\n", &second);
11785 DestroyChildProcess(second.pr, 4 + second.useSigterm);
11786 second.reload = TRUE;
11788 second.pr = NoProc;
11791 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11792 char resChar = '=';
11796 if (first.twoMachinesColor[0] == 'w') {
11799 second.matchWins++;
11804 if (first.twoMachinesColor[0] == 'b') {
11807 second.matchWins++;
11810 case GameUnfinished:
11816 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11817 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11818 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11819 ReserveGame(nextGame, resChar); // sets nextGame
11820 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11821 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11822 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11824 if (nextGame <= appData.matchGames && !abortMatch) {
11825 gameMode = nextGameMode;
11826 matchGame = nextGame; // this will be overruled in tourney mode!
11827 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11828 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11829 endingGame = 0; /* [HGM] crash */
11832 gameMode = nextGameMode;
11833 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11834 first.tidy, second.tidy,
11835 first.matchWins, second.matchWins,
11836 appData.matchGames - (first.matchWins + second.matchWins));
11837 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11838 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11839 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11840 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11841 first.twoMachinesColor = "black\n";
11842 second.twoMachinesColor = "white\n";
11844 first.twoMachinesColor = "white\n";
11845 second.twoMachinesColor = "black\n";
11849 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11850 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11852 gameMode = nextGameMode;
11854 endingGame = 0; /* [HGM] crash */
11855 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11856 if(matchMode == TRUE) { // match through command line: exit with or without popup
11858 ToNrEvent(forwardMostMove);
11859 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11861 } else DisplayFatalError(buf, 0, 0);
11862 } else { // match through menu; just stop, with or without popup
11863 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11866 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11867 } else DisplayNote(buf);
11869 if(ranking) free(ranking);
11873 /* Assumes program was just initialized (initString sent).
11874 Leaves program in force mode. */
11876 FeedMovesToProgram (ChessProgramState *cps, int upto)
11880 if (appData.debugMode)
11881 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11882 startedFromSetupPosition ? "position and " : "",
11883 backwardMostMove, upto, cps->which);
11884 if(currentlyInitializedVariant != gameInfo.variant) {
11886 // [HGM] variantswitch: make engine aware of new variant
11887 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11888 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11889 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11890 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11891 SendToProgram(buf, cps);
11892 currentlyInitializedVariant = gameInfo.variant;
11894 SendToProgram("force\n", cps);
11895 if (startedFromSetupPosition) {
11896 SendBoard(cps, backwardMostMove);
11897 if (appData.debugMode) {
11898 fprintf(debugFP, "feedMoves\n");
11901 for (i = backwardMostMove; i < upto; i++) {
11902 SendMoveToProgram(i, cps);
11908 ResurrectChessProgram ()
11910 /* The chess program may have exited.
11911 If so, restart it and feed it all the moves made so far. */
11912 static int doInit = 0;
11914 if (appData.noChessProgram) return 1;
11916 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11917 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11918 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11919 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11921 if (first.pr != NoProc) return 1;
11922 StartChessProgram(&first);
11924 InitChessProgram(&first, FALSE);
11925 FeedMovesToProgram(&first, currentMove);
11927 if (!first.sendTime) {
11928 /* can't tell gnuchess what its clock should read,
11929 so we bow to its notion. */
11931 timeRemaining[0][currentMove] = whiteTimeRemaining;
11932 timeRemaining[1][currentMove] = blackTimeRemaining;
11935 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11936 appData.icsEngineAnalyze) && first.analysisSupport) {
11937 SendToProgram("analyze\n", &first);
11938 first.analyzing = TRUE;
11944 * Button procedures
11947 Reset (int redraw, int init)
11951 if (appData.debugMode) {
11952 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11953 redraw, init, gameMode);
11955 pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11956 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11957 CleanupTail(); // [HGM] vari: delete any stored variations
11958 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11959 pausing = pauseExamInvalid = FALSE;
11960 startedFromSetupPosition = blackPlaysFirst = FALSE;
11962 whiteFlag = blackFlag = FALSE;
11963 userOfferedDraw = FALSE;
11964 hintRequested = bookRequested = FALSE;
11965 first.maybeThinking = FALSE;
11966 second.maybeThinking = FALSE;
11967 first.bookSuspend = FALSE; // [HGM] book
11968 second.bookSuspend = FALSE;
11969 thinkOutput[0] = NULLCHAR;
11970 lastHint[0] = NULLCHAR;
11971 ClearGameInfo(&gameInfo);
11972 gameInfo.variant = StringToVariant(appData.variant);
11973 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11974 ics_user_moved = ics_clock_paused = FALSE;
11975 ics_getting_history = H_FALSE;
11977 white_holding[0] = black_holding[0] = NULLCHAR;
11978 ClearProgramStats();
11979 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11983 flipView = appData.flipView;
11984 ClearPremoveHighlights();
11985 gotPremove = FALSE;
11986 alarmSounded = FALSE;
11987 killX = killY = kill2X = kill2Y = -1; // [HGM] lion
11989 GameEnds(EndOfFile, NULL, GE_PLAYER);
11990 if(appData.serverMovesName != NULL) {
11991 /* [HGM] prepare to make moves file for broadcasting */
11992 clock_t t = clock();
11993 if(serverMoves != NULL) fclose(serverMoves);
11994 serverMoves = fopen(appData.serverMovesName, "r");
11995 if(serverMoves != NULL) {
11996 fclose(serverMoves);
11997 /* delay 15 sec before overwriting, so all clients can see end */
11998 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12000 serverMoves = fopen(appData.serverMovesName, "w");
12004 gameMode = BeginningOfGame;
12006 if(appData.icsActive) gameInfo.variant = VariantNormal;
12007 currentMove = forwardMostMove = backwardMostMove = 0;
12008 MarkTargetSquares(1);
12009 InitPosition(redraw);
12010 for (i = 0; i < MAX_MOVES; i++) {
12011 if (commentList[i] != NULL) {
12012 free(commentList[i]);
12013 commentList[i] = NULL;
12017 timeRemaining[0][0] = whiteTimeRemaining;
12018 timeRemaining[1][0] = blackTimeRemaining;
12020 if (first.pr == NoProc) {
12021 StartChessProgram(&first);
12024 InitChessProgram(&first, startedFromSetupPosition);
12027 DisplayMessage("", "");
12028 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12029 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12030 ClearMap(); // [HGM] exclude: invalidate map
12034 AutoPlayGameLoop ()
12037 if (!AutoPlayOneMove())
12039 if (matchMode || appData.timeDelay == 0)
12041 if (appData.timeDelay < 0)
12043 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12051 ReloadGame(1); // next game
12057 int fromX, fromY, toX, toY;
12059 if (appData.debugMode) {
12060 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12063 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12066 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12067 pvInfoList[currentMove].depth = programStats.depth;
12068 pvInfoList[currentMove].score = programStats.score;
12069 pvInfoList[currentMove].time = 0;
12070 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12071 else { // append analysis of final position as comment
12073 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12074 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12076 programStats.depth = 0;
12079 if (currentMove >= forwardMostMove) {
12080 if(gameMode == AnalyzeFile) {
12081 if(appData.loadGameIndex == -1) {
12082 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12083 ScheduleDelayedEvent(AnalyzeNextGame, 10);
12085 ExitAnalyzeMode(); SendToProgram("force\n", &first);
12088 // gameMode = EndOfGame;
12089 // ModeHighlight();
12091 /* [AS] Clear current move marker at the end of a game */
12092 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12097 toX = moveList[currentMove][2] - AAA;
12098 toY = moveList[currentMove][3] - ONE;
12100 if (moveList[currentMove][1] == '@') {
12101 if (appData.highlightLastMove) {
12102 SetHighlights(-1, -1, toX, toY);
12105 fromX = moveList[currentMove][0] - AAA;
12106 fromY = moveList[currentMove][1] - ONE;
12108 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12110 if(moveList[currentMove][4] == ';') { // multi-leg
12111 killX = moveList[currentMove][5] - AAA;
12112 killY = moveList[currentMove][6] - ONE;
12114 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12115 killX = killY = -1;
12117 if (appData.highlightLastMove) {
12118 SetHighlights(fromX, fromY, toX, toY);
12121 DisplayMove(currentMove);
12122 SendMoveToProgram(currentMove++, &first);
12123 DisplayBothClocks();
12124 DrawPosition(FALSE, boards[currentMove]);
12125 // [HGM] PV info: always display, routine tests if empty
12126 DisplayComment(currentMove - 1, commentList[currentMove]);
12132 LoadGameOneMove (ChessMove readAhead)
12134 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12135 char promoChar = NULLCHAR;
12136 ChessMove moveType;
12137 char move[MSG_SIZ];
12140 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12141 gameMode != AnalyzeMode && gameMode != Training) {
12146 yyboardindex = forwardMostMove;
12147 if (readAhead != EndOfFile) {
12148 moveType = readAhead;
12150 if (gameFileFP == NULL)
12152 moveType = (ChessMove) Myylex();
12156 switch (moveType) {
12158 if (appData.debugMode)
12159 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12162 /* append the comment but don't display it */
12163 AppendComment(currentMove, p, FALSE);
12166 case WhiteCapturesEnPassant:
12167 case BlackCapturesEnPassant:
12168 case WhitePromotion:
12169 case BlackPromotion:
12170 case WhiteNonPromotion:
12171 case BlackNonPromotion:
12174 case WhiteKingSideCastle:
12175 case WhiteQueenSideCastle:
12176 case BlackKingSideCastle:
12177 case BlackQueenSideCastle:
12178 case WhiteKingSideCastleWild:
12179 case WhiteQueenSideCastleWild:
12180 case BlackKingSideCastleWild:
12181 case BlackQueenSideCastleWild:
12183 case WhiteHSideCastleFR:
12184 case WhiteASideCastleFR:
12185 case BlackHSideCastleFR:
12186 case BlackASideCastleFR:
12188 if (appData.debugMode)
12189 fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12190 fromX = currentMoveString[0] - AAA;
12191 fromY = currentMoveString[1] - ONE;
12192 toX = currentMoveString[2] - AAA;
12193 toY = currentMoveString[3] - ONE;
12194 promoChar = currentMoveString[4];
12195 if(promoChar == ';') promoChar = currentMoveString[7];
12200 if (appData.debugMode)
12201 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12202 fromX = moveType == WhiteDrop ?
12203 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12204 (int) CharToPiece(ToLower(currentMoveString[0]));
12206 toX = currentMoveString[2] - AAA;
12207 toY = currentMoveString[3] - ONE;
12213 case GameUnfinished:
12214 if (appData.debugMode)
12215 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12216 p = strchr(yy_text, '{');
12217 if (p == NULL) p = strchr(yy_text, '(');
12220 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12222 q = strchr(p, *p == '{' ? '}' : ')');
12223 if (q != NULL) *q = NULLCHAR;
12226 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12227 GameEnds(moveType, p, GE_FILE);
12229 if (cmailMsgLoaded) {
12231 flipView = WhiteOnMove(currentMove);
12232 if (moveType == GameUnfinished) flipView = !flipView;
12233 if (appData.debugMode)
12234 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12239 if (appData.debugMode)
12240 fprintf(debugFP, "Parser hit end of file\n");
12241 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12247 if (WhiteOnMove(currentMove)) {
12248 GameEnds(BlackWins, "Black mates", GE_FILE);
12250 GameEnds(WhiteWins, "White mates", GE_FILE);
12254 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12260 case MoveNumberOne:
12261 if (lastLoadGameStart == GNUChessGame) {
12262 /* GNUChessGames have numbers, but they aren't move numbers */
12263 if (appData.debugMode)
12264 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12265 yy_text, (int) moveType);
12266 return LoadGameOneMove(EndOfFile); /* tail recursion */
12268 /* else fall thru */
12273 /* Reached start of next game in file */
12274 if (appData.debugMode)
12275 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12276 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12282 if (WhiteOnMove(currentMove)) {
12283 GameEnds(BlackWins, "Black mates", GE_FILE);
12285 GameEnds(WhiteWins, "White mates", GE_FILE);
12289 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12295 case PositionDiagram: /* should not happen; ignore */
12296 case ElapsedTime: /* ignore */
12297 case NAG: /* ignore */
12298 if (appData.debugMode)
12299 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12300 yy_text, (int) moveType);
12301 return LoadGameOneMove(EndOfFile); /* tail recursion */
12304 if (appData.testLegality) {
12305 if (appData.debugMode)
12306 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12307 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12308 (forwardMostMove / 2) + 1,
12309 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12310 DisplayError(move, 0);
12313 if (appData.debugMode)
12314 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12315 yy_text, currentMoveString);
12316 if(currentMoveString[1] == '@') {
12317 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12320 fromX = currentMoveString[0] - AAA;
12321 fromY = currentMoveString[1] - ONE;
12323 toX = currentMoveString[2] - AAA;
12324 toY = currentMoveString[3] - ONE;
12325 promoChar = currentMoveString[4];
12329 case AmbiguousMove:
12330 if (appData.debugMode)
12331 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12332 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12333 (forwardMostMove / 2) + 1,
12334 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12335 DisplayError(move, 0);
12340 case ImpossibleMove:
12341 if (appData.debugMode)
12342 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12343 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12344 (forwardMostMove / 2) + 1,
12345 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12346 DisplayError(move, 0);
12352 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12353 DrawPosition(FALSE, boards[currentMove]);
12354 DisplayBothClocks();
12355 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12356 DisplayComment(currentMove - 1, commentList[currentMove]);
12358 (void) StopLoadGameTimer();
12360 cmailOldMove = forwardMostMove;
12363 /* currentMoveString is set as a side-effect of yylex */
12365 thinkOutput[0] = NULLCHAR;
12366 MakeMove(fromX, fromY, toX, toY, promoChar);
12367 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12368 currentMove = forwardMostMove;
12373 /* Load the nth game from the given file */
12375 LoadGameFromFile (char *filename, int n, char *title, int useList)
12380 if (strcmp(filename, "-") == 0) {
12384 f = fopen(filename, "rb");
12386 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12387 DisplayError(buf, errno);
12391 if (fseek(f, 0, 0) == -1) {
12392 /* f is not seekable; probably a pipe */
12395 if (useList && n == 0) {
12396 int error = GameListBuild(f);
12398 DisplayError(_("Cannot build game list"), error);
12399 } else if (!ListEmpty(&gameList) &&
12400 ((ListGame *) gameList.tailPred)->number > 1) {
12401 GameListPopUp(f, title);
12408 return LoadGame(f, n, title, FALSE);
12413 MakeRegisteredMove ()
12415 int fromX, fromY, toX, toY;
12417 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12418 switch (cmailMoveType[lastLoadGameNumber - 1]) {
12421 if (appData.debugMode)
12422 fprintf(debugFP, "Restoring %s for game %d\n",
12423 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12425 thinkOutput[0] = NULLCHAR;
12426 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12427 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12428 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12429 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12430 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12431 promoChar = cmailMove[lastLoadGameNumber - 1][4];
12432 MakeMove(fromX, fromY, toX, toY, promoChar);
12433 ShowMove(fromX, fromY, toX, toY);
12435 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12442 if (WhiteOnMove(currentMove)) {
12443 GameEnds(BlackWins, "Black mates", GE_PLAYER);
12445 GameEnds(WhiteWins, "White mates", GE_PLAYER);
12450 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12457 if (WhiteOnMove(currentMove)) {
12458 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12460 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12465 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12476 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12478 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12482 if (gameNumber > nCmailGames) {
12483 DisplayError(_("No more games in this message"), 0);
12486 if (f == lastLoadGameFP) {
12487 int offset = gameNumber - lastLoadGameNumber;
12489 cmailMsg[0] = NULLCHAR;
12490 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12491 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12492 nCmailMovesRegistered--;
12494 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12495 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12496 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12499 if (! RegisterMove()) return FALSE;
12503 retVal = LoadGame(f, gameNumber, title, useList);
12505 /* Make move registered during previous look at this game, if any */
12506 MakeRegisteredMove();
12508 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12509 commentList[currentMove]
12510 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12511 DisplayComment(currentMove - 1, commentList[currentMove]);
12517 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12519 ReloadGame (int offset)
12521 int gameNumber = lastLoadGameNumber + offset;
12522 if (lastLoadGameFP == NULL) {
12523 DisplayError(_("No game has been loaded yet"), 0);
12526 if (gameNumber <= 0) {
12527 DisplayError(_("Can't back up any further"), 0);
12530 if (cmailMsgLoaded) {
12531 return CmailLoadGame(lastLoadGameFP, gameNumber,
12532 lastLoadGameTitle, lastLoadGameUseList);
12534 return LoadGame(lastLoadGameFP, gameNumber,
12535 lastLoadGameTitle, lastLoadGameUseList);
12539 int keys[EmptySquare+1];
12542 PositionMatches (Board b1, Board b2)
12545 switch(appData.searchMode) {
12546 case 1: return CompareWithRights(b1, b2);
12548 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12549 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12553 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12554 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12555 sum += keys[b1[r][f]] - keys[b2[r][f]];
12559 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12560 sum += keys[b1[r][f]] - keys[b2[r][f]];
12572 int pieceList[256], quickBoard[256];
12573 ChessSquare pieceType[256] = { EmptySquare };
12574 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12575 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12576 int soughtTotal, turn;
12577 Boolean epOK, flipSearch;
12580 unsigned char piece, to;
12583 #define DSIZE (250000)
12585 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12586 Move *moveDatabase = initialSpace;
12587 unsigned int movePtr, dataSize = DSIZE;
12590 MakePieceList (Board board, int *counts)
12592 int r, f, n=Q_PROMO, total=0;
12593 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12594 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12595 int sq = f + (r<<4);
12596 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12597 quickBoard[sq] = ++n;
12599 pieceType[n] = board[r][f];
12600 counts[board[r][f]]++;
12601 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12602 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12606 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12611 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12613 int sq = fromX + (fromY<<4);
12614 int piece = quickBoard[sq], rook;
12615 quickBoard[sq] = 0;
12616 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12617 if(piece == pieceList[1] && fromY == toY) {
12618 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12619 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12620 moveDatabase[movePtr++].piece = Q_WCASTL;
12621 quickBoard[sq] = piece;
12622 piece = quickBoard[from]; quickBoard[from] = 0;
12623 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12624 } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12625 quickBoard[sq] = 0; // remove Rook
12626 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12627 moveDatabase[movePtr++].piece = Q_WCASTL;
12628 quickBoard[sq] = pieceList[1]; // put King
12630 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12633 if(piece == pieceList[2] && fromY == toY) {
12634 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12635 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12636 moveDatabase[movePtr++].piece = Q_BCASTL;
12637 quickBoard[sq] = piece;
12638 piece = quickBoard[from]; quickBoard[from] = 0;
12639 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12640 } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12641 quickBoard[sq] = 0; // remove Rook
12642 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12643 moveDatabase[movePtr++].piece = Q_BCASTL;
12644 quickBoard[sq] = pieceList[2]; // put King
12646 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12649 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12650 quickBoard[(fromY<<4)+toX] = 0;
12651 moveDatabase[movePtr].piece = Q_EP;
12652 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12653 moveDatabase[movePtr].to = sq;
12655 if(promoPiece != pieceType[piece]) {
12656 moveDatabase[movePtr++].piece = Q_PROMO;
12657 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12659 moveDatabase[movePtr].piece = piece;
12660 quickBoard[sq] = piece;
12665 PackGame (Board board)
12667 Move *newSpace = NULL;
12668 moveDatabase[movePtr].piece = 0; // terminate previous game
12669 if(movePtr > dataSize) {
12670 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12671 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12672 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12675 Move *p = moveDatabase, *q = newSpace;
12676 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12677 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12678 moveDatabase = newSpace;
12679 } else { // calloc failed, we must be out of memory. Too bad...
12680 dataSize = 0; // prevent calloc events for all subsequent games
12681 return 0; // and signal this one isn't cached
12685 MakePieceList(board, counts);
12690 QuickCompare (Board board, int *minCounts, int *maxCounts)
12691 { // compare according to search mode
12693 switch(appData.searchMode)
12695 case 1: // exact position match
12696 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12697 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12698 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12701 case 2: // can have extra material on empty squares
12702 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12703 if(board[r][f] == EmptySquare) continue;
12704 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12707 case 3: // material with exact Pawn structure
12708 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12709 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12710 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12711 } // fall through to material comparison
12712 case 4: // exact material
12713 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12715 case 6: // material range with given imbalance
12716 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12717 // fall through to range comparison
12718 case 5: // material range
12719 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12725 QuickScan (Board board, Move *move)
12726 { // reconstruct game,and compare all positions in it
12727 int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12729 int piece = move->piece;
12730 int to = move->to, from = pieceList[piece];
12731 if(found < 0) { // if already found just scan to game end for final piece count
12732 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12733 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12734 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12735 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12737 static int lastCounts[EmptySquare+1];
12739 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12740 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12741 } else stretch = 0;
12742 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12743 if(found >= 0 && !appData.minPieces) return found;
12745 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12746 if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12747 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12748 piece = (++move)->piece;
12749 from = pieceList[piece];
12750 counts[pieceType[piece]]--;
12751 pieceType[piece] = (ChessSquare) move->to;
12752 counts[move->to]++;
12753 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12754 counts[pieceType[quickBoard[to]]]--;
12755 quickBoard[to] = 0; total--;
12758 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12759 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12760 from = pieceList[piece]; // so this must be King
12761 quickBoard[from] = 0;
12762 pieceList[piece] = to;
12763 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12764 quickBoard[from] = 0; // rook
12765 quickBoard[to] = piece;
12766 to = move->to; piece = move->piece;
12770 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12771 if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12772 quickBoard[from] = 0;
12774 quickBoard[to] = piece;
12775 pieceList[piece] = to;
12785 flipSearch = FALSE;
12786 CopyBoard(soughtBoard, boards[currentMove]);
12787 soughtTotal = MakePieceList(soughtBoard, maxSought);
12788 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12789 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12790 CopyBoard(reverseBoard, boards[currentMove]);
12791 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12792 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12793 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12794 reverseBoard[r][f] = piece;
12796 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12797 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12798 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12799 || (boards[currentMove][CASTLING][2] == NoRights ||
12800 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12801 && (boards[currentMove][CASTLING][5] == NoRights ||
12802 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12805 CopyBoard(flipBoard, soughtBoard);
12806 CopyBoard(rotateBoard, reverseBoard);
12807 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12808 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12809 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12812 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12813 if(appData.searchMode >= 5) {
12814 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12815 MakePieceList(soughtBoard, minSought);
12816 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12818 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12819 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12822 GameInfo dummyInfo;
12823 static int creatingBook;
12826 GameContainsPosition (FILE *f, ListGame *lg)
12828 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12829 int fromX, fromY, toX, toY;
12831 static int initDone=FALSE;
12833 // weed out games based on numerical tag comparison
12834 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12835 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12836 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12837 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12839 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12842 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12843 else CopyBoard(boards[scratch], initialPosition); // default start position
12846 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12847 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12850 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12851 fseek(f, lg->offset, 0);
12854 yyboardindex = scratch;
12855 quickFlag = plyNr+1;
12860 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12866 if(plyNr) return -1; // after we have seen moves, this is for new game
12869 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12870 case ImpossibleMove:
12871 case WhiteWins: // game ends here with these four
12874 case GameUnfinished:
12878 if(appData.testLegality) return -1;
12879 case WhiteCapturesEnPassant:
12880 case BlackCapturesEnPassant:
12881 case WhitePromotion:
12882 case BlackPromotion:
12883 case WhiteNonPromotion:
12884 case BlackNonPromotion:
12887 case WhiteKingSideCastle:
12888 case WhiteQueenSideCastle:
12889 case BlackKingSideCastle:
12890 case BlackQueenSideCastle:
12891 case WhiteKingSideCastleWild:
12892 case WhiteQueenSideCastleWild:
12893 case BlackKingSideCastleWild:
12894 case BlackQueenSideCastleWild:
12895 case WhiteHSideCastleFR:
12896 case WhiteASideCastleFR:
12897 case BlackHSideCastleFR:
12898 case BlackASideCastleFR:
12899 fromX = currentMoveString[0] - AAA;
12900 fromY = currentMoveString[1] - ONE;
12901 toX = currentMoveString[2] - AAA;
12902 toY = currentMoveString[3] - ONE;
12903 promoChar = currentMoveString[4];
12907 fromX = next == WhiteDrop ?
12908 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12909 (int) CharToPiece(ToLower(currentMoveString[0]));
12911 toX = currentMoveString[2] - AAA;
12912 toY = currentMoveString[3] - ONE;
12916 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12918 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12919 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12920 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12921 if(appData.findMirror) {
12922 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12923 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12928 /* Load the nth game from open file f */
12930 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12934 int gn = gameNumber;
12935 ListGame *lg = NULL;
12936 int numPGNTags = 0, i;
12938 GameMode oldGameMode;
12939 VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12940 char oldName[MSG_SIZ];
12942 safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12944 if (appData.debugMode)
12945 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12947 if (gameMode == Training )
12948 SetTrainingModeOff();
12950 oldGameMode = gameMode;
12951 if (gameMode != BeginningOfGame) {
12952 Reset(FALSE, TRUE);
12954 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
12957 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12958 fclose(lastLoadGameFP);
12962 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12965 fseek(f, lg->offset, 0);
12966 GameListHighlight(gameNumber);
12967 pos = lg->position;
12971 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12972 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12974 DisplayError(_("Game number out of range"), 0);
12979 if (fseek(f, 0, 0) == -1) {
12980 if (f == lastLoadGameFP ?
12981 gameNumber == lastLoadGameNumber + 1 :
12985 DisplayError(_("Can't seek on game file"), 0);
12990 lastLoadGameFP = f;
12991 lastLoadGameNumber = gameNumber;
12992 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12993 lastLoadGameUseList = useList;
12997 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12998 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12999 lg->gameInfo.black);
13001 } else if (*title != NULLCHAR) {
13002 if (gameNumber > 1) {
13003 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13006 DisplayTitle(title);
13010 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13011 gameMode = PlayFromGameFile;
13015 currentMove = forwardMostMove = backwardMostMove = 0;
13016 CopyBoard(boards[0], initialPosition);
13020 * Skip the first gn-1 games in the file.
13021 * Also skip over anything that precedes an identifiable
13022 * start of game marker, to avoid being confused by
13023 * garbage at the start of the file. Currently
13024 * recognized start of game markers are the move number "1",
13025 * the pattern "gnuchess .* game", the pattern
13026 * "^[#;%] [^ ]* game file", and a PGN tag block.
13027 * A game that starts with one of the latter two patterns
13028 * will also have a move number 1, possibly
13029 * following a position diagram.
13030 * 5-4-02: Let's try being more lenient and allowing a game to
13031 * start with an unnumbered move. Does that break anything?
13033 cm = lastLoadGameStart = EndOfFile;
13035 yyboardindex = forwardMostMove;
13036 cm = (ChessMove) Myylex();
13039 if (cmailMsgLoaded) {
13040 nCmailGames = CMAIL_MAX_GAMES - gn;
13043 DisplayError(_("Game not found in file"), 0);
13050 lastLoadGameStart = cm;
13053 case MoveNumberOne:
13054 switch (lastLoadGameStart) {
13059 case MoveNumberOne:
13061 gn--; /* count this game */
13062 lastLoadGameStart = cm;
13071 switch (lastLoadGameStart) {
13074 case MoveNumberOne:
13076 gn--; /* count this game */
13077 lastLoadGameStart = cm;
13080 lastLoadGameStart = cm; /* game counted already */
13088 yyboardindex = forwardMostMove;
13089 cm = (ChessMove) Myylex();
13090 } while (cm == PGNTag || cm == Comment);
13097 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13098 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
13099 != CMAIL_OLD_RESULT) {
13101 cmailResult[ CMAIL_MAX_GAMES
13102 - gn - 1] = CMAIL_OLD_RESULT;
13109 /* Only a NormalMove can be at the start of a game
13110 * without a position diagram. */
13111 if (lastLoadGameStart == EndOfFile ) {
13113 lastLoadGameStart = MoveNumberOne;
13122 if (appData.debugMode)
13123 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13125 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13127 if (cm == XBoardGame) {
13128 /* Skip any header junk before position diagram and/or move 1 */
13130 yyboardindex = forwardMostMove;
13131 cm = (ChessMove) Myylex();
13133 if (cm == EndOfFile ||
13134 cm == GNUChessGame || cm == XBoardGame) {
13135 /* Empty game; pretend end-of-file and handle later */
13140 if (cm == MoveNumberOne || cm == PositionDiagram ||
13141 cm == PGNTag || cm == Comment)
13144 } else if (cm == GNUChessGame) {
13145 if (gameInfo.event != NULL) {
13146 free(gameInfo.event);
13148 gameInfo.event = StrSave(yy_text);
13151 startedFromSetupPosition = startedFromPositionFile; // [HGM]
13152 while (cm == PGNTag) {
13153 if (appData.debugMode)
13154 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13155 err = ParsePGNTag(yy_text, &gameInfo);
13156 if (!err) numPGNTags++;
13158 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13159 if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13160 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13161 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13162 InitPosition(TRUE);
13163 oldVariant = gameInfo.variant;
13164 if (appData.debugMode)
13165 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13169 if (gameInfo.fen != NULL) {
13170 Board initial_position;
13171 startedFromSetupPosition = TRUE;
13172 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13174 DisplayError(_("Bad FEN position in file"), 0);
13177 CopyBoard(boards[0], initial_position);
13178 if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13179 CopyBoard(initialPosition, initial_position);
13180 if (blackPlaysFirst) {
13181 currentMove = forwardMostMove = backwardMostMove = 1;
13182 CopyBoard(boards[1], initial_position);
13183 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13184 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13185 timeRemaining[0][1] = whiteTimeRemaining;
13186 timeRemaining[1][1] = blackTimeRemaining;
13187 if (commentList[0] != NULL) {
13188 commentList[1] = commentList[0];
13189 commentList[0] = NULL;
13192 currentMove = forwardMostMove = backwardMostMove = 0;
13194 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13196 initialRulePlies = FENrulePlies;
13197 for( i=0; i< nrCastlingRights; i++ )
13198 initialRights[i] = initial_position[CASTLING][i];
13200 yyboardindex = forwardMostMove;
13201 free(gameInfo.fen);
13202 gameInfo.fen = NULL;
13205 yyboardindex = forwardMostMove;
13206 cm = (ChessMove) Myylex();
13208 /* Handle comments interspersed among the tags */
13209 while (cm == Comment) {
13211 if (appData.debugMode)
13212 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13214 AppendComment(currentMove, p, FALSE);
13215 yyboardindex = forwardMostMove;
13216 cm = (ChessMove) Myylex();
13220 /* don't rely on existence of Event tag since if game was
13221 * pasted from clipboard the Event tag may not exist
13223 if (numPGNTags > 0){
13225 if (gameInfo.variant == VariantNormal) {
13226 VariantClass v = StringToVariant(gameInfo.event);
13227 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13228 if(v < VariantShogi) gameInfo.variant = v;
13231 if( appData.autoDisplayTags ) {
13232 tags = PGNTags(&gameInfo);
13233 TagsPopUp(tags, CmailMsg());
13238 /* Make something up, but don't display it now */
13243 if (cm == PositionDiagram) {
13246 Board initial_position;
13248 if (appData.debugMode)
13249 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13251 if (!startedFromSetupPosition) {
13253 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13254 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13265 initial_position[i][j++] = CharToPiece(*p);
13268 while (*p == ' ' || *p == '\t' ||
13269 *p == '\n' || *p == '\r') p++;
13271 if (strncmp(p, "black", strlen("black"))==0)
13272 blackPlaysFirst = TRUE;
13274 blackPlaysFirst = FALSE;
13275 startedFromSetupPosition = TRUE;
13277 CopyBoard(boards[0], initial_position);
13278 if (blackPlaysFirst) {
13279 currentMove = forwardMostMove = backwardMostMove = 1;
13280 CopyBoard(boards[1], initial_position);
13281 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13282 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13283 timeRemaining[0][1] = whiteTimeRemaining;
13284 timeRemaining[1][1] = blackTimeRemaining;
13285 if (commentList[0] != NULL) {
13286 commentList[1] = commentList[0];
13287 commentList[0] = NULL;
13290 currentMove = forwardMostMove = backwardMostMove = 0;
13293 yyboardindex = forwardMostMove;
13294 cm = (ChessMove) Myylex();
13297 if(!creatingBook) {
13298 if (first.pr == NoProc) {
13299 StartChessProgram(&first);
13301 InitChessProgram(&first, FALSE);
13302 if(gameInfo.variant == VariantUnknown && *oldName) {
13303 safeStrCpy(engineVariant, oldName, MSG_SIZ);
13304 gameInfo.variant = v;
13306 SendToProgram("force\n", &first);
13307 if (startedFromSetupPosition) {
13308 SendBoard(&first, forwardMostMove);
13309 if (appData.debugMode) {
13310 fprintf(debugFP, "Load Game\n");
13312 DisplayBothClocks();
13316 /* [HGM] server: flag to write setup moves in broadcast file as one */
13317 loadFlag = appData.suppressLoadMoves;
13319 while (cm == Comment) {
13321 if (appData.debugMode)
13322 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13324 AppendComment(currentMove, p, FALSE);
13325 yyboardindex = forwardMostMove;
13326 cm = (ChessMove) Myylex();
13329 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13330 cm == WhiteWins || cm == BlackWins ||
13331 cm == GameIsDrawn || cm == GameUnfinished) {
13332 DisplayMessage("", _("No moves in game"));
13333 if (cmailMsgLoaded) {
13334 if (appData.debugMode)
13335 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13339 DrawPosition(FALSE, boards[currentMove]);
13340 DisplayBothClocks();
13341 gameMode = EditGame;
13348 // [HGM] PV info: routine tests if comment empty
13349 if (!matchMode && (pausing || appData.timeDelay != 0)) {
13350 DisplayComment(currentMove - 1, commentList[currentMove]);
13352 if (!matchMode && appData.timeDelay != 0)
13353 DrawPosition(FALSE, boards[currentMove]);
13355 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13356 programStats.ok_to_send = 1;
13359 /* if the first token after the PGN tags is a move
13360 * and not move number 1, retrieve it from the parser
13362 if (cm != MoveNumberOne)
13363 LoadGameOneMove(cm);
13365 /* load the remaining moves from the file */
13366 while (LoadGameOneMove(EndOfFile)) {
13367 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13368 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13371 /* rewind to the start of the game */
13372 currentMove = backwardMostMove;
13374 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13376 if (oldGameMode == AnalyzeFile) {
13377 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13378 AnalyzeFileEvent();
13380 if (oldGameMode == AnalyzeMode) {
13381 AnalyzeFileEvent();
13384 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13385 long int w, b; // [HGM] adjourn: restore saved clock times
13386 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13387 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13388 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13389 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13393 if(creatingBook) return TRUE;
13394 if (!matchMode && pos > 0) {
13395 ToNrEvent(pos); // [HGM] no autoplay if selected on position
13397 if (matchMode || appData.timeDelay == 0) {
13399 } else if (appData.timeDelay > 0) {
13400 AutoPlayGameLoop();
13403 if (appData.debugMode)
13404 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13406 loadFlag = 0; /* [HGM] true game starts */
13410 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13412 ReloadPosition (int offset)
13414 int positionNumber = lastLoadPositionNumber + offset;
13415 if (lastLoadPositionFP == NULL) {
13416 DisplayError(_("No position has been loaded yet"), 0);
13419 if (positionNumber <= 0) {
13420 DisplayError(_("Can't back up any further"), 0);
13423 return LoadPosition(lastLoadPositionFP, positionNumber,
13424 lastLoadPositionTitle);
13427 /* Load the nth position from the given file */
13429 LoadPositionFromFile (char *filename, int n, char *title)
13434 if (strcmp(filename, "-") == 0) {
13435 return LoadPosition(stdin, n, "stdin");
13437 f = fopen(filename, "rb");
13439 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13440 DisplayError(buf, errno);
13443 return LoadPosition(f, n, title);
13448 /* Load the nth position from the given open file, and close it */
13450 LoadPosition (FILE *f, int positionNumber, char *title)
13452 char *p, line[MSG_SIZ];
13453 Board initial_position;
13454 int i, j, fenMode, pn;
13456 if (gameMode == Training )
13457 SetTrainingModeOff();
13459 if (gameMode != BeginningOfGame) {
13460 Reset(FALSE, TRUE);
13462 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13463 fclose(lastLoadPositionFP);
13465 if (positionNumber == 0) positionNumber = 1;
13466 lastLoadPositionFP = f;
13467 lastLoadPositionNumber = positionNumber;
13468 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13469 if (first.pr == NoProc && !appData.noChessProgram) {
13470 StartChessProgram(&first);
13471 InitChessProgram(&first, FALSE);
13473 pn = positionNumber;
13474 if (positionNumber < 0) {
13475 /* Negative position number means to seek to that byte offset */
13476 if (fseek(f, -positionNumber, 0) == -1) {
13477 DisplayError(_("Can't seek on position file"), 0);
13482 if (fseek(f, 0, 0) == -1) {
13483 if (f == lastLoadPositionFP ?
13484 positionNumber == lastLoadPositionNumber + 1 :
13485 positionNumber == 1) {
13488 DisplayError(_("Can't seek on position file"), 0);
13493 /* See if this file is FEN or old-style xboard */
13494 if (fgets(line, MSG_SIZ, f) == NULL) {
13495 DisplayError(_("Position not found in file"), 0);
13498 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13499 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13502 if (fenMode || line[0] == '#') pn--;
13504 /* skip positions before number pn */
13505 if (fgets(line, MSG_SIZ, f) == NULL) {
13507 DisplayError(_("Position not found in file"), 0);
13510 if (fenMode || line[0] == '#') pn--;
13516 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13517 DisplayError(_("Bad FEN position in file"), 0);
13520 if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13521 sscanf(p+3, "%s", bestMove);
13522 } else *bestMove = NULLCHAR;
13524 (void) fgets(line, MSG_SIZ, f);
13525 (void) fgets(line, MSG_SIZ, f);
13527 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13528 (void) fgets(line, MSG_SIZ, f);
13529 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13532 initial_position[i][j++] = CharToPiece(*p);
13536 blackPlaysFirst = FALSE;
13538 (void) fgets(line, MSG_SIZ, f);
13539 if (strncmp(line, "black", strlen("black"))==0)
13540 blackPlaysFirst = TRUE;
13543 startedFromSetupPosition = TRUE;
13545 CopyBoard(boards[0], initial_position);
13546 if (blackPlaysFirst) {
13547 currentMove = forwardMostMove = backwardMostMove = 1;
13548 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13549 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13550 CopyBoard(boards[1], initial_position);
13551 DisplayMessage("", _("Black to play"));
13553 currentMove = forwardMostMove = backwardMostMove = 0;
13554 DisplayMessage("", _("White to play"));
13556 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13557 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13558 SendToProgram("force\n", &first);
13559 SendBoard(&first, forwardMostMove);
13561 if (appData.debugMode) {
13563 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13564 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13565 fprintf(debugFP, "Load Position\n");
13568 if (positionNumber > 1) {
13569 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13570 DisplayTitle(line);
13572 DisplayTitle(title);
13574 gameMode = EditGame;
13577 timeRemaining[0][1] = whiteTimeRemaining;
13578 timeRemaining[1][1] = blackTimeRemaining;
13579 DrawPosition(FALSE, boards[currentMove]);
13586 CopyPlayerNameIntoFileName (char **dest, char *src)
13588 while (*src != NULLCHAR && *src != ',') {
13593 *(*dest)++ = *src++;
13599 DefaultFileName (char *ext)
13601 static char def[MSG_SIZ];
13604 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13606 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13608 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13610 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13617 /* Save the current game to the given file */
13619 SaveGameToFile (char *filename, int append)
13623 int result, i, t,tot=0;
13625 if (strcmp(filename, "-") == 0) {
13626 return SaveGame(stdout, 0, NULL);
13628 for(i=0; i<10; i++) { // upto 10 tries
13629 f = fopen(filename, append ? "a" : "w");
13630 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13631 if(f || errno != 13) break;
13632 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13636 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13637 DisplayError(buf, errno);
13640 safeStrCpy(buf, lastMsg, MSG_SIZ);
13641 DisplayMessage(_("Waiting for access to save file"), "");
13642 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13643 DisplayMessage(_("Saving game"), "");
13644 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13645 result = SaveGame(f, 0, NULL);
13646 DisplayMessage(buf, "");
13653 SavePart (char *str)
13655 static char buf[MSG_SIZ];
13658 p = strchr(str, ' ');
13659 if (p == NULL) return str;
13660 strncpy(buf, str, p - str);
13661 buf[p - str] = NULLCHAR;
13665 #define PGN_MAX_LINE 75
13667 #define PGN_SIDE_WHITE 0
13668 #define PGN_SIDE_BLACK 1
13671 FindFirstMoveOutOfBook (int side)
13675 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13676 int index = backwardMostMove;
13677 int has_book_hit = 0;
13679 if( (index % 2) != side ) {
13683 while( index < forwardMostMove ) {
13684 /* Check to see if engine is in book */
13685 int depth = pvInfoList[index].depth;
13686 int score = pvInfoList[index].score;
13692 else if( score == 0 && depth == 63 ) {
13693 in_book = 1; /* Zappa */
13695 else if( score == 2 && depth == 99 ) {
13696 in_book = 1; /* Abrok */
13699 has_book_hit += in_book;
13715 GetOutOfBookInfo (char * buf)
13719 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13721 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13722 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13726 if( oob[0] >= 0 || oob[1] >= 0 ) {
13727 for( i=0; i<2; i++ ) {
13731 if( i > 0 && oob[0] >= 0 ) {
13732 strcat( buf, " " );
13735 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13736 sprintf( buf+strlen(buf), "%s%.2f",
13737 pvInfoList[idx].score >= 0 ? "+" : "",
13738 pvInfoList[idx].score / 100.0 );
13744 /* Save game in PGN style */
13746 SaveGamePGN2 (FILE *f)
13748 int i, offset, linelen, newblock;
13751 int movelen, numlen, blank;
13752 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13754 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13756 PrintPGNTags(f, &gameInfo);
13758 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13760 if (backwardMostMove > 0 || startedFromSetupPosition) {
13761 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13762 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13763 fprintf(f, "\n{--------------\n");
13764 PrintPosition(f, backwardMostMove);
13765 fprintf(f, "--------------}\n");
13769 /* [AS] Out of book annotation */
13770 if( appData.saveOutOfBookInfo ) {
13773 GetOutOfBookInfo( buf );
13775 if( buf[0] != '\0' ) {
13776 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13783 i = backwardMostMove;
13787 while (i < forwardMostMove) {
13788 /* Print comments preceding this move */
13789 if (commentList[i] != NULL) {
13790 if (linelen > 0) fprintf(f, "\n");
13791 fprintf(f, "%s", commentList[i]);
13796 /* Format move number */
13798 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13801 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13803 numtext[0] = NULLCHAR;
13805 numlen = strlen(numtext);
13808 /* Print move number */
13809 blank = linelen > 0 && numlen > 0;
13810 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13819 fprintf(f, "%s", numtext);
13823 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13824 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13827 blank = linelen > 0 && movelen > 0;
13828 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13837 fprintf(f, "%s", move_buffer);
13838 linelen += movelen;
13840 /* [AS] Add PV info if present */
13841 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13842 /* [HGM] add time */
13843 char buf[MSG_SIZ]; int seconds;
13845 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13851 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13854 seconds = (seconds + 4)/10; // round to full seconds
13856 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13858 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13861 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13862 pvInfoList[i].score >= 0 ? "+" : "",
13863 pvInfoList[i].score / 100.0,
13864 pvInfoList[i].depth,
13867 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13869 /* Print score/depth */
13870 blank = linelen > 0 && movelen > 0;
13871 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13880 fprintf(f, "%s", move_buffer);
13881 linelen += movelen;
13887 /* Start a new line */
13888 if (linelen > 0) fprintf(f, "\n");
13890 /* Print comments after last move */
13891 if (commentList[i] != NULL) {
13892 fprintf(f, "%s\n", commentList[i]);
13896 if (gameInfo.resultDetails != NULL &&
13897 gameInfo.resultDetails[0] != NULLCHAR) {
13898 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13899 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13900 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13901 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13902 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13904 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13908 /* Save game in PGN style and close the file */
13910 SaveGamePGN (FILE *f)
13914 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13918 /* Save game in old style and close the file */
13920 SaveGameOldStyle (FILE *f)
13925 tm = time((time_t *) NULL);
13927 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13930 if (backwardMostMove > 0 || startedFromSetupPosition) {
13931 fprintf(f, "\n[--------------\n");
13932 PrintPosition(f, backwardMostMove);
13933 fprintf(f, "--------------]\n");
13938 i = backwardMostMove;
13939 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13941 while (i < forwardMostMove) {
13942 if (commentList[i] != NULL) {
13943 fprintf(f, "[%s]\n", commentList[i]);
13946 if ((i % 2) == 1) {
13947 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13950 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13952 if (commentList[i] != NULL) {
13956 if (i >= forwardMostMove) {
13960 fprintf(f, "%s\n", parseList[i]);
13965 if (commentList[i] != NULL) {
13966 fprintf(f, "[%s]\n", commentList[i]);
13969 /* This isn't really the old style, but it's close enough */
13970 if (gameInfo.resultDetails != NULL &&
13971 gameInfo.resultDetails[0] != NULLCHAR) {
13972 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13973 gameInfo.resultDetails);
13975 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13982 /* Save the current game to open file f and close the file */
13984 SaveGame (FILE *f, int dummy, char *dummy2)
13986 if (gameMode == EditPosition) EditPositionDone(TRUE);
13987 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13988 if (appData.oldSaveStyle)
13989 return SaveGameOldStyle(f);
13991 return SaveGamePGN(f);
13994 /* Save the current position to the given file */
13996 SavePositionToFile (char *filename)
14001 if (strcmp(filename, "-") == 0) {
14002 return SavePosition(stdout, 0, NULL);
14004 f = fopen(filename, "a");
14006 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14007 DisplayError(buf, errno);
14010 safeStrCpy(buf, lastMsg, MSG_SIZ);
14011 DisplayMessage(_("Waiting for access to save file"), "");
14012 flock(fileno(f), LOCK_EX); // [HGM] lock
14013 DisplayMessage(_("Saving position"), "");
14014 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
14015 SavePosition(f, 0, NULL);
14016 DisplayMessage(buf, "");
14022 /* Save the current position to the given open file and close the file */
14024 SavePosition (FILE *f, int dummy, char *dummy2)
14029 if (gameMode == EditPosition) EditPositionDone(TRUE);
14030 if (appData.oldSaveStyle) {
14031 tm = time((time_t *) NULL);
14033 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14035 fprintf(f, "[--------------\n");
14036 PrintPosition(f, currentMove);
14037 fprintf(f, "--------------]\n");
14039 fen = PositionToFEN(currentMove, NULL, 1);
14040 fprintf(f, "%s\n", fen);
14048 ReloadCmailMsgEvent (int unregister)
14051 static char *inFilename = NULL;
14052 static char *outFilename;
14054 struct stat inbuf, outbuf;
14057 /* Any registered moves are unregistered if unregister is set, */
14058 /* i.e. invoked by the signal handler */
14060 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14061 cmailMoveRegistered[i] = FALSE;
14062 if (cmailCommentList[i] != NULL) {
14063 free(cmailCommentList[i]);
14064 cmailCommentList[i] = NULL;
14067 nCmailMovesRegistered = 0;
14070 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14071 cmailResult[i] = CMAIL_NOT_RESULT;
14075 if (inFilename == NULL) {
14076 /* Because the filenames are static they only get malloced once */
14077 /* and they never get freed */
14078 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14079 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14081 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14082 sprintf(outFilename, "%s.out", appData.cmailGameName);
14085 status = stat(outFilename, &outbuf);
14087 cmailMailedMove = FALSE;
14089 status = stat(inFilename, &inbuf);
14090 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14093 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14094 counts the games, notes how each one terminated, etc.
14096 It would be nice to remove this kludge and instead gather all
14097 the information while building the game list. (And to keep it
14098 in the game list nodes instead of having a bunch of fixed-size
14099 parallel arrays.) Note this will require getting each game's
14100 termination from the PGN tags, as the game list builder does
14101 not process the game moves. --mann
14103 cmailMsgLoaded = TRUE;
14104 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14106 /* Load first game in the file or popup game menu */
14107 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14109 #endif /* !WIN32 */
14117 char string[MSG_SIZ];
14119 if ( cmailMailedMove
14120 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14121 return TRUE; /* Allow free viewing */
14124 /* Unregister move to ensure that we don't leave RegisterMove */
14125 /* with the move registered when the conditions for registering no */
14127 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14128 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14129 nCmailMovesRegistered --;
14131 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14133 free(cmailCommentList[lastLoadGameNumber - 1]);
14134 cmailCommentList[lastLoadGameNumber - 1] = NULL;
14138 if (cmailOldMove == -1) {
14139 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14143 if (currentMove > cmailOldMove + 1) {
14144 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14148 if (currentMove < cmailOldMove) {
14149 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14153 if (forwardMostMove > currentMove) {
14154 /* Silently truncate extra moves */
14158 if ( (currentMove == cmailOldMove + 1)
14159 || ( (currentMove == cmailOldMove)
14160 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14161 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14162 if (gameInfo.result != GameUnfinished) {
14163 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14166 if (commentList[currentMove] != NULL) {
14167 cmailCommentList[lastLoadGameNumber - 1]
14168 = StrSave(commentList[currentMove]);
14170 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14172 if (appData.debugMode)
14173 fprintf(debugFP, "Saving %s for game %d\n",
14174 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14176 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14178 f = fopen(string, "w");
14179 if (appData.oldSaveStyle) {
14180 SaveGameOldStyle(f); /* also closes the file */
14182 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14183 f = fopen(string, "w");
14184 SavePosition(f, 0, NULL); /* also closes the file */
14186 fprintf(f, "{--------------\n");
14187 PrintPosition(f, currentMove);
14188 fprintf(f, "--------------}\n\n");
14190 SaveGame(f, 0, NULL); /* also closes the file*/
14193 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14194 nCmailMovesRegistered ++;
14195 } else if (nCmailGames == 1) {
14196 DisplayError(_("You have not made a move yet"), 0);
14207 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14208 FILE *commandOutput;
14209 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14210 int nBytes = 0; /* Suppress warnings on uninitialized variables */
14216 if (! cmailMsgLoaded) {
14217 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14221 if (nCmailGames == nCmailResults) {
14222 DisplayError(_("No unfinished games"), 0);
14226 #if CMAIL_PROHIBIT_REMAIL
14227 if (cmailMailedMove) {
14228 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);
14229 DisplayError(msg, 0);
14234 if (! (cmailMailedMove || RegisterMove())) return;
14236 if ( cmailMailedMove
14237 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14238 snprintf(string, MSG_SIZ, partCommandString,
14239 appData.debugMode ? " -v" : "", appData.cmailGameName);
14240 commandOutput = popen(string, "r");
14242 if (commandOutput == NULL) {
14243 DisplayError(_("Failed to invoke cmail"), 0);
14245 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14246 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14248 if (nBuffers > 1) {
14249 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14250 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14251 nBytes = MSG_SIZ - 1;
14253 (void) memcpy(msg, buffer, nBytes);
14255 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14257 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14258 cmailMailedMove = TRUE; /* Prevent >1 moves */
14261 for (i = 0; i < nCmailGames; i ++) {
14262 if (cmailResult[i] == CMAIL_NOT_RESULT) {
14267 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14269 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14271 appData.cmailGameName,
14273 LoadGameFromFile(buffer, 1, buffer, FALSE);
14274 cmailMsgLoaded = FALSE;
14278 DisplayInformation(msg);
14279 pclose(commandOutput);
14282 if ((*cmailMsg) != '\0') {
14283 DisplayInformation(cmailMsg);
14288 #endif /* !WIN32 */
14297 int prependComma = 0;
14299 char string[MSG_SIZ]; /* Space for game-list */
14302 if (!cmailMsgLoaded) return "";
14304 if (cmailMailedMove) {
14305 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14307 /* Create a list of games left */
14308 snprintf(string, MSG_SIZ, "[");
14309 for (i = 0; i < nCmailGames; i ++) {
14310 if (! ( cmailMoveRegistered[i]
14311 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14312 if (prependComma) {
14313 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14315 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14319 strcat(string, number);
14322 strcat(string, "]");
14324 if (nCmailMovesRegistered + nCmailResults == 0) {
14325 switch (nCmailGames) {
14327 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14331 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14335 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14340 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14342 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14347 if (nCmailResults == nCmailGames) {
14348 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14350 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14355 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14367 if (gameMode == Training)
14368 SetTrainingModeOff();
14371 cmailMsgLoaded = FALSE;
14372 if (appData.icsActive) {
14373 SendToICS(ics_prefix);
14374 SendToICS("refresh\n");
14379 ExitEvent (int status)
14383 /* Give up on clean exit */
14387 /* Keep trying for clean exit */
14391 if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14392 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14394 if (telnetISR != NULL) {
14395 RemoveInputSource(telnetISR);
14397 if (icsPR != NoProc) {
14398 DestroyChildProcess(icsPR, TRUE);
14401 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14402 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14404 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14405 /* make sure this other one finishes before killing it! */
14406 if(endingGame) { int count = 0;
14407 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14408 while(endingGame && count++ < 10) DoSleep(1);
14409 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14412 /* Kill off chess programs */
14413 if (first.pr != NoProc) {
14416 DoSleep( appData.delayBeforeQuit );
14417 SendToProgram("quit\n", &first);
14418 DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14420 if (second.pr != NoProc) {
14421 DoSleep( appData.delayBeforeQuit );
14422 SendToProgram("quit\n", &second);
14423 DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14425 if (first.isr != NULL) {
14426 RemoveInputSource(first.isr);
14428 if (second.isr != NULL) {
14429 RemoveInputSource(second.isr);
14432 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14433 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14435 ShutDownFrontEnd();
14440 PauseEngine (ChessProgramState *cps)
14442 SendToProgram("pause\n", cps);
14447 UnPauseEngine (ChessProgramState *cps)
14449 SendToProgram("resume\n", cps);
14456 if (appData.debugMode)
14457 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14461 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14463 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14464 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14465 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14467 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14468 HandleMachineMove(stashedInputMove, stalledEngine);
14469 stalledEngine = NULL;
14472 if (gameMode == MachinePlaysWhite ||
14473 gameMode == TwoMachinesPlay ||
14474 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14475 if(first.pause) UnPauseEngine(&first);
14476 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14477 if(second.pause) UnPauseEngine(&second);
14478 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14481 DisplayBothClocks();
14483 if (gameMode == PlayFromGameFile) {
14484 if (appData.timeDelay >= 0)
14485 AutoPlayGameLoop();
14486 } else if (gameMode == IcsExamining && pauseExamInvalid) {
14487 Reset(FALSE, TRUE);
14488 SendToICS(ics_prefix);
14489 SendToICS("refresh\n");
14490 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14491 ForwardInner(forwardMostMove);
14493 pauseExamInvalid = FALSE;
14495 switch (gameMode) {
14499 pauseExamForwardMostMove = forwardMostMove;
14500 pauseExamInvalid = FALSE;
14503 case IcsPlayingWhite:
14504 case IcsPlayingBlack:
14508 case PlayFromGameFile:
14509 (void) StopLoadGameTimer();
14513 case BeginningOfGame:
14514 if (appData.icsActive) return;
14515 /* else fall through */
14516 case MachinePlaysWhite:
14517 case MachinePlaysBlack:
14518 case TwoMachinesPlay:
14519 if (forwardMostMove == 0)
14520 return; /* don't pause if no one has moved */
14521 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14522 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14523 if(onMove->pause) { // thinking engine can be paused
14524 PauseEngine(onMove); // do it
14525 if(onMove->other->pause) // pondering opponent can always be paused immediately
14526 PauseEngine(onMove->other);
14528 SendToProgram("easy\n", onMove->other);
14530 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14531 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14533 PauseEngine(&first);
14535 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14536 } else { // human on move, pause pondering by either method
14538 PauseEngine(&first);
14539 else if(appData.ponderNextMove)
14540 SendToProgram("easy\n", &first);
14543 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14553 EditCommentEvent ()
14555 char title[MSG_SIZ];
14557 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14558 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14560 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14561 WhiteOnMove(currentMove - 1) ? " " : ".. ",
14562 parseList[currentMove - 1]);
14565 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14572 char *tags = PGNTags(&gameInfo);
14574 EditTagsPopUp(tags, NULL);
14581 if(second.analyzing) {
14582 SendToProgram("exit\n", &second);
14583 second.analyzing = FALSE;
14585 if (second.pr == NoProc) StartChessProgram(&second);
14586 InitChessProgram(&second, FALSE);
14587 FeedMovesToProgram(&second, currentMove);
14589 SendToProgram("analyze\n", &second);
14590 second.analyzing = TRUE;
14594 /* Toggle ShowThinking */
14596 ToggleShowThinking()
14598 appData.showThinking = !appData.showThinking;
14599 ShowThinkingEvent();
14603 AnalyzeModeEvent ()
14607 if (!first.analysisSupport) {
14608 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14609 DisplayError(buf, 0);
14612 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14613 if (appData.icsActive) {
14614 if (gameMode != IcsObserving) {
14615 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14616 DisplayError(buf, 0);
14618 if (appData.icsEngineAnalyze) {
14619 if (appData.debugMode)
14620 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14626 /* if enable, user wants to disable icsEngineAnalyze */
14627 if (appData.icsEngineAnalyze) {
14632 appData.icsEngineAnalyze = TRUE;
14633 if (appData.debugMode)
14634 fprintf(debugFP, "ICS engine analyze starting... \n");
14637 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14638 if (appData.noChessProgram || gameMode == AnalyzeMode)
14641 if (gameMode != AnalyzeFile) {
14642 if (!appData.icsEngineAnalyze) {
14644 if (gameMode != EditGame) return 0;
14646 if (!appData.showThinking) ToggleShowThinking();
14647 ResurrectChessProgram();
14648 SendToProgram("analyze\n", &first);
14649 first.analyzing = TRUE;
14650 /*first.maybeThinking = TRUE;*/
14651 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14652 EngineOutputPopUp();
14654 if (!appData.icsEngineAnalyze) {
14655 gameMode = AnalyzeMode;
14656 ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14662 StartAnalysisClock();
14663 GetTimeMark(&lastNodeCountTime);
14669 AnalyzeFileEvent ()
14671 if (appData.noChessProgram || gameMode == AnalyzeFile)
14674 if (!first.analysisSupport) {
14676 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14677 DisplayError(buf, 0);
14681 if (gameMode != AnalyzeMode) {
14682 keepInfo = 1; // mere annotating should not alter PGN tags
14685 if (gameMode != EditGame) return;
14686 if (!appData.showThinking) ToggleShowThinking();
14687 ResurrectChessProgram();
14688 SendToProgram("analyze\n", &first);
14689 first.analyzing = TRUE;
14690 /*first.maybeThinking = TRUE;*/
14691 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14692 EngineOutputPopUp();
14694 gameMode = AnalyzeFile;
14698 StartAnalysisClock();
14699 GetTimeMark(&lastNodeCountTime);
14701 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14702 AnalysisPeriodicEvent(1);
14706 MachineWhiteEvent ()
14709 char *bookHit = NULL;
14711 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14715 if (gameMode == PlayFromGameFile ||
14716 gameMode == TwoMachinesPlay ||
14717 gameMode == Training ||
14718 gameMode == AnalyzeMode ||
14719 gameMode == EndOfGame)
14722 if (gameMode == EditPosition)
14723 EditPositionDone(TRUE);
14725 if (!WhiteOnMove(currentMove)) {
14726 DisplayError(_("It is not White's turn"), 0);
14730 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14733 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14734 gameMode == AnalyzeFile)
14737 ResurrectChessProgram(); /* in case it isn't running */
14738 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14739 gameMode = MachinePlaysWhite;
14742 gameMode = MachinePlaysWhite;
14746 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14748 if (first.sendName) {
14749 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14750 SendToProgram(buf, &first);
14752 if (first.sendTime) {
14753 if (first.useColors) {
14754 SendToProgram("black\n", &first); /*gnu kludge*/
14756 SendTimeRemaining(&first, TRUE);
14758 if (first.useColors) {
14759 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14761 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14762 SetMachineThinkingEnables();
14763 first.maybeThinking = TRUE;
14767 if (appData.autoFlipView && !flipView) {
14768 flipView = !flipView;
14769 DrawPosition(FALSE, NULL);
14770 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14773 if(bookHit) { // [HGM] book: simulate book reply
14774 static char bookMove[MSG_SIZ]; // a bit generous?
14776 programStats.nodes = programStats.depth = programStats.time =
14777 programStats.score = programStats.got_only_move = 0;
14778 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14780 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14781 strcat(bookMove, bookHit);
14782 HandleMachineMove(bookMove, &first);
14787 MachineBlackEvent ()
14790 char *bookHit = NULL;
14792 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14796 if (gameMode == PlayFromGameFile ||
14797 gameMode == TwoMachinesPlay ||
14798 gameMode == Training ||
14799 gameMode == AnalyzeMode ||
14800 gameMode == EndOfGame)
14803 if (gameMode == EditPosition)
14804 EditPositionDone(TRUE);
14806 if (WhiteOnMove(currentMove)) {
14807 DisplayError(_("It is not Black's turn"), 0);
14811 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14814 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14815 gameMode == AnalyzeFile)
14818 ResurrectChessProgram(); /* in case it isn't running */
14819 gameMode = MachinePlaysBlack;
14823 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14825 if (first.sendName) {
14826 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14827 SendToProgram(buf, &first);
14829 if (first.sendTime) {
14830 if (first.useColors) {
14831 SendToProgram("white\n", &first); /*gnu kludge*/
14833 SendTimeRemaining(&first, FALSE);
14835 if (first.useColors) {
14836 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14838 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14839 SetMachineThinkingEnables();
14840 first.maybeThinking = TRUE;
14843 if (appData.autoFlipView && flipView) {
14844 flipView = !flipView;
14845 DrawPosition(FALSE, NULL);
14846 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14848 if(bookHit) { // [HGM] book: simulate book reply
14849 static char bookMove[MSG_SIZ]; // a bit generous?
14851 programStats.nodes = programStats.depth = programStats.time =
14852 programStats.score = programStats.got_only_move = 0;
14853 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14855 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14856 strcat(bookMove, bookHit);
14857 HandleMachineMove(bookMove, &first);
14863 DisplayTwoMachinesTitle ()
14866 if (appData.matchGames > 0) {
14867 if(appData.tourneyFile[0]) {
14868 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14869 gameInfo.white, _("vs."), gameInfo.black,
14870 nextGame+1, appData.matchGames+1,
14871 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14873 if (first.twoMachinesColor[0] == 'w') {
14874 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14875 gameInfo.white, _("vs."), gameInfo.black,
14876 first.matchWins, second.matchWins,
14877 matchGame - 1 - (first.matchWins + second.matchWins));
14879 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14880 gameInfo.white, _("vs."), gameInfo.black,
14881 second.matchWins, first.matchWins,
14882 matchGame - 1 - (first.matchWins + second.matchWins));
14885 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14891 SettingsMenuIfReady ()
14893 if (second.lastPing != second.lastPong) {
14894 DisplayMessage("", _("Waiting for second chess program"));
14895 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14899 DisplayMessage("", "");
14900 SettingsPopUp(&second);
14904 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14907 if (cps->pr == NoProc) {
14908 StartChessProgram(cps);
14909 if (cps->protocolVersion == 1) {
14911 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14913 /* kludge: allow timeout for initial "feature" command */
14914 if(retry != TwoMachinesEventIfReady) FreezeUI();
14915 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14916 DisplayMessage("", buf);
14917 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14925 TwoMachinesEvent P((void))
14929 ChessProgramState *onmove;
14930 char *bookHit = NULL;
14931 static int stalling = 0;
14935 if (appData.noChessProgram) return;
14937 switch (gameMode) {
14938 case TwoMachinesPlay:
14940 case MachinePlaysWhite:
14941 case MachinePlaysBlack:
14942 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14943 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14947 case BeginningOfGame:
14948 case PlayFromGameFile:
14951 if (gameMode != EditGame) return;
14954 EditPositionDone(TRUE);
14965 // forwardMostMove = currentMove;
14966 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14967 startingEngine = TRUE;
14969 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14971 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14972 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14973 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14976 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14978 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14979 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14980 startingEngine = matchMode = FALSE;
14981 DisplayError("second engine does not play this", 0);
14982 gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14983 EditGameEvent(); // switch back to EditGame mode
14988 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14989 SendToProgram("force\n", &second);
14991 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14994 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14995 if(appData.matchPause>10000 || appData.matchPause<10)
14996 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14997 wait = SubtractTimeMarks(&now, &pauseStart);
14998 if(wait < appData.matchPause) {
14999 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15002 // we are now committed to starting the game
15004 DisplayMessage("", "");
15005 if (startedFromSetupPosition) {
15006 SendBoard(&second, backwardMostMove);
15007 if (appData.debugMode) {
15008 fprintf(debugFP, "Two Machines\n");
15011 for (i = backwardMostMove; i < forwardMostMove; i++) {
15012 SendMoveToProgram(i, &second);
15015 gameMode = TwoMachinesPlay;
15016 pausing = startingEngine = FALSE;
15017 ModeHighlight(); // [HGM] logo: this triggers display update of logos
15019 DisplayTwoMachinesTitle();
15021 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15026 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15027 SendToProgram(first.computerString, &first);
15028 if (first.sendName) {
15029 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15030 SendToProgram(buf, &first);
15032 SendToProgram(second.computerString, &second);
15033 if (second.sendName) {
15034 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15035 SendToProgram(buf, &second);
15039 if (!first.sendTime || !second.sendTime) {
15040 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15041 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15043 if (onmove->sendTime) {
15044 if (onmove->useColors) {
15045 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15047 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15049 if (onmove->useColors) {
15050 SendToProgram(onmove->twoMachinesColor, onmove);
15052 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15053 // SendToProgram("go\n", onmove);
15054 onmove->maybeThinking = TRUE;
15055 SetMachineThinkingEnables();
15059 if(bookHit) { // [HGM] book: simulate book reply
15060 static char bookMove[MSG_SIZ]; // a bit generous?
15062 programStats.nodes = programStats.depth = programStats.time =
15063 programStats.score = programStats.got_only_move = 0;
15064 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15066 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15067 strcat(bookMove, bookHit);
15068 savedMessage = bookMove; // args for deferred call
15069 savedState = onmove;
15070 ScheduleDelayedEvent(DeferredBookMove, 1);
15077 if (gameMode == Training) {
15078 SetTrainingModeOff();
15079 gameMode = PlayFromGameFile;
15080 DisplayMessage("", _("Training mode off"));
15082 gameMode = Training;
15083 animateTraining = appData.animate;
15085 /* make sure we are not already at the end of the game */
15086 if (currentMove < forwardMostMove) {
15087 SetTrainingModeOn();
15088 DisplayMessage("", _("Training mode on"));
15090 gameMode = PlayFromGameFile;
15091 DisplayError(_("Already at end of game"), 0);
15100 if (!appData.icsActive) return;
15101 switch (gameMode) {
15102 case IcsPlayingWhite:
15103 case IcsPlayingBlack:
15106 case BeginningOfGame:
15114 EditPositionDone(TRUE);
15127 gameMode = IcsIdle;
15137 switch (gameMode) {
15139 SetTrainingModeOff();
15141 case MachinePlaysWhite:
15142 case MachinePlaysBlack:
15143 case BeginningOfGame:
15144 SendToProgram("force\n", &first);
15145 if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15146 if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15148 abortEngineThink = TRUE;
15149 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15150 SendToProgram(buf, &first);
15151 DisplayMessage("Aborting engine think", "");
15155 SetUserThinkingEnables();
15157 case PlayFromGameFile:
15158 (void) StopLoadGameTimer();
15159 if (gameFileFP != NULL) {
15164 EditPositionDone(TRUE);
15169 SendToProgram("force\n", &first);
15171 case TwoMachinesPlay:
15172 GameEnds(EndOfFile, NULL, GE_PLAYER);
15173 ResurrectChessProgram();
15174 SetUserThinkingEnables();
15177 ResurrectChessProgram();
15179 case IcsPlayingBlack:
15180 case IcsPlayingWhite:
15181 DisplayError(_("Warning: You are still playing a game"), 0);
15184 DisplayError(_("Warning: You are still observing a game"), 0);
15187 DisplayError(_("Warning: You are still examining a game"), 0);
15198 first.offeredDraw = second.offeredDraw = 0;
15200 if (gameMode == PlayFromGameFile) {
15201 whiteTimeRemaining = timeRemaining[0][currentMove];
15202 blackTimeRemaining = timeRemaining[1][currentMove];
15206 if (gameMode == MachinePlaysWhite ||
15207 gameMode == MachinePlaysBlack ||
15208 gameMode == TwoMachinesPlay ||
15209 gameMode == EndOfGame) {
15210 i = forwardMostMove;
15211 while (i > currentMove) {
15212 SendToProgram("undo\n", &first);
15215 if(!adjustedClock) {
15216 whiteTimeRemaining = timeRemaining[0][currentMove];
15217 blackTimeRemaining = timeRemaining[1][currentMove];
15218 DisplayBothClocks();
15220 if (whiteFlag || blackFlag) {
15221 whiteFlag = blackFlag = 0;
15226 gameMode = EditGame;
15233 EditPositionEvent ()
15235 if (gameMode == EditPosition) {
15241 if (gameMode != EditGame) return;
15243 gameMode = EditPosition;
15246 if (currentMove > 0)
15247 CopyBoard(boards[0], boards[currentMove]);
15249 blackPlaysFirst = !WhiteOnMove(currentMove);
15251 currentMove = forwardMostMove = backwardMostMove = 0;
15252 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15254 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15260 /* [DM] icsEngineAnalyze - possible call from other functions */
15261 if (appData.icsEngineAnalyze) {
15262 appData.icsEngineAnalyze = FALSE;
15264 DisplayMessage("",_("Close ICS engine analyze..."));
15266 if (first.analysisSupport && first.analyzing) {
15267 SendToBoth("exit\n");
15268 first.analyzing = second.analyzing = FALSE;
15270 thinkOutput[0] = NULLCHAR;
15274 EditPositionDone (Boolean fakeRights)
15276 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15278 startedFromSetupPosition = TRUE;
15279 InitChessProgram(&first, FALSE);
15280 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15281 boards[0][EP_STATUS] = EP_NONE;
15282 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15283 if(boards[0][0][BOARD_WIDTH>>1] == king) {
15284 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15285 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15286 } else boards[0][CASTLING][2] = NoRights;
15287 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15288 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15289 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15290 } else boards[0][CASTLING][5] = NoRights;
15291 if(gameInfo.variant == VariantSChess) {
15293 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15294 boards[0][VIRGIN][i] = 0;
15295 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15296 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15300 SendToProgram("force\n", &first);
15301 if (blackPlaysFirst) {
15302 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15303 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15304 currentMove = forwardMostMove = backwardMostMove = 1;
15305 CopyBoard(boards[1], boards[0]);
15307 currentMove = forwardMostMove = backwardMostMove = 0;
15309 SendBoard(&first, forwardMostMove);
15310 if (appData.debugMode) {
15311 fprintf(debugFP, "EditPosDone\n");
15314 DisplayMessage("", "");
15315 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15316 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15317 gameMode = EditGame;
15319 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15320 ClearHighlights(); /* [AS] */
15323 /* Pause for `ms' milliseconds */
15324 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15326 TimeDelay (long ms)
15333 } while (SubtractTimeMarks(&m2, &m1) < ms);
15336 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15338 SendMultiLineToICS (char *buf)
15340 char temp[MSG_SIZ+1], *p;
15347 strncpy(temp, buf, len);
15352 if (*p == '\n' || *p == '\r')
15357 strcat(temp, "\n");
15359 SendToPlayer(temp, strlen(temp));
15363 SetWhiteToPlayEvent ()
15365 if (gameMode == EditPosition) {
15366 blackPlaysFirst = FALSE;
15367 DisplayBothClocks(); /* works because currentMove is 0 */
15368 } else if (gameMode == IcsExamining) {
15369 SendToICS(ics_prefix);
15370 SendToICS("tomove white\n");
15375 SetBlackToPlayEvent ()
15377 if (gameMode == EditPosition) {
15378 blackPlaysFirst = TRUE;
15379 currentMove = 1; /* kludge */
15380 DisplayBothClocks();
15382 } else if (gameMode == IcsExamining) {
15383 SendToICS(ics_prefix);
15384 SendToICS("tomove black\n");
15389 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15392 ChessSquare piece = boards[0][y][x];
15393 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15394 static int lastVariant;
15396 if (gameMode != EditPosition && gameMode != IcsExamining) return;
15398 switch (selection) {
15400 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15401 MarkTargetSquares(1);
15402 CopyBoard(currentBoard, boards[0]);
15403 CopyBoard(menuBoard, initialPosition);
15404 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15405 SendToICS(ics_prefix);
15406 SendToICS("bsetup clear\n");
15407 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15408 SendToICS(ics_prefix);
15409 SendToICS("clearboard\n");
15412 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15413 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15414 for (y = 0; y < BOARD_HEIGHT; y++) {
15415 if (gameMode == IcsExamining) {
15416 if (boards[currentMove][y][x] != EmptySquare) {
15417 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15421 } else if(boards[0][y][x] != DarkSquare) {
15422 if(boards[0][y][x] != p) nonEmpty++;
15423 boards[0][y][x] = p;
15427 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15429 for(r = 0; r < BOARD_HEIGHT; r++) {
15430 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
15431 ChessSquare p = menuBoard[r][x];
15432 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15435 DisplayMessage("Clicking clock again restores position", "");
15436 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15437 if(!nonEmpty) { // asked to clear an empty board
15438 CopyBoard(boards[0], menuBoard);
15440 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15441 CopyBoard(boards[0], initialPosition);
15443 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15444 && !CompareBoards(nullBoard, erasedBoard)) {
15445 CopyBoard(boards[0], erasedBoard);
15447 CopyBoard(erasedBoard, currentBoard);
15451 if (gameMode == EditPosition) {
15452 DrawPosition(FALSE, boards[0]);
15457 SetWhiteToPlayEvent();
15461 SetBlackToPlayEvent();
15465 if (gameMode == IcsExamining) {
15466 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15467 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15470 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15471 if(x == BOARD_LEFT-2) {
15472 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15473 boards[0][y][1] = 0;
15475 if(x == BOARD_RGHT+1) {
15476 if(y >= gameInfo.holdingsSize) break;
15477 boards[0][y][BOARD_WIDTH-2] = 0;
15480 boards[0][y][x] = EmptySquare;
15481 DrawPosition(FALSE, boards[0]);
15486 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15487 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
15488 selection = (ChessSquare) (PROMOTED(piece));
15489 } else if(piece == EmptySquare) selection = WhiteSilver;
15490 else selection = (ChessSquare)((int)piece - 1);
15494 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15495 piece > (int)BlackMan && piece <= (int)BlackKing ) {
15496 selection = (ChessSquare) (DEMOTED(piece));
15497 } else if(piece == EmptySquare) selection = BlackSilver;
15498 else selection = (ChessSquare)((int)piece + 1);
15503 if(gameInfo.variant == VariantShatranj ||
15504 gameInfo.variant == VariantXiangqi ||
15505 gameInfo.variant == VariantCourier ||
15506 gameInfo.variant == VariantASEAN ||
15507 gameInfo.variant == VariantMakruk )
15508 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15513 if(gameInfo.variant == VariantXiangqi)
15514 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15515 if(gameInfo.variant == VariantKnightmate)
15516 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15519 if (gameMode == IcsExamining) {
15520 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15521 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15522 PieceToChar(selection), AAA + x, ONE + y);
15525 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15527 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15528 n = PieceToNumber(selection - BlackPawn);
15529 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15530 boards[0][BOARD_HEIGHT-1-n][0] = selection;
15531 boards[0][BOARD_HEIGHT-1-n][1]++;
15533 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15534 n = PieceToNumber(selection);
15535 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15536 boards[0][n][BOARD_WIDTH-1] = selection;
15537 boards[0][n][BOARD_WIDTH-2]++;
15540 boards[0][y][x] = selection;
15541 DrawPosition(TRUE, boards[0]);
15543 fromX = fromY = -1;
15551 DropMenuEvent (ChessSquare selection, int x, int y)
15553 ChessMove moveType;
15555 switch (gameMode) {
15556 case IcsPlayingWhite:
15557 case MachinePlaysBlack:
15558 if (!WhiteOnMove(currentMove)) {
15559 DisplayMoveError(_("It is Black's turn"));
15562 moveType = WhiteDrop;
15564 case IcsPlayingBlack:
15565 case MachinePlaysWhite:
15566 if (WhiteOnMove(currentMove)) {
15567 DisplayMoveError(_("It is White's turn"));
15570 moveType = BlackDrop;
15573 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15579 if (moveType == BlackDrop && selection < BlackPawn) {
15580 selection = (ChessSquare) ((int) selection
15581 + (int) BlackPawn - (int) WhitePawn);
15583 if (boards[currentMove][y][x] != EmptySquare) {
15584 DisplayMoveError(_("That square is occupied"));
15588 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15594 /* Accept a pending offer of any kind from opponent */
15596 if (appData.icsActive) {
15597 SendToICS(ics_prefix);
15598 SendToICS("accept\n");
15599 } else if (cmailMsgLoaded) {
15600 if (currentMove == cmailOldMove &&
15601 commentList[cmailOldMove] != NULL &&
15602 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15603 "Black offers a draw" : "White offers a draw")) {
15605 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15606 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15608 DisplayError(_("There is no pending offer on this move"), 0);
15609 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15612 /* Not used for offers from chess program */
15619 /* Decline a pending offer of any kind from opponent */
15621 if (appData.icsActive) {
15622 SendToICS(ics_prefix);
15623 SendToICS("decline\n");
15624 } else if (cmailMsgLoaded) {
15625 if (currentMove == cmailOldMove &&
15626 commentList[cmailOldMove] != NULL &&
15627 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15628 "Black offers a draw" : "White offers a draw")) {
15630 AppendComment(cmailOldMove, "Draw declined", TRUE);
15631 DisplayComment(cmailOldMove - 1, "Draw declined");
15634 DisplayError(_("There is no pending offer on this move"), 0);
15637 /* Not used for offers from chess program */
15644 /* Issue ICS rematch command */
15645 if (appData.icsActive) {
15646 SendToICS(ics_prefix);
15647 SendToICS("rematch\n");
15654 /* Call your opponent's flag (claim a win on time) */
15655 if (appData.icsActive) {
15656 SendToICS(ics_prefix);
15657 SendToICS("flag\n");
15659 switch (gameMode) {
15662 case MachinePlaysWhite:
15665 GameEnds(GameIsDrawn, "Both players ran out of time",
15668 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15670 DisplayError(_("Your opponent is not out of time"), 0);
15673 case MachinePlaysBlack:
15676 GameEnds(GameIsDrawn, "Both players ran out of time",
15679 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15681 DisplayError(_("Your opponent is not out of time"), 0);
15689 ClockClick (int which)
15690 { // [HGM] code moved to back-end from winboard.c
15691 if(which) { // black clock
15692 if (gameMode == EditPosition || gameMode == IcsExamining) {
15693 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15694 SetBlackToPlayEvent();
15695 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15696 gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15697 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15698 } else if (shiftKey) {
15699 AdjustClock(which, -1);
15700 } else if (gameMode == IcsPlayingWhite ||
15701 gameMode == MachinePlaysBlack) {
15704 } else { // white clock
15705 if (gameMode == EditPosition || gameMode == IcsExamining) {
15706 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15707 SetWhiteToPlayEvent();
15708 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15709 gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15710 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15711 } else if (shiftKey) {
15712 AdjustClock(which, -1);
15713 } else if (gameMode == IcsPlayingBlack ||
15714 gameMode == MachinePlaysWhite) {
15723 /* Offer draw or accept pending draw offer from opponent */
15725 if (appData.icsActive) {
15726 /* Note: tournament rules require draw offers to be
15727 made after you make your move but before you punch
15728 your clock. Currently ICS doesn't let you do that;
15729 instead, you immediately punch your clock after making
15730 a move, but you can offer a draw at any time. */
15732 SendToICS(ics_prefix);
15733 SendToICS("draw\n");
15734 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15735 } else if (cmailMsgLoaded) {
15736 if (currentMove == cmailOldMove &&
15737 commentList[cmailOldMove] != NULL &&
15738 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15739 "Black offers a draw" : "White offers a draw")) {
15740 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15741 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15742 } else if (currentMove == cmailOldMove + 1) {
15743 char *offer = WhiteOnMove(cmailOldMove) ?
15744 "White offers a draw" : "Black offers a draw";
15745 AppendComment(currentMove, offer, TRUE);
15746 DisplayComment(currentMove - 1, offer);
15747 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15749 DisplayError(_("You must make your move before offering a draw"), 0);
15750 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15752 } else if (first.offeredDraw) {
15753 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15755 if (first.sendDrawOffers) {
15756 SendToProgram("draw\n", &first);
15757 userOfferedDraw = TRUE;
15765 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15767 if (appData.icsActive) {
15768 SendToICS(ics_prefix);
15769 SendToICS("adjourn\n");
15771 /* Currently GNU Chess doesn't offer or accept Adjourns */
15779 /* Offer Abort or accept pending Abort offer from opponent */
15781 if (appData.icsActive) {
15782 SendToICS(ics_prefix);
15783 SendToICS("abort\n");
15785 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15792 /* Resign. You can do this even if it's not your turn. */
15794 if (appData.icsActive) {
15795 SendToICS(ics_prefix);
15796 SendToICS("resign\n");
15798 switch (gameMode) {
15799 case MachinePlaysWhite:
15800 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15802 case MachinePlaysBlack:
15803 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15806 if (cmailMsgLoaded) {
15808 if (WhiteOnMove(cmailOldMove)) {
15809 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15811 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15813 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15824 StopObservingEvent ()
15826 /* Stop observing current games */
15827 SendToICS(ics_prefix);
15828 SendToICS("unobserve\n");
15832 StopExaminingEvent ()
15834 /* Stop observing current game */
15835 SendToICS(ics_prefix);
15836 SendToICS("unexamine\n");
15840 ForwardInner (int target)
15842 int limit; int oldSeekGraphUp = seekGraphUp;
15844 if (appData.debugMode)
15845 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15846 target, currentMove, forwardMostMove);
15848 if (gameMode == EditPosition)
15851 seekGraphUp = FALSE;
15852 MarkTargetSquares(1);
15853 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15855 if (gameMode == PlayFromGameFile && !pausing)
15858 if (gameMode == IcsExamining && pausing)
15859 limit = pauseExamForwardMostMove;
15861 limit = forwardMostMove;
15863 if (target > limit) target = limit;
15865 if (target > 0 && moveList[target - 1][0]) {
15866 int fromX, fromY, toX, toY;
15867 toX = moveList[target - 1][2] - AAA;
15868 toY = moveList[target - 1][3] - ONE;
15869 if (moveList[target - 1][1] == '@') {
15870 if (appData.highlightLastMove) {
15871 SetHighlights(-1, -1, toX, toY);
15874 fromX = moveList[target - 1][0] - AAA;
15875 fromY = moveList[target - 1][1] - ONE;
15876 if (target == currentMove + 1) {
15877 if(moveList[target - 1][4] == ';') { // multi-leg
15878 killX = moveList[target - 1][5] - AAA;
15879 killY = moveList[target - 1][6] - ONE;
15881 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15882 killX = killY = -1;
15884 if (appData.highlightLastMove) {
15885 SetHighlights(fromX, fromY, toX, toY);
15889 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15890 gameMode == Training || gameMode == PlayFromGameFile ||
15891 gameMode == AnalyzeFile) {
15892 while (currentMove < target) {
15893 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15894 SendMoveToProgram(currentMove++, &first);
15897 currentMove = target;
15900 if (gameMode == EditGame || gameMode == EndOfGame) {
15901 whiteTimeRemaining = timeRemaining[0][currentMove];
15902 blackTimeRemaining = timeRemaining[1][currentMove];
15904 DisplayBothClocks();
15905 DisplayMove(currentMove - 1);
15906 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15907 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15908 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15909 DisplayComment(currentMove - 1, commentList[currentMove]);
15911 ClearMap(); // [HGM] exclude: invalidate map
15918 if (gameMode == IcsExamining && !pausing) {
15919 SendToICS(ics_prefix);
15920 SendToICS("forward\n");
15922 ForwardInner(currentMove + 1);
15929 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15930 /* to optimze, we temporarily turn off analysis mode while we feed
15931 * the remaining moves to the engine. Otherwise we get analysis output
15934 if (first.analysisSupport) {
15935 SendToProgram("exit\nforce\n", &first);
15936 first.analyzing = FALSE;
15940 if (gameMode == IcsExamining && !pausing) {
15941 SendToICS(ics_prefix);
15942 SendToICS("forward 999999\n");
15944 ForwardInner(forwardMostMove);
15947 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15948 /* we have fed all the moves, so reactivate analysis mode */
15949 SendToProgram("analyze\n", &first);
15950 first.analyzing = TRUE;
15951 /*first.maybeThinking = TRUE;*/
15952 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15957 BackwardInner (int target)
15959 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15961 if (appData.debugMode)
15962 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15963 target, currentMove, forwardMostMove);
15965 if (gameMode == EditPosition) return;
15966 seekGraphUp = FALSE;
15967 MarkTargetSquares(1);
15968 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15969 if (currentMove <= backwardMostMove) {
15971 DrawPosition(full_redraw, boards[currentMove]);
15974 if (gameMode == PlayFromGameFile && !pausing)
15977 if (moveList[target][0]) {
15978 int fromX, fromY, toX, toY;
15979 toX = moveList[target][2] - AAA;
15980 toY = moveList[target][3] - ONE;
15981 if (moveList[target][1] == '@') {
15982 if (appData.highlightLastMove) {
15983 SetHighlights(-1, -1, toX, toY);
15986 fromX = moveList[target][0] - AAA;
15987 fromY = moveList[target][1] - ONE;
15988 if (target == currentMove - 1) {
15989 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15991 if (appData.highlightLastMove) {
15992 SetHighlights(fromX, fromY, toX, toY);
15996 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15997 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15998 while (currentMove > target) {
15999 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16000 // null move cannot be undone. Reload program with move history before it.
16002 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16003 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16005 SendBoard(&first, i);
16006 if(second.analyzing) SendBoard(&second, i);
16007 for(currentMove=i; currentMove<target; currentMove++) {
16008 SendMoveToProgram(currentMove, &first);
16009 if(second.analyzing) SendMoveToProgram(currentMove, &second);
16013 SendToBoth("undo\n");
16017 currentMove = target;
16020 if (gameMode == EditGame || gameMode == EndOfGame) {
16021 whiteTimeRemaining = timeRemaining[0][currentMove];
16022 blackTimeRemaining = timeRemaining[1][currentMove];
16024 DisplayBothClocks();
16025 DisplayMove(currentMove - 1);
16026 DrawPosition(full_redraw, boards[currentMove]);
16027 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16028 // [HGM] PV info: routine tests if comment empty
16029 DisplayComment(currentMove - 1, commentList[currentMove]);
16030 ClearMap(); // [HGM] exclude: invalidate map
16036 if (gameMode == IcsExamining && !pausing) {
16037 SendToICS(ics_prefix);
16038 SendToICS("backward\n");
16040 BackwardInner(currentMove - 1);
16047 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16048 /* to optimize, we temporarily turn off analysis mode while we undo
16049 * all the moves. Otherwise we get analysis output after each undo.
16051 if (first.analysisSupport) {
16052 SendToProgram("exit\nforce\n", &first);
16053 first.analyzing = FALSE;
16057 if (gameMode == IcsExamining && !pausing) {
16058 SendToICS(ics_prefix);
16059 SendToICS("backward 999999\n");
16061 BackwardInner(backwardMostMove);
16064 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16065 /* we have fed all the moves, so reactivate analysis mode */
16066 SendToProgram("analyze\n", &first);
16067 first.analyzing = TRUE;
16068 /*first.maybeThinking = TRUE;*/
16069 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16076 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16077 if (to >= forwardMostMove) to = forwardMostMove;
16078 if (to <= backwardMostMove) to = backwardMostMove;
16079 if (to < currentMove) {
16087 RevertEvent (Boolean annotate)
16089 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16092 if (gameMode != IcsExamining) {
16093 DisplayError(_("You are not examining a game"), 0);
16097 DisplayError(_("You can't revert while pausing"), 0);
16100 SendToICS(ics_prefix);
16101 SendToICS("revert\n");
16105 RetractMoveEvent ()
16107 switch (gameMode) {
16108 case MachinePlaysWhite:
16109 case MachinePlaysBlack:
16110 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16111 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16114 if (forwardMostMove < 2) return;
16115 currentMove = forwardMostMove = forwardMostMove - 2;
16116 whiteTimeRemaining = timeRemaining[0][currentMove];
16117 blackTimeRemaining = timeRemaining[1][currentMove];
16118 DisplayBothClocks();
16119 DisplayMove(currentMove - 1);
16120 ClearHighlights();/*!! could figure this out*/
16121 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16122 SendToProgram("remove\n", &first);
16123 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16126 case BeginningOfGame:
16130 case IcsPlayingWhite:
16131 case IcsPlayingBlack:
16132 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16133 SendToICS(ics_prefix);
16134 SendToICS("takeback 2\n");
16136 SendToICS(ics_prefix);
16137 SendToICS("takeback 1\n");
16146 ChessProgramState *cps;
16148 switch (gameMode) {
16149 case MachinePlaysWhite:
16150 if (!WhiteOnMove(forwardMostMove)) {
16151 DisplayError(_("It is your turn"), 0);
16156 case MachinePlaysBlack:
16157 if (WhiteOnMove(forwardMostMove)) {
16158 DisplayError(_("It is your turn"), 0);
16163 case TwoMachinesPlay:
16164 if (WhiteOnMove(forwardMostMove) ==
16165 (first.twoMachinesColor[0] == 'w')) {
16171 case BeginningOfGame:
16175 SendToProgram("?\n", cps);
16179 TruncateGameEvent ()
16182 if (gameMode != EditGame) return;
16189 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16190 if (forwardMostMove > currentMove) {
16191 if (gameInfo.resultDetails != NULL) {
16192 free(gameInfo.resultDetails);
16193 gameInfo.resultDetails = NULL;
16194 gameInfo.result = GameUnfinished;
16196 forwardMostMove = currentMove;
16197 HistorySet(parseList, backwardMostMove, forwardMostMove,
16205 if (appData.noChessProgram) return;
16206 switch (gameMode) {
16207 case MachinePlaysWhite:
16208 if (WhiteOnMove(forwardMostMove)) {
16209 DisplayError(_("Wait until your turn."), 0);
16213 case BeginningOfGame:
16214 case MachinePlaysBlack:
16215 if (!WhiteOnMove(forwardMostMove)) {
16216 DisplayError(_("Wait until your turn."), 0);
16221 DisplayError(_("No hint available"), 0);
16224 SendToProgram("hint\n", &first);
16225 hintRequested = TRUE;
16229 SaveSelected (FILE *g, int dummy, char *dummy2)
16231 ListGame * lg = (ListGame *) gameList.head;
16235 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16236 DisplayError(_("Game list not loaded or empty"), 0);
16240 creatingBook = TRUE; // suppresses stuff during load game
16242 /* Get list size */
16243 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16244 if(lg->position >= 0) { // selected?
16245 LoadGame(f, nItem, "", TRUE);
16246 SaveGamePGN2(g); // leaves g open
16249 lg = (ListGame *) lg->node.succ;
16253 creatingBook = FALSE;
16261 ListGame * lg = (ListGame *) gameList.head;
16264 static int secondTime = FALSE;
16266 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16267 DisplayError(_("Game list not loaded or empty"), 0);
16271 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16274 DisplayNote(_("Book file exists! Try again for overwrite."));
16278 creatingBook = TRUE;
16279 secondTime = FALSE;
16281 /* Get list size */
16282 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16283 if(lg->position >= 0) {
16284 LoadGame(f, nItem, "", TRUE);
16285 AddGameToBook(TRUE);
16288 lg = (ListGame *) lg->node.succ;
16291 creatingBook = FALSE;
16298 if (appData.noChessProgram) return;
16299 switch (gameMode) {
16300 case MachinePlaysWhite:
16301 if (WhiteOnMove(forwardMostMove)) {
16302 DisplayError(_("Wait until your turn."), 0);
16306 case BeginningOfGame:
16307 case MachinePlaysBlack:
16308 if (!WhiteOnMove(forwardMostMove)) {
16309 DisplayError(_("Wait until your turn."), 0);
16314 EditPositionDone(TRUE);
16316 case TwoMachinesPlay:
16321 SendToProgram("bk\n", &first);
16322 bookOutput[0] = NULLCHAR;
16323 bookRequested = TRUE;
16329 char *tags = PGNTags(&gameInfo);
16330 TagsPopUp(tags, CmailMsg());
16334 /* end button procedures */
16337 PrintPosition (FILE *fp, int move)
16341 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16342 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16343 char c = PieceToChar(boards[move][i][j]);
16344 fputc(c == '?' ? '.' : c, fp);
16345 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16348 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16349 fprintf(fp, "white to play\n");
16351 fprintf(fp, "black to play\n");
16355 PrintOpponents (FILE *fp)
16357 if (gameInfo.white != NULL) {
16358 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16364 /* Find last component of program's own name, using some heuristics */
16366 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16369 int local = (strcmp(host, "localhost") == 0);
16370 while (!local && (p = strchr(prog, ';')) != NULL) {
16372 while (*p == ' ') p++;
16375 if (*prog == '"' || *prog == '\'') {
16376 q = strchr(prog + 1, *prog);
16378 q = strchr(prog, ' ');
16380 if (q == NULL) q = prog + strlen(prog);
16382 while (p >= prog && *p != '/' && *p != '\\') p--;
16384 if(p == prog && *p == '"') p++;
16386 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16387 memcpy(buf, p, q - p);
16388 buf[q - p] = NULLCHAR;
16396 TimeControlTagValue ()
16399 if (!appData.clockMode) {
16400 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16401 } else if (movesPerSession > 0) {
16402 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16403 } else if (timeIncrement == 0) {
16404 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16406 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16408 return StrSave(buf);
16414 /* This routine is used only for certain modes */
16415 VariantClass v = gameInfo.variant;
16416 ChessMove r = GameUnfinished;
16419 if(keepInfo) return;
16421 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16422 r = gameInfo.result;
16423 p = gameInfo.resultDetails;
16424 gameInfo.resultDetails = NULL;
16426 ClearGameInfo(&gameInfo);
16427 gameInfo.variant = v;
16429 switch (gameMode) {
16430 case MachinePlaysWhite:
16431 gameInfo.event = StrSave( appData.pgnEventHeader );
16432 gameInfo.site = StrSave(HostName());
16433 gameInfo.date = PGNDate();
16434 gameInfo.round = StrSave("-");
16435 gameInfo.white = StrSave(first.tidy);
16436 gameInfo.black = StrSave(UserName());
16437 gameInfo.timeControl = TimeControlTagValue();
16440 case MachinePlaysBlack:
16441 gameInfo.event = StrSave( appData.pgnEventHeader );
16442 gameInfo.site = StrSave(HostName());
16443 gameInfo.date = PGNDate();
16444 gameInfo.round = StrSave("-");
16445 gameInfo.white = StrSave(UserName());
16446 gameInfo.black = StrSave(first.tidy);
16447 gameInfo.timeControl = TimeControlTagValue();
16450 case TwoMachinesPlay:
16451 gameInfo.event = StrSave( appData.pgnEventHeader );
16452 gameInfo.site = StrSave(HostName());
16453 gameInfo.date = PGNDate();
16456 snprintf(buf, MSG_SIZ, "%d", roundNr);
16457 gameInfo.round = StrSave(buf);
16459 gameInfo.round = StrSave("-");
16461 if (first.twoMachinesColor[0] == 'w') {
16462 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16463 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16465 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16466 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16468 gameInfo.timeControl = TimeControlTagValue();
16472 gameInfo.event = StrSave("Edited game");
16473 gameInfo.site = StrSave(HostName());
16474 gameInfo.date = PGNDate();
16475 gameInfo.round = StrSave("-");
16476 gameInfo.white = StrSave("-");
16477 gameInfo.black = StrSave("-");
16478 gameInfo.result = r;
16479 gameInfo.resultDetails = p;
16483 gameInfo.event = StrSave("Edited position");
16484 gameInfo.site = StrSave(HostName());
16485 gameInfo.date = PGNDate();
16486 gameInfo.round = StrSave("-");
16487 gameInfo.white = StrSave("-");
16488 gameInfo.black = StrSave("-");
16491 case IcsPlayingWhite:
16492 case IcsPlayingBlack:
16497 case PlayFromGameFile:
16498 gameInfo.event = StrSave("Game from non-PGN file");
16499 gameInfo.site = StrSave(HostName());
16500 gameInfo.date = PGNDate();
16501 gameInfo.round = StrSave("-");
16502 gameInfo.white = StrSave("?");
16503 gameInfo.black = StrSave("?");
16512 ReplaceComment (int index, char *text)
16518 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16519 pvInfoList[index-1].depth == len &&
16520 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16521 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16522 while (*text == '\n') text++;
16523 len = strlen(text);
16524 while (len > 0 && text[len - 1] == '\n') len--;
16526 if (commentList[index] != NULL)
16527 free(commentList[index]);
16530 commentList[index] = NULL;
16533 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16534 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16535 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16536 commentList[index] = (char *) malloc(len + 2);
16537 strncpy(commentList[index], text, len);
16538 commentList[index][len] = '\n';
16539 commentList[index][len + 1] = NULLCHAR;
16541 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16543 commentList[index] = (char *) malloc(len + 7);
16544 safeStrCpy(commentList[index], "{\n", 3);
16545 safeStrCpy(commentList[index]+2, text, len+1);
16546 commentList[index][len+2] = NULLCHAR;
16547 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16548 strcat(commentList[index], "\n}\n");
16553 CrushCRs (char *text)
16561 if (ch == '\r') continue;
16563 } while (ch != '\0');
16567 AppendComment (int index, char *text, Boolean addBraces)
16568 /* addBraces tells if we should add {} */
16573 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16574 if(addBraces == 3) addBraces = 0; else // force appending literally
16575 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16578 while (*text == '\n') text++;
16579 len = strlen(text);
16580 while (len > 0 && text[len - 1] == '\n') len--;
16581 text[len] = NULLCHAR;
16583 if (len == 0) return;
16585 if (commentList[index] != NULL) {
16586 Boolean addClosingBrace = addBraces;
16587 old = commentList[index];
16588 oldlen = strlen(old);
16589 while(commentList[index][oldlen-1] == '\n')
16590 commentList[index][--oldlen] = NULLCHAR;
16591 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16592 safeStrCpy(commentList[index], old, oldlen + len + 6);
16594 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16595 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16596 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16597 while (*text == '\n') { text++; len--; }
16598 commentList[index][--oldlen] = NULLCHAR;
16600 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16601 else strcat(commentList[index], "\n");
16602 strcat(commentList[index], text);
16603 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16604 else strcat(commentList[index], "\n");
16606 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16608 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16609 else commentList[index][0] = NULLCHAR;
16610 strcat(commentList[index], text);
16611 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16612 if(addBraces == TRUE) strcat(commentList[index], "}\n");
16617 FindStr (char * text, char * sub_text)
16619 char * result = strstr( text, sub_text );
16621 if( result != NULL ) {
16622 result += strlen( sub_text );
16628 /* [AS] Try to extract PV info from PGN comment */
16629 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16631 GetInfoFromComment (int index, char * text)
16633 char * sep = text, *p;
16635 if( text != NULL && index > 0 ) {
16638 int time = -1, sec = 0, deci;
16639 char * s_eval = FindStr( text, "[%eval " );
16640 char * s_emt = FindStr( text, "[%emt " );
16642 if( s_eval != NULL || s_emt != NULL ) {
16644 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16649 if( s_eval != NULL ) {
16650 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16654 if( delim != ']' ) {
16659 if( s_emt != NULL ) {
16664 /* We expect something like: [+|-]nnn.nn/dd */
16667 if(*text != '{') return text; // [HGM] braces: must be normal comment
16669 sep = strchr( text, '/' );
16670 if( sep == NULL || sep < (text+4) ) {
16675 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16676 if(p[1] == '(') { // comment starts with PV
16677 p = strchr(p, ')'); // locate end of PV
16678 if(p == NULL || sep < p+5) return text;
16679 // at this point we have something like "{(.*) +0.23/6 ..."
16680 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16681 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16682 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16684 time = -1; sec = -1; deci = -1;
16685 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16686 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16687 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16688 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16692 if( score_lo < 0 || score_lo >= 100 ) {
16696 if(sec >= 0) time = 600*time + 10*sec; else
16697 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16699 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16701 /* [HGM] PV time: now locate end of PV info */
16702 while( *++sep >= '0' && *sep <= '9'); // strip depth
16704 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16706 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16708 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16709 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16720 pvInfoList[index-1].depth = depth;
16721 pvInfoList[index-1].score = score;
16722 pvInfoList[index-1].time = 10*time; // centi-sec
16723 if(*sep == '}') *sep = 0; else *--sep = '{';
16724 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16730 SendToProgram (char *message, ChessProgramState *cps)
16732 int count, outCount, error;
16735 if (cps->pr == NoProc) return;
16738 if (appData.debugMode) {
16741 fprintf(debugFP, "%ld >%-6s: %s",
16742 SubtractTimeMarks(&now, &programStartTime),
16743 cps->which, message);
16745 fprintf(serverFP, "%ld >%-6s: %s",
16746 SubtractTimeMarks(&now, &programStartTime),
16747 cps->which, message), fflush(serverFP);
16750 count = strlen(message);
16751 outCount = OutputToProcess(cps->pr, message, count, &error);
16752 if (outCount < count && !exiting
16753 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16754 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16755 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16756 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16757 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16758 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16759 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16760 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16762 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16763 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16764 gameInfo.result = res;
16766 gameInfo.resultDetails = StrSave(buf);
16768 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16769 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16774 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16778 ChessProgramState *cps = (ChessProgramState *)closure;
16780 if (isr != cps->isr) return; /* Killed intentionally */
16783 RemoveInputSource(cps->isr);
16784 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16785 _(cps->which), cps->program);
16786 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16787 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16788 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16789 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16790 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16791 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16793 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16794 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16795 gameInfo.result = res;
16797 gameInfo.resultDetails = StrSave(buf);
16799 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16800 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16802 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16803 _(cps->which), cps->program);
16804 RemoveInputSource(cps->isr);
16806 /* [AS] Program is misbehaving badly... kill it */
16807 if( count == -2 ) {
16808 DestroyChildProcess( cps->pr, 9 );
16812 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16817 if ((end_str = strchr(message, '\r')) != NULL)
16818 *end_str = NULLCHAR;
16819 if ((end_str = strchr(message, '\n')) != NULL)
16820 *end_str = NULLCHAR;
16822 if (appData.debugMode) {
16823 TimeMark now; int print = 1;
16824 char *quote = ""; char c; int i;
16826 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16827 char start = message[0];
16828 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16829 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16830 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16831 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16832 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16833 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16834 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16835 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16836 sscanf(message, "hint: %c", &c)!=1 &&
16837 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16838 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16839 print = (appData.engineComments >= 2);
16841 message[0] = start; // restore original message
16845 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16846 SubtractTimeMarks(&now, &programStartTime), cps->which,
16850 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16851 SubtractTimeMarks(&now, &programStartTime), cps->which,
16853 message), fflush(serverFP);
16857 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16858 if (appData.icsEngineAnalyze) {
16859 if (strstr(message, "whisper") != NULL ||
16860 strstr(message, "kibitz") != NULL ||
16861 strstr(message, "tellics") != NULL) return;
16864 HandleMachineMove(message, cps);
16869 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16874 if( timeControl_2 > 0 ) {
16875 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16876 tc = timeControl_2;
16879 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16880 inc /= cps->timeOdds;
16881 st /= cps->timeOdds;
16883 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16886 /* Set exact time per move, normally using st command */
16887 if (cps->stKludge) {
16888 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16890 if (seconds == 0) {
16891 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16893 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16896 snprintf(buf, MSG_SIZ, "st %d\n", st);
16899 /* Set conventional or incremental time control, using level command */
16900 if (seconds == 0) {
16901 /* Note old gnuchess bug -- minutes:seconds used to not work.
16902 Fixed in later versions, but still avoid :seconds
16903 when seconds is 0. */
16904 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16906 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16907 seconds, inc/1000.);
16910 SendToProgram(buf, cps);
16912 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16913 /* Orthogonally, limit search to given depth */
16915 if (cps->sdKludge) {
16916 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16918 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16920 SendToProgram(buf, cps);
16923 if(cps->nps >= 0) { /* [HGM] nps */
16924 if(cps->supportsNPS == FALSE)
16925 cps->nps = -1; // don't use if engine explicitly says not supported!
16927 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16928 SendToProgram(buf, cps);
16933 ChessProgramState *
16935 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16937 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16938 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16944 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16946 char message[MSG_SIZ];
16949 /* Note: this routine must be called when the clocks are stopped
16950 or when they have *just* been set or switched; otherwise
16951 it will be off by the time since the current tick started.
16953 if (machineWhite) {
16954 time = whiteTimeRemaining / 10;
16955 otime = blackTimeRemaining / 10;
16957 time = blackTimeRemaining / 10;
16958 otime = whiteTimeRemaining / 10;
16960 /* [HGM] translate opponent's time by time-odds factor */
16961 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16963 if (time <= 0) time = 1;
16964 if (otime <= 0) otime = 1;
16966 snprintf(message, MSG_SIZ, "time %ld\n", time);
16967 SendToProgram(message, cps);
16969 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16970 SendToProgram(message, cps);
16974 EngineDefinedVariant (ChessProgramState *cps, int n)
16975 { // return name of n-th unknown variant that engine supports
16976 static char buf[MSG_SIZ];
16977 char *p, *s = cps->variants;
16978 if(!s) return NULL;
16979 do { // parse string from variants feature
16981 p = strchr(s, ',');
16982 if(p) *p = NULLCHAR;
16983 v = StringToVariant(s);
16984 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16985 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16986 if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16987 !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16988 !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16989 !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16990 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16993 if(n < 0) return buf;
16999 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17002 int len = strlen(name);
17005 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17007 sscanf(*p, "%d", &val);
17009 while (**p && **p != ' ')
17011 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17012 SendToProgram(buf, cps);
17019 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17022 int len = strlen(name);
17023 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17025 sscanf(*p, "%d", loc);
17026 while (**p && **p != ' ') (*p)++;
17027 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17028 SendToProgram(buf, cps);
17035 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17038 int len = strlen(name);
17039 if (strncmp((*p), name, len) == 0
17040 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17042 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17043 sscanf(*p, "%[^\"]", *loc);
17044 while (**p && **p != '\"') (*p)++;
17045 if (**p == '\"') (*p)++;
17046 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17047 SendToProgram(buf, cps);
17054 ParseOption (Option *opt, ChessProgramState *cps)
17055 // [HGM] options: process the string that defines an engine option, and determine
17056 // name, type, default value, and allowed value range
17058 char *p, *q, buf[MSG_SIZ];
17059 int n, min = (-1)<<31, max = 1<<31, def;
17061 opt->target = &opt->value; // OK for spin/slider and checkbox
17062 if(p = strstr(opt->name, " -spin ")) {
17063 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17064 if(max < min) max = min; // enforce consistency
17065 if(def < min) def = min;
17066 if(def > max) def = max;
17071 } else if((p = strstr(opt->name, " -slider "))) {
17072 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17073 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17074 if(max < min) max = min; // enforce consistency
17075 if(def < min) def = min;
17076 if(def > max) def = max;
17080 opt->type = Spin; // Slider;
17081 } else if((p = strstr(opt->name, " -string "))) {
17082 opt->textValue = p+9;
17083 opt->type = TextBox;
17084 opt->target = &opt->textValue;
17085 } else if((p = strstr(opt->name, " -file "))) {
17086 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17087 opt->target = opt->textValue = p+7;
17088 opt->type = FileName; // FileName;
17089 opt->target = &opt->textValue;
17090 } else if((p = strstr(opt->name, " -path "))) {
17091 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17092 opt->target = opt->textValue = p+7;
17093 opt->type = PathName; // PathName;
17094 opt->target = &opt->textValue;
17095 } else if(p = strstr(opt->name, " -check ")) {
17096 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17097 opt->value = (def != 0);
17098 opt->type = CheckBox;
17099 } else if(p = strstr(opt->name, " -combo ")) {
17100 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17101 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17102 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17103 opt->value = n = 0;
17104 while(q = StrStr(q, " /// ")) {
17105 n++; *q = 0; // count choices, and null-terminate each of them
17107 if(*q == '*') { // remember default, which is marked with * prefix
17111 cps->comboList[cps->comboCnt++] = q;
17113 cps->comboList[cps->comboCnt++] = NULL;
17115 opt->type = ComboBox;
17116 } else if(p = strstr(opt->name, " -button")) {
17117 opt->type = Button;
17118 } else if(p = strstr(opt->name, " -save")) {
17119 opt->type = SaveButton;
17120 } else return FALSE;
17121 *p = 0; // terminate option name
17122 // now look if the command-line options define a setting for this engine option.
17123 if(cps->optionSettings && cps->optionSettings[0])
17124 p = strstr(cps->optionSettings, opt->name); else p = NULL;
17125 if(p && (p == cps->optionSettings || p[-1] == ',')) {
17126 snprintf(buf, MSG_SIZ, "option %s", p);
17127 if(p = strstr(buf, ",")) *p = 0;
17128 if(q = strchr(buf, '=')) switch(opt->type) {
17130 for(n=0; n<opt->max; n++)
17131 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17134 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17138 opt->value = atoi(q+1);
17143 SendToProgram(buf, cps);
17149 FeatureDone (ChessProgramState *cps, int val)
17151 DelayedEventCallback cb = GetDelayedEvent();
17152 if ((cb == InitBackEnd3 && cps == &first) ||
17153 (cb == SettingsMenuIfReady && cps == &second) ||
17154 (cb == LoadEngine) ||
17155 (cb == TwoMachinesEventIfReady)) {
17156 CancelDelayedEvent();
17157 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17158 } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17159 cps->initDone = val;
17160 if(val) cps->reload = FALSE, RefreshSettingsDialog(cps, val);
17163 /* Parse feature command from engine */
17165 ParseFeatures (char *args, ChessProgramState *cps)
17173 while (*p == ' ') p++;
17174 if (*p == NULLCHAR) return;
17176 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17177 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17178 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17179 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17180 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17181 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17182 if (BoolFeature(&p, "reuse", &val, cps)) {
17183 /* Engine can disable reuse, but can't enable it if user said no */
17184 if (!val) cps->reuse = FALSE;
17187 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17188 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17189 if (gameMode == TwoMachinesPlay) {
17190 DisplayTwoMachinesTitle();
17196 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17197 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17198 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17199 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17200 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17201 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17202 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17203 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17204 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17205 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17206 if (IntFeature(&p, "done", &val, cps)) {
17207 FeatureDone(cps, val);
17210 /* Added by Tord: */
17211 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17212 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17213 /* End of additions by Tord */
17215 /* [HGM] added features: */
17216 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17217 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17218 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17219 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17220 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17221 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17222 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17223 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17224 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17225 FREE(cps->option[cps->nrOptions].name);
17226 cps->option[cps->nrOptions].name = q; q = NULL;
17227 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17228 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17229 SendToProgram(buf, cps);
17232 if(cps->nrOptions >= MAX_OPTIONS) {
17234 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17235 DisplayError(buf, 0);
17239 /* End of additions by HGM */
17241 /* unknown feature: complain and skip */
17243 while (*q && *q != '=') q++;
17244 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17245 SendToProgram(buf, cps);
17251 while (*p && *p != '\"') p++;
17252 if (*p == '\"') p++;
17254 while (*p && *p != ' ') p++;
17262 PeriodicUpdatesEvent (int newState)
17264 if (newState == appData.periodicUpdates)
17267 appData.periodicUpdates=newState;
17269 /* Display type changes, so update it now */
17270 // DisplayAnalysis();
17272 /* Get the ball rolling again... */
17274 AnalysisPeriodicEvent(1);
17275 StartAnalysisClock();
17280 PonderNextMoveEvent (int newState)
17282 if (newState == appData.ponderNextMove) return;
17283 if (gameMode == EditPosition) EditPositionDone(TRUE);
17285 SendToProgram("hard\n", &first);
17286 if (gameMode == TwoMachinesPlay) {
17287 SendToProgram("hard\n", &second);
17290 SendToProgram("easy\n", &first);
17291 thinkOutput[0] = NULLCHAR;
17292 if (gameMode == TwoMachinesPlay) {
17293 SendToProgram("easy\n", &second);
17296 appData.ponderNextMove = newState;
17300 NewSettingEvent (int option, int *feature, char *command, int value)
17304 if (gameMode == EditPosition) EditPositionDone(TRUE);
17305 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17306 if(feature == NULL || *feature) SendToProgram(buf, &first);
17307 if (gameMode == TwoMachinesPlay) {
17308 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17313 ShowThinkingEvent ()
17314 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17316 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17317 int newState = appData.showThinking
17318 // [HGM] thinking: other features now need thinking output as well
17319 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17321 if (oldState == newState) return;
17322 oldState = newState;
17323 if (gameMode == EditPosition) EditPositionDone(TRUE);
17325 SendToProgram("post\n", &first);
17326 if (gameMode == TwoMachinesPlay) {
17327 SendToProgram("post\n", &second);
17330 SendToProgram("nopost\n", &first);
17331 thinkOutput[0] = NULLCHAR;
17332 if (gameMode == TwoMachinesPlay) {
17333 SendToProgram("nopost\n", &second);
17336 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17340 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17342 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17343 if (pr == NoProc) return;
17344 AskQuestion(title, question, replyPrefix, pr);
17348 TypeInEvent (char firstChar)
17350 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17351 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17352 gameMode == AnalyzeMode || gameMode == EditGame ||
17353 gameMode == EditPosition || gameMode == IcsExamining ||
17354 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17355 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17356 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17357 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
17358 gameMode == Training) PopUpMoveDialog(firstChar);
17362 TypeInDoneEvent (char *move)
17365 int n, fromX, fromY, toX, toY;
17367 ChessMove moveType;
17370 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17371 EditPositionPasteFEN(move);
17374 // [HGM] movenum: allow move number to be typed in any mode
17375 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17379 // undocumented kludge: allow command-line option to be typed in!
17380 // (potentially fatal, and does not implement the effect of the option.)
17381 // should only be used for options that are values on which future decisions will be made,
17382 // and definitely not on options that would be used during initialization.
17383 if(strstr(move, "!!! -") == move) {
17384 ParseArgsFromString(move+4);
17388 if (gameMode != EditGame && currentMove != forwardMostMove &&
17389 gameMode != Training) {
17390 DisplayMoveError(_("Displayed move is not current"));
17392 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17393 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17394 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17395 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17396 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17397 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17399 DisplayMoveError(_("Could not parse move"));
17405 DisplayMove (int moveNumber)
17407 char message[MSG_SIZ];
17409 char cpThinkOutput[MSG_SIZ];
17411 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17413 if (moveNumber == forwardMostMove - 1 ||
17414 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17416 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17418 if (strchr(cpThinkOutput, '\n')) {
17419 *strchr(cpThinkOutput, '\n') = NULLCHAR;
17422 *cpThinkOutput = NULLCHAR;
17425 /* [AS] Hide thinking from human user */
17426 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17427 *cpThinkOutput = NULLCHAR;
17428 if( thinkOutput[0] != NULLCHAR ) {
17431 for( i=0; i<=hiddenThinkOutputState; i++ ) {
17432 cpThinkOutput[i] = '.';
17434 cpThinkOutput[i] = NULLCHAR;
17435 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17439 if (moveNumber == forwardMostMove - 1 &&
17440 gameInfo.resultDetails != NULL) {
17441 if (gameInfo.resultDetails[0] == NULLCHAR) {
17442 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17444 snprintf(res, MSG_SIZ, " {%s} %s",
17445 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17451 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17452 DisplayMessage(res, cpThinkOutput);
17454 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17455 WhiteOnMove(moveNumber) ? " " : ".. ",
17456 parseList[moveNumber], res);
17457 DisplayMessage(message, cpThinkOutput);
17462 DisplayComment (int moveNumber, char *text)
17464 char title[MSG_SIZ];
17466 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17467 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17469 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17470 WhiteOnMove(moveNumber) ? " " : ".. ",
17471 parseList[moveNumber]);
17473 if (text != NULL && (appData.autoDisplayComment || commentUp))
17474 CommentPopUp(title, text);
17477 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17478 * might be busy thinking or pondering. It can be omitted if your
17479 * gnuchess is configured to stop thinking immediately on any user
17480 * input. However, that gnuchess feature depends on the FIONREAD
17481 * ioctl, which does not work properly on some flavors of Unix.
17484 Attention (ChessProgramState *cps)
17487 if (!cps->useSigint) return;
17488 if (appData.noChessProgram || (cps->pr == NoProc)) return;
17489 switch (gameMode) {
17490 case MachinePlaysWhite:
17491 case MachinePlaysBlack:
17492 case TwoMachinesPlay:
17493 case IcsPlayingWhite:
17494 case IcsPlayingBlack:
17497 /* Skip if we know it isn't thinking */
17498 if (!cps->maybeThinking) return;
17499 if (appData.debugMode)
17500 fprintf(debugFP, "Interrupting %s\n", cps->which);
17501 InterruptChildProcess(cps->pr);
17502 cps->maybeThinking = FALSE;
17507 #endif /*ATTENTION*/
17513 if (whiteTimeRemaining <= 0) {
17516 if (appData.icsActive) {
17517 if (appData.autoCallFlag &&
17518 gameMode == IcsPlayingBlack && !blackFlag) {
17519 SendToICS(ics_prefix);
17520 SendToICS("flag\n");
17524 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17526 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17527 if (appData.autoCallFlag) {
17528 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17535 if (blackTimeRemaining <= 0) {
17538 if (appData.icsActive) {
17539 if (appData.autoCallFlag &&
17540 gameMode == IcsPlayingWhite && !whiteFlag) {
17541 SendToICS(ics_prefix);
17542 SendToICS("flag\n");
17546 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17548 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17549 if (appData.autoCallFlag) {
17550 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17561 CheckTimeControl ()
17563 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17564 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17567 * add time to clocks when time control is achieved ([HGM] now also used for increment)
17569 if ( !WhiteOnMove(forwardMostMove) ) {
17570 /* White made time control */
17571 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17572 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17573 /* [HGM] time odds: correct new time quota for time odds! */
17574 / WhitePlayer()->timeOdds;
17575 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17577 lastBlack -= blackTimeRemaining;
17578 /* Black made time control */
17579 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17580 / WhitePlayer()->other->timeOdds;
17581 lastWhite = whiteTimeRemaining;
17586 DisplayBothClocks ()
17588 int wom = gameMode == EditPosition ?
17589 !blackPlaysFirst : WhiteOnMove(currentMove);
17590 DisplayWhiteClock(whiteTimeRemaining, wom);
17591 DisplayBlackClock(blackTimeRemaining, !wom);
17595 /* Timekeeping seems to be a portability nightmare. I think everyone
17596 has ftime(), but I'm really not sure, so I'm including some ifdefs
17597 to use other calls if you don't. Clocks will be less accurate if
17598 you have neither ftime nor gettimeofday.
17601 /* VS 2008 requires the #include outside of the function */
17602 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17603 #include <sys/timeb.h>
17606 /* Get the current time as a TimeMark */
17608 GetTimeMark (TimeMark *tm)
17610 #if HAVE_GETTIMEOFDAY
17612 struct timeval timeVal;
17613 struct timezone timeZone;
17615 gettimeofday(&timeVal, &timeZone);
17616 tm->sec = (long) timeVal.tv_sec;
17617 tm->ms = (int) (timeVal.tv_usec / 1000L);
17619 #else /*!HAVE_GETTIMEOFDAY*/
17622 // include <sys/timeb.h> / moved to just above start of function
17623 struct timeb timeB;
17626 tm->sec = (long) timeB.time;
17627 tm->ms = (int) timeB.millitm;
17629 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17630 tm->sec = (long) time(NULL);
17636 /* Return the difference in milliseconds between two
17637 time marks. We assume the difference will fit in a long!
17640 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17642 return 1000L*(tm2->sec - tm1->sec) +
17643 (long) (tm2->ms - tm1->ms);
17648 * Code to manage the game clocks.
17650 * In tournament play, black starts the clock and then white makes a move.
17651 * We give the human user a slight advantage if he is playing white---the
17652 * clocks don't run until he makes his first move, so it takes zero time.
17653 * Also, we don't account for network lag, so we could get out of sync
17654 * with GNU Chess's clock -- but then, referees are always right.
17657 static TimeMark tickStartTM;
17658 static long intendedTickLength;
17661 NextTickLength (long timeRemaining)
17663 long nominalTickLength, nextTickLength;
17665 if (timeRemaining > 0L && timeRemaining <= 10000L)
17666 nominalTickLength = 100L;
17668 nominalTickLength = 1000L;
17669 nextTickLength = timeRemaining % nominalTickLength;
17670 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17672 return nextTickLength;
17675 /* Adjust clock one minute up or down */
17677 AdjustClock (Boolean which, int dir)
17679 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17680 if(which) blackTimeRemaining += 60000*dir;
17681 else whiteTimeRemaining += 60000*dir;
17682 DisplayBothClocks();
17683 adjustedClock = TRUE;
17686 /* Stop clocks and reset to a fresh time control */
17690 (void) StopClockTimer();
17691 if (appData.icsActive) {
17692 whiteTimeRemaining = blackTimeRemaining = 0;
17693 } else if (searchTime) {
17694 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17695 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17696 } else { /* [HGM] correct new time quote for time odds */
17697 whiteTC = blackTC = fullTimeControlString;
17698 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17699 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17701 if (whiteFlag || blackFlag) {
17703 whiteFlag = blackFlag = FALSE;
17705 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17706 DisplayBothClocks();
17707 adjustedClock = FALSE;
17710 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17712 /* Decrement running clock by amount of time that has passed */
17716 long timeRemaining;
17717 long lastTickLength, fudge;
17720 if (!appData.clockMode) return;
17721 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17725 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17727 /* Fudge if we woke up a little too soon */
17728 fudge = intendedTickLength - lastTickLength;
17729 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17731 if (WhiteOnMove(forwardMostMove)) {
17732 if(whiteNPS >= 0) lastTickLength = 0;
17733 timeRemaining = whiteTimeRemaining -= lastTickLength;
17734 if(timeRemaining < 0 && !appData.icsActive) {
17735 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17736 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17737 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17738 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17741 DisplayWhiteClock(whiteTimeRemaining - fudge,
17742 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17744 if(blackNPS >= 0) lastTickLength = 0;
17745 timeRemaining = blackTimeRemaining -= lastTickLength;
17746 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17747 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17749 blackStartMove = forwardMostMove;
17750 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17753 DisplayBlackClock(blackTimeRemaining - fudge,
17754 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17756 if (CheckFlags()) return;
17758 if(twoBoards) { // count down secondary board's clocks as well
17759 activePartnerTime -= lastTickLength;
17761 if(activePartner == 'W')
17762 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17764 DisplayBlackClock(activePartnerTime, TRUE);
17769 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17770 StartClockTimer(intendedTickLength);
17772 /* if the time remaining has fallen below the alarm threshold, sound the
17773 * alarm. if the alarm has sounded and (due to a takeback or time control
17774 * with increment) the time remaining has increased to a level above the
17775 * threshold, reset the alarm so it can sound again.
17778 if (appData.icsActive && appData.icsAlarm) {
17780 /* make sure we are dealing with the user's clock */
17781 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17782 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17785 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17786 alarmSounded = FALSE;
17787 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17789 alarmSounded = TRUE;
17795 /* A player has just moved, so stop the previously running
17796 clock and (if in clock mode) start the other one.
17797 We redisplay both clocks in case we're in ICS mode, because
17798 ICS gives us an update to both clocks after every move.
17799 Note that this routine is called *after* forwardMostMove
17800 is updated, so the last fractional tick must be subtracted
17801 from the color that is *not* on move now.
17804 SwitchClocks (int newMoveNr)
17806 long lastTickLength;
17808 int flagged = FALSE;
17812 if (StopClockTimer() && appData.clockMode) {
17813 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17814 if (!WhiteOnMove(forwardMostMove)) {
17815 if(blackNPS >= 0) lastTickLength = 0;
17816 blackTimeRemaining -= lastTickLength;
17817 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17818 // if(pvInfoList[forwardMostMove].time == -1)
17819 pvInfoList[forwardMostMove].time = // use GUI time
17820 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17822 if(whiteNPS >= 0) lastTickLength = 0;
17823 whiteTimeRemaining -= lastTickLength;
17824 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17825 // if(pvInfoList[forwardMostMove].time == -1)
17826 pvInfoList[forwardMostMove].time =
17827 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17829 flagged = CheckFlags();
17831 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17832 CheckTimeControl();
17834 if (flagged || !appData.clockMode) return;
17836 switch (gameMode) {
17837 case MachinePlaysBlack:
17838 case MachinePlaysWhite:
17839 case BeginningOfGame:
17840 if (pausing) return;
17844 case PlayFromGameFile:
17852 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17853 if(WhiteOnMove(forwardMostMove))
17854 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17855 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17859 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17860 whiteTimeRemaining : blackTimeRemaining);
17861 StartClockTimer(intendedTickLength);
17865 /* Stop both clocks */
17869 long lastTickLength;
17872 if (!StopClockTimer()) return;
17873 if (!appData.clockMode) return;
17877 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17878 if (WhiteOnMove(forwardMostMove)) {
17879 if(whiteNPS >= 0) lastTickLength = 0;
17880 whiteTimeRemaining -= lastTickLength;
17881 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17883 if(blackNPS >= 0) lastTickLength = 0;
17884 blackTimeRemaining -= lastTickLength;
17885 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17890 /* Start clock of player on move. Time may have been reset, so
17891 if clock is already running, stop and restart it. */
17895 (void) StopClockTimer(); /* in case it was running already */
17896 DisplayBothClocks();
17897 if (CheckFlags()) return;
17899 if (!appData.clockMode) return;
17900 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17902 GetTimeMark(&tickStartTM);
17903 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17904 whiteTimeRemaining : blackTimeRemaining);
17906 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17907 whiteNPS = blackNPS = -1;
17908 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17909 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17910 whiteNPS = first.nps;
17911 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17912 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17913 blackNPS = first.nps;
17914 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17915 whiteNPS = second.nps;
17916 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17917 blackNPS = second.nps;
17918 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17920 StartClockTimer(intendedTickLength);
17924 TimeString (long ms)
17926 long second, minute, hour, day;
17928 static char buf[32];
17930 if (ms > 0 && ms <= 9900) {
17931 /* convert milliseconds to tenths, rounding up */
17932 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17934 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17938 /* convert milliseconds to seconds, rounding up */
17939 /* use floating point to avoid strangeness of integer division
17940 with negative dividends on many machines */
17941 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17948 day = second / (60 * 60 * 24);
17949 second = second % (60 * 60 * 24);
17950 hour = second / (60 * 60);
17951 second = second % (60 * 60);
17952 minute = second / 60;
17953 second = second % 60;
17956 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17957 sign, day, hour, minute, second);
17959 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17961 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17968 * This is necessary because some C libraries aren't ANSI C compliant yet.
17971 StrStr (char *string, char *match)
17975 length = strlen(match);
17977 for (i = strlen(string) - length; i >= 0; i--, string++)
17978 if (!strncmp(match, string, length))
17985 StrCaseStr (char *string, char *match)
17989 length = strlen(match);
17991 for (i = strlen(string) - length; i >= 0; i--, string++) {
17992 for (j = 0; j < length; j++) {
17993 if (ToLower(match[j]) != ToLower(string[j]))
17996 if (j == length) return string;
18004 StrCaseCmp (char *s1, char *s2)
18009 c1 = ToLower(*s1++);
18010 c2 = ToLower(*s2++);
18011 if (c1 > c2) return 1;
18012 if (c1 < c2) return -1;
18013 if (c1 == NULLCHAR) return 0;
18021 return isupper(c) ? tolower(c) : c;
18028 return islower(c) ? toupper(c) : c;
18030 #endif /* !_amigados */
18037 if ((ret = (char *) malloc(strlen(s) + 1)))
18039 safeStrCpy(ret, s, strlen(s)+1);
18045 StrSavePtr (char *s, char **savePtr)
18050 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18051 safeStrCpy(*savePtr, s, strlen(s)+1);
18063 clock = time((time_t *)NULL);
18064 tm = localtime(&clock);
18065 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18066 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18067 return StrSave(buf);
18072 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18074 int i, j, fromX, fromY, toX, toY;
18075 int whiteToPlay, haveRights = nrCastlingRights;
18081 whiteToPlay = (gameMode == EditPosition) ?
18082 !blackPlaysFirst : (move % 2 == 0);
18085 /* Piece placement data */
18086 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18087 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18089 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18090 if (boards[move][i][j] == EmptySquare) {
18092 } else { ChessSquare piece = boards[move][i][j];
18093 if (emptycount > 0) {
18094 if(emptycount<10) /* [HGM] can be >= 10 */
18095 *p++ = '0' + emptycount;
18096 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18099 if(PieceToChar(piece) == '+') {
18100 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18102 piece = (ChessSquare)(CHUDEMOTED(piece));
18104 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18105 if(*p = PieceSuffix(piece)) p++;
18107 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18108 p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18113 if (emptycount > 0) {
18114 if(emptycount<10) /* [HGM] can be >= 10 */
18115 *p++ = '0' + emptycount;
18116 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18123 /* [HGM] print Crazyhouse or Shogi holdings */
18124 if( gameInfo.holdingsWidth ) {
18125 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18127 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18128 piece = boards[move][i][BOARD_WIDTH-1];
18129 if( piece != EmptySquare )
18130 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18131 *p++ = PieceToChar(piece);
18133 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18134 piece = boards[move][BOARD_HEIGHT-i-1][0];
18135 if( piece != EmptySquare )
18136 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18137 *p++ = PieceToChar(piece);
18140 if( q == p ) *p++ = '-';
18146 *p++ = whiteToPlay ? 'w' : 'b';
18149 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18150 haveRights = 0; q = p;
18151 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18152 piece = boards[move][0][i];
18153 if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18154 if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18157 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18158 piece = boards[move][BOARD_HEIGHT-1][i];
18159 if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18160 if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18163 if(p == q) *p++ = '-';
18167 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18168 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18171 int handW=0, handB=0;
18172 if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18173 for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18174 for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18177 if(appData.fischerCastling) {
18178 if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18179 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18180 if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18182 /* [HGM] write directly from rights */
18183 if(boards[move][CASTLING][2] != NoRights &&
18184 boards[move][CASTLING][0] != NoRights )
18185 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18186 if(boards[move][CASTLING][2] != NoRights &&
18187 boards[move][CASTLING][1] != NoRights )
18188 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18191 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18192 if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18194 if(boards[move][CASTLING][5] != NoRights &&
18195 boards[move][CASTLING][3] != NoRights )
18196 *p++ = boards[move][CASTLING][3] + AAA;
18197 if(boards[move][CASTLING][5] != NoRights &&
18198 boards[move][CASTLING][4] != NoRights )
18199 *p++ = boards[move][CASTLING][4] + AAA;
18203 /* [HGM] write true castling rights */
18204 if( nrCastlingRights == 6 ) {
18206 if(boards[move][CASTLING][0] != NoRights &&
18207 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
18208 q = (boards[move][CASTLING][1] != NoRights &&
18209 boards[move][CASTLING][2] != NoRights );
18210 if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18211 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18212 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18213 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18217 if(boards[move][CASTLING][3] != NoRights &&
18218 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
18219 q = (boards[move][CASTLING][4] != NoRights &&
18220 boards[move][CASTLING][5] != NoRights );
18222 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18223 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18224 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18229 if (q == p) *p++ = '-'; /* No castling rights */
18233 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18234 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18235 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18236 /* En passant target square */
18237 if (move > backwardMostMove) {
18238 fromX = moveList[move - 1][0] - AAA;
18239 fromY = moveList[move - 1][1] - ONE;
18240 toX = moveList[move - 1][2] - AAA;
18241 toY = moveList[move - 1][3] - ONE;
18242 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18243 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18244 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18246 /* 2-square pawn move just happened */
18248 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18252 } else if(move == backwardMostMove) {
18253 // [HGM] perhaps we should always do it like this, and forget the above?
18254 if((signed char)boards[move][EP_STATUS] >= 0) {
18255 *p++ = boards[move][EP_STATUS] + AAA;
18256 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18268 { int i = 0, j=move;
18270 /* [HGM] find reversible plies */
18271 if (appData.debugMode) { int k;
18272 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18273 for(k=backwardMostMove; k<=forwardMostMove; k++)
18274 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18278 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18279 if( j == backwardMostMove ) i += initialRulePlies;
18280 sprintf(p, "%d ", i);
18281 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18283 /* Fullmove number */
18284 sprintf(p, "%d", (move / 2) + 1);
18285 } else *--p = NULLCHAR;
18287 return StrSave(buf);
18291 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18293 int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18295 int emptycount, virgin[BOARD_FILES];
18296 ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18300 /* Piece placement data */
18301 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18304 if (*p == '/' || *p == ' ' || *p == '[' ) {
18306 emptycount = gameInfo.boardWidth - j;
18307 while (emptycount--)
18308 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18309 if (*p == '/') p++;
18310 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18311 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18312 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18314 appData.NrRanks = gameInfo.boardHeight - i; i=0;
18317 #if(BOARD_FILES >= 10)*0
18318 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18319 p++; emptycount=10;
18320 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18321 while (emptycount--)
18322 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18324 } else if (*p == '*') {
18325 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18326 } else if (isdigit(*p)) {
18327 emptycount = *p++ - '0';
18328 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18329 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18330 while (emptycount--)
18331 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18332 } else if (*p == '<') {
18333 if(i == BOARD_HEIGHT-1) shuffle = 1;
18334 else if (i != 0 || !shuffle) return FALSE;
18336 } else if (shuffle && *p == '>') {
18337 p++; // for now ignore closing shuffle range, and assume rank-end
18338 } else if (*p == '?') {
18339 if (j >= gameInfo.boardWidth) return FALSE;
18340 if (i != 0 && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18341 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18342 } else if (*p == '+' || isalpha(*p)) {
18343 char *q, *s = SUFFIXES;
18344 if (j >= gameInfo.boardWidth) return FALSE;
18347 if(q = strchr(s, p[1])) p++;
18348 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18349 if(piece == EmptySquare) return FALSE; /* unknown piece */
18350 piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18351 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18354 if(q = strchr(s, *p)) p++;
18355 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18358 if(piece==EmptySquare) return FALSE; /* unknown piece */
18359 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18360 piece = (ChessSquare) (PROMOTED(piece));
18361 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18364 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18365 if(piece == king) wKingRank = i;
18366 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18372 while (*p == '/' || *p == ' ') p++;
18374 if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18376 /* [HGM] by default clear Crazyhouse holdings, if present */
18377 if(gameInfo.holdingsWidth) {
18378 for(i=0; i<BOARD_HEIGHT; i++) {
18379 board[i][0] = EmptySquare; /* black holdings */
18380 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18381 board[i][1] = (ChessSquare) 0; /* black counts */
18382 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18386 /* [HGM] look for Crazyhouse holdings here */
18387 while(*p==' ') p++;
18388 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18389 int swap=0, wcnt=0, bcnt=0;
18391 if(*p == '<') swap++, p++;
18392 if(*p == '-' ) p++; /* empty holdings */ else {
18393 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18394 /* if we would allow FEN reading to set board size, we would */
18395 /* have to add holdings and shift the board read so far here */
18396 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18398 if((int) piece >= (int) BlackPawn ) {
18399 i = (int)piece - (int)BlackPawn;
18400 i = PieceToNumber((ChessSquare)i);
18401 if( i >= gameInfo.holdingsSize ) return FALSE;
18402 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18403 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
18406 i = (int)piece - (int)WhitePawn;
18407 i = PieceToNumber((ChessSquare)i);
18408 if( i >= gameInfo.holdingsSize ) return FALSE;
18409 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
18410 board[i][BOARD_WIDTH-2]++; /* black holdings */
18414 if(subst) { // substitute back-rank question marks by holdings pieces
18415 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18416 int k, m, n = bcnt + 1;
18417 if(board[0][j] == ClearBoard) {
18418 if(!wcnt) return FALSE;
18420 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18421 board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18422 if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18426 if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18427 if(!bcnt) return FALSE;
18428 if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18429 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18430 board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18431 if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18442 if(subst) return FALSE; // substitution requested, but no holdings
18444 while(*p == ' ') p++;
18448 if(appData.colorNickNames) {
18449 if( c == appData.colorNickNames[0] ) c = 'w'; else
18450 if( c == appData.colorNickNames[1] ) c = 'b';
18454 *blackPlaysFirst = FALSE;
18457 *blackPlaysFirst = TRUE;
18463 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18464 /* return the extra info in global variiables */
18466 while(*p==' ') p++;
18468 if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18469 if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18470 if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18473 /* set defaults in case FEN is incomplete */
18474 board[EP_STATUS] = EP_UNKNOWN;
18475 board[TOUCHED_W] = board[TOUCHED_B] = 0;
18476 for(i=0; i<nrCastlingRights; i++ ) {
18477 board[CASTLING][i] =
18478 appData.fischerCastling ? NoRights : initialRights[i];
18479 } /* assume possible unless obviously impossible */
18480 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18481 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18482 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18483 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18484 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18485 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18486 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18487 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18490 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18493 while(isalpha(*p)) {
18494 if(isupper(*p)) w |= 1 << (*p++ - 'A');
18495 if(islower(*p)) b |= 1 << (*p++ - 'a');
18499 board[TOUCHED_W] = ~w;
18500 board[TOUCHED_B] = ~b;
18501 while(*p == ' ') p++;
18505 if(nrCastlingRights) {
18507 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18508 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18509 /* castling indicator present, so default becomes no castlings */
18510 for(i=0; i<nrCastlingRights; i++ ) {
18511 board[CASTLING][i] = NoRights;
18514 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18515 (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18516 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18517 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
18518 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18520 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18521 if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18522 if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18524 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18525 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18526 if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18527 && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18528 if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18529 && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18532 for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18533 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18534 board[CASTLING][2] = whiteKingFile;
18535 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18536 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18537 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18540 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18541 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18542 board[CASTLING][2] = whiteKingFile;
18543 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18544 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18545 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18548 for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18549 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18550 board[CASTLING][5] = blackKingFile;
18551 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18552 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18553 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18556 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18557 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18558 board[CASTLING][5] = blackKingFile;
18559 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18560 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18561 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18564 default: /* FRC castlings */
18565 if(c >= 'a') { /* black rights */
18566 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18567 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18568 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18569 if(i == BOARD_RGHT) break;
18570 board[CASTLING][5] = i;
18572 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
18573 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
18575 board[CASTLING][3] = c;
18577 board[CASTLING][4] = c;
18578 } else { /* white rights */
18579 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18580 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18581 if(board[0][i] == WhiteKing) break;
18582 if(i == BOARD_RGHT) break;
18583 board[CASTLING][2] = i;
18584 c -= AAA - 'a' + 'A';
18585 if(board[0][c] >= WhiteKing) break;
18587 board[CASTLING][0] = c;
18589 board[CASTLING][1] = c;
18593 for(i=0; i<nrCastlingRights; i++)
18594 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18595 if(gameInfo.variant == VariantSChess)
18596 for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18597 if(fischer && shuffle) appData.fischerCastling = TRUE;
18598 if (appData.debugMode) {
18599 fprintf(debugFP, "FEN castling rights:");
18600 for(i=0; i<nrCastlingRights; i++)
18601 fprintf(debugFP, " %d", board[CASTLING][i]);
18602 fprintf(debugFP, "\n");
18605 while(*p==' ') p++;
18608 if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18610 /* read e.p. field in games that know e.p. capture */
18611 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18612 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18613 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18615 p++; board[EP_STATUS] = EP_NONE;
18617 char c = *p++ - AAA;
18619 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18620 if(*p >= '0' && *p <='9') p++;
18621 board[EP_STATUS] = c;
18626 if(sscanf(p, "%d", &i) == 1) {
18627 FENrulePlies = i; /* 50-move ply counter */
18628 /* (The move number is still ignored) */
18635 EditPositionPasteFEN (char *fen)
18638 Board initial_position;
18640 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18641 DisplayError(_("Bad FEN position in clipboard"), 0);
18644 int savedBlackPlaysFirst = blackPlaysFirst;
18645 EditPositionEvent();
18646 blackPlaysFirst = savedBlackPlaysFirst;
18647 CopyBoard(boards[0], initial_position);
18648 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18649 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18650 DisplayBothClocks();
18651 DrawPosition(FALSE, boards[currentMove]);
18656 static char cseq[12] = "\\ ";
18659 set_cont_sequence (char *new_seq)
18664 // handle bad attempts to set the sequence
18666 return 0; // acceptable error - no debug
18668 len = strlen(new_seq);
18669 ret = (len > 0) && (len < sizeof(cseq));
18671 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18672 else if (appData.debugMode)
18673 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18678 reformat a source message so words don't cross the width boundary. internal
18679 newlines are not removed. returns the wrapped size (no null character unless
18680 included in source message). If dest is NULL, only calculate the size required
18681 for the dest buffer. lp argument indicats line position upon entry, and it's
18682 passed back upon exit.
18685 wrap (char *dest, char *src, int count, int width, int *lp)
18687 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18689 cseq_len = strlen(cseq);
18690 old_line = line = *lp;
18691 ansi = len = clen = 0;
18693 for (i=0; i < count; i++)
18695 if (src[i] == '\033')
18698 // if we hit the width, back up
18699 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18701 // store i & len in case the word is too long
18702 old_i = i, old_len = len;
18704 // find the end of the last word
18705 while (i && src[i] != ' ' && src[i] != '\n')
18711 // word too long? restore i & len before splitting it
18712 if ((old_i-i+clen) >= width)
18719 if (i && src[i-1] == ' ')
18722 if (src[i] != ' ' && src[i] != '\n')
18729 // now append the newline and continuation sequence
18734 strncpy(dest+len, cseq, cseq_len);
18742 dest[len] = src[i];
18746 if (src[i] == '\n')
18751 if (dest && appData.debugMode)
18753 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18754 count, width, line, len, *lp);
18755 show_bytes(debugFP, src, count);
18756 fprintf(debugFP, "\ndest: ");
18757 show_bytes(debugFP, dest, len);
18758 fprintf(debugFP, "\n");
18760 *lp = dest ? line : old_line;
18765 // [HGM] vari: routines for shelving variations
18766 Boolean modeRestore = FALSE;
18769 PushInner (int firstMove, int lastMove)
18771 int i, j, nrMoves = lastMove - firstMove;
18773 // push current tail of game on stack
18774 savedResult[storedGames] = gameInfo.result;
18775 savedDetails[storedGames] = gameInfo.resultDetails;
18776 gameInfo.resultDetails = NULL;
18777 savedFirst[storedGames] = firstMove;
18778 savedLast [storedGames] = lastMove;
18779 savedFramePtr[storedGames] = framePtr;
18780 framePtr -= nrMoves; // reserve space for the boards
18781 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18782 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18783 for(j=0; j<MOVE_LEN; j++)
18784 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18785 for(j=0; j<2*MOVE_LEN; j++)
18786 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18787 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18788 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18789 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18790 pvInfoList[firstMove+i-1].depth = 0;
18791 commentList[framePtr+i] = commentList[firstMove+i];
18792 commentList[firstMove+i] = NULL;
18796 forwardMostMove = firstMove; // truncate game so we can start variation
18800 PushTail (int firstMove, int lastMove)
18802 if(appData.icsActive) { // only in local mode
18803 forwardMostMove = currentMove; // mimic old ICS behavior
18806 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18808 PushInner(firstMove, lastMove);
18809 if(storedGames == 1) GreyRevert(FALSE);
18810 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18814 PopInner (Boolean annotate)
18817 char buf[8000], moveBuf[20];
18819 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18820 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18821 nrMoves = savedLast[storedGames] - currentMove;
18824 if(!WhiteOnMove(currentMove))
18825 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18826 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18827 for(i=currentMove; i<forwardMostMove; i++) {
18829 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18830 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18831 strcat(buf, moveBuf);
18832 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18833 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18837 for(i=1; i<=nrMoves; i++) { // copy last variation back
18838 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18839 for(j=0; j<MOVE_LEN; j++)
18840 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18841 for(j=0; j<2*MOVE_LEN; j++)
18842 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18843 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18844 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18845 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18846 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18847 commentList[currentMove+i] = commentList[framePtr+i];
18848 commentList[framePtr+i] = NULL;
18850 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18851 framePtr = savedFramePtr[storedGames];
18852 gameInfo.result = savedResult[storedGames];
18853 if(gameInfo.resultDetails != NULL) {
18854 free(gameInfo.resultDetails);
18856 gameInfo.resultDetails = savedDetails[storedGames];
18857 forwardMostMove = currentMove + nrMoves;
18861 PopTail (Boolean annotate)
18863 if(appData.icsActive) return FALSE; // only in local mode
18864 if(!storedGames) return FALSE; // sanity
18865 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18867 PopInner(annotate);
18868 if(currentMove < forwardMostMove) ForwardEvent(); else
18869 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18871 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18877 { // remove all shelved variations
18879 for(i=0; i<storedGames; i++) {
18880 if(savedDetails[i])
18881 free(savedDetails[i]);
18882 savedDetails[i] = NULL;
18884 for(i=framePtr; i<MAX_MOVES; i++) {
18885 if(commentList[i]) free(commentList[i]);
18886 commentList[i] = NULL;
18888 framePtr = MAX_MOVES-1;
18893 LoadVariation (int index, char *text)
18894 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18895 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18896 int level = 0, move;
18898 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18899 // first find outermost bracketing variation
18900 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18901 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18902 if(*p == '{') wait = '}'; else
18903 if(*p == '[') wait = ']'; else
18904 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18905 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18907 if(*p == wait) wait = NULLCHAR; // closing ]} found
18910 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18911 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18912 end[1] = NULLCHAR; // clip off comment beyond variation
18913 ToNrEvent(currentMove-1);
18914 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18915 // kludge: use ParsePV() to append variation to game
18916 move = currentMove;
18917 ParsePV(start, TRUE, TRUE);
18918 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18919 ClearPremoveHighlights();
18921 ToNrEvent(currentMove+1);
18927 char *p, *q, buf[MSG_SIZ];
18928 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18929 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18930 ParseArgsFromString(buf);
18931 ActivateTheme(TRUE); // also redo colors
18935 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18938 q = appData.themeNames;
18939 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18940 if(appData.useBitmaps) {
18941 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18942 appData.liteBackTextureFile, appData.darkBackTextureFile,
18943 appData.liteBackTextureMode,
18944 appData.darkBackTextureMode );
18946 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18947 Col2Text(2), // lightSquareColor
18948 Col2Text(3) ); // darkSquareColor
18950 if(appData.useBorder) {
18951 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18954 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18956 if(appData.useFont) {
18957 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18958 appData.renderPiecesWithFont,
18959 appData.fontToPieceTable,
18960 Col2Text(9), // appData.fontBackColorWhite
18961 Col2Text(10) ); // appData.fontForeColorBlack
18963 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18964 appData.pieceDirectory);
18965 if(!appData.pieceDirectory[0])
18966 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18967 Col2Text(0), // whitePieceColor
18968 Col2Text(1) ); // blackPieceColor
18970 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18971 Col2Text(4), // highlightSquareColor
18972 Col2Text(5) ); // premoveHighlightColor
18973 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18974 if(insert != q) insert[-1] = NULLCHAR;
18975 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18978 ActivateTheme(FALSE);