2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 int flock(int f, int code);
75 #include <sys/types.h>
84 #else /* not STDC_HEADERS */
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
105 # include <sys/time.h>
111 #if defined(_amigados) && !defined(__GNUC__)
116 extern int gettimeofday(struct timeval *, struct timezone *);
124 #include "frontend.h"
131 #include "backendz.h"
132 #include "evalgraph.h"
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153 char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180 char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182 int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
231 extern void ConsoleCreate();
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
254 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
278 static int initPing = -1;
280 /* States for ics_getting_history */
282 #define H_REQUESTED 1
283 #define H_GOT_REQ_HEADER 2
284 #define H_GOT_UNREQ_HEADER 3
285 #define H_GETTING_MOVES 4
286 #define H_GOT_UNWANTED_HEADER 5
288 /* whosays values for GameEnds */
297 /* Maximum number of games in a cmail message */
298 #define CMAIL_MAX_GAMES 20
300 /* Different types of move when calling RegisterMove */
302 #define CMAIL_RESIGN 1
304 #define CMAIL_ACCEPT 3
306 /* Different types of result to remember for each game */
307 #define CMAIL_NOT_RESULT 0
308 #define CMAIL_OLD_RESULT 1
309 #define CMAIL_NEW_RESULT 2
311 /* Telnet protocol constants */
322 safeStrCpy (char *dst, const char *src, size_t count)
325 assert( dst != NULL );
326 assert( src != NULL );
329 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
330 if( i == count && dst[count-1] != NULLCHAR)
332 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
333 if(appData.debugMode)
334 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
340 /* Some compiler can't cast u64 to double
341 * This function do the job for us:
343 * We use the highest bit for cast, this only
344 * works if the highest bit is not
345 * in use (This should not happen)
347 * We used this for all compiler
350 u64ToDouble (u64 value)
353 u64 tmp = value & u64Const(0x7fffffffffffffff);
354 r = (double)(s64)tmp;
355 if (value & u64Const(0x8000000000000000))
356 r += 9.2233720368547758080e18; /* 2^63 */
360 /* Fake up flags for now, as we aren't keeping track of castling
361 availability yet. [HGM] Change of logic: the flag now only
362 indicates the type of castlings allowed by the rule of the game.
363 The actual rights themselves are maintained in the array
364 castlingRights, as part of the game history, and are not probed
370 int flags = F_ALL_CASTLE_OK;
371 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
372 switch (gameInfo.variant) {
374 flags &= ~F_ALL_CASTLE_OK;
375 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
376 flags |= F_IGNORE_CHECK;
378 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
381 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
383 case VariantKriegspiel:
384 flags |= F_KRIEGSPIEL_CAPTURE;
386 case VariantCapaRandom:
387 case VariantFischeRandom:
388 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
389 case VariantNoCastle:
390 case VariantShatranj:
395 flags &= ~F_ALL_CASTLE_OK;
403 FILE *gameFileFP, *debugFP, *serverFP;
404 char *currentDebugFile; // [HGM] debug split: to remember name
407 [AS] Note: sometimes, the sscanf() function is used to parse the input
408 into a fixed-size buffer. Because of this, we must be prepared to
409 receive strings as long as the size of the input buffer, which is currently
410 set to 4K for Windows and 8K for the rest.
411 So, we must either allocate sufficiently large buffers here, or
412 reduce the size of the input buffer in the input reading part.
415 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
416 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
417 char thinkOutput1[MSG_SIZ*10];
419 ChessProgramState first, second, pairing;
421 /* premove variables */
424 int premoveFromX = 0;
425 int premoveFromY = 0;
426 int premovePromoChar = 0;
428 Boolean alarmSounded;
429 /* end premove variables */
431 char *ics_prefix = "$";
432 enum ICS_TYPE ics_type = ICS_GENERIC;
434 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
435 int pauseExamForwardMostMove = 0;
436 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
437 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
438 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
439 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
440 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
441 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
442 int whiteFlag = FALSE, blackFlag = FALSE;
443 int userOfferedDraw = FALSE;
444 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
445 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
446 int cmailMoveType[CMAIL_MAX_GAMES];
447 long ics_clock_paused = 0;
448 ProcRef icsPR = NoProc, cmailPR = NoProc;
449 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
450 GameMode gameMode = BeginningOfGame;
451 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
452 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
453 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
454 int hiddenThinkOutputState = 0; /* [AS] */
455 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
456 int adjudicateLossPlies = 6;
457 char white_holding[64], black_holding[64];
458 TimeMark lastNodeCountTime;
459 long lastNodeCount=0;
460 int shiftKey, controlKey; // [HGM] set by mouse handler
462 int have_sent_ICS_logon = 0;
464 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
465 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
466 Boolean adjustedClock;
467 long timeControl_2; /* [AS] Allow separate time controls */
468 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
469 long timeRemaining[2][MAX_MOVES];
470 int matchGame = 0, nextGame = 0, roundNr = 0;
471 Boolean waitingForGame = FALSE, startingEngine = FALSE;
472 TimeMark programStartTime, pauseStart;
473 char ics_handle[MSG_SIZ];
474 int have_set_title = 0;
476 /* animateTraining preserves the state of appData.animate
477 * when Training mode is activated. This allows the
478 * response to be animated when appData.animate == TRUE and
479 * appData.animateDragging == TRUE.
481 Boolean animateTraining;
487 Board boards[MAX_MOVES];
488 /* [HGM] Following 7 needed for accurate legality tests: */
489 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
490 signed char initialRights[BOARD_FILES];
491 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
492 int initialRulePlies, FENrulePlies;
493 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
495 Boolean shuffleOpenings;
496 int mute; // mute all sounds
498 // [HGM] vari: next 12 to save and restore variations
499 #define MAX_VARIATIONS 10
500 int framePtr = MAX_MOVES-1; // points to free stack entry
502 int savedFirst[MAX_VARIATIONS];
503 int savedLast[MAX_VARIATIONS];
504 int savedFramePtr[MAX_VARIATIONS];
505 char *savedDetails[MAX_VARIATIONS];
506 ChessMove savedResult[MAX_VARIATIONS];
508 void PushTail P((int firstMove, int lastMove));
509 Boolean PopTail P((Boolean annotate));
510 void PushInner P((int firstMove, int lastMove));
511 void PopInner P((Boolean annotate));
512 void CleanupTail P((void));
514 ChessSquare FIDEArray[2][BOARD_FILES] = {
515 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518 BlackKing, BlackBishop, BlackKnight, BlackRook }
521 ChessSquare twoKingsArray[2][BOARD_FILES] = {
522 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
524 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
525 BlackKing, BlackKing, BlackKnight, BlackRook }
528 ChessSquare KnightmateArray[2][BOARD_FILES] = {
529 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
530 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
531 { BlackRook, BlackMan, BlackBishop, BlackQueen,
532 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
535 ChessSquare SpartanArray[2][BOARD_FILES] = {
536 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
539 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
542 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
543 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
546 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
549 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
551 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
553 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
556 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
557 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
558 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
559 { BlackRook, BlackKnight, BlackMan, BlackFerz,
560 BlackKing, BlackMan, BlackKnight, BlackRook }
563 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
564 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
565 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
566 { BlackRook, BlackKnight, BlackMan, BlackFerz,
567 BlackKing, BlackMan, BlackKnight, BlackRook }
570 ChessSquare lionArray[2][BOARD_FILES] = {
571 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
572 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
573 { BlackRook, BlackLion, BlackBishop, BlackQueen,
574 BlackKing, BlackBishop, BlackKnight, BlackRook }
578 #if (BOARD_FILES>=10)
579 ChessSquare ShogiArray[2][BOARD_FILES] = {
580 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
581 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
582 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
583 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
586 ChessSquare XiangqiArray[2][BOARD_FILES] = {
587 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
588 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
589 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
590 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
593 ChessSquare CapablancaArray[2][BOARD_FILES] = {
594 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
595 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
596 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
597 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
600 ChessSquare GreatArray[2][BOARD_FILES] = {
601 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
602 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
603 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
604 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
607 ChessSquare JanusArray[2][BOARD_FILES] = {
608 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
609 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
610 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
611 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
614 ChessSquare GrandArray[2][BOARD_FILES] = {
615 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
616 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
617 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
618 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
622 ChessSquare GothicArray[2][BOARD_FILES] = {
623 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
624 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
625 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
626 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
629 #define GothicArray CapablancaArray
633 ChessSquare FalconArray[2][BOARD_FILES] = {
634 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
635 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
636 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
637 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
640 #define FalconArray CapablancaArray
643 #else // !(BOARD_FILES>=10)
644 #define XiangqiPosition FIDEArray
645 #define CapablancaArray FIDEArray
646 #define GothicArray FIDEArray
647 #define GreatArray FIDEArray
648 #endif // !(BOARD_FILES>=10)
650 #if (BOARD_FILES>=12)
651 ChessSquare CourierArray[2][BOARD_FILES] = {
652 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
653 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
654 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
655 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
657 ChessSquare ChuArray[6][BOARD_FILES] = {
658 { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
659 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
660 { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
661 BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
662 { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
663 WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
664 { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
665 BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
666 { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
667 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
668 { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
669 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
671 #else // !(BOARD_FILES>=12)
672 #define CourierArray CapablancaArray
673 #define ChuArray CapablancaArray
674 #endif // !(BOARD_FILES>=12)
677 Board initialPosition;
680 /* Convert str to a rating. Checks for special cases of "----",
682 "++++", etc. Also strips ()'s */
684 string_to_rating (char *str)
686 while(*str && !isdigit(*str)) ++str;
688 return 0; /* One of the special "no rating" cases */
696 /* Init programStats */
697 programStats.movelist[0] = 0;
698 programStats.depth = 0;
699 programStats.nr_moves = 0;
700 programStats.moves_left = 0;
701 programStats.nodes = 0;
702 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
703 programStats.score = 0;
704 programStats.got_only_move = 0;
705 programStats.got_fail = 0;
706 programStats.line_is_book = 0;
711 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
712 if (appData.firstPlaysBlack) {
713 first.twoMachinesColor = "black\n";
714 second.twoMachinesColor = "white\n";
716 first.twoMachinesColor = "white\n";
717 second.twoMachinesColor = "black\n";
720 first.other = &second;
721 second.other = &first;
724 if(appData.timeOddsMode) {
725 norm = appData.timeOdds[0];
726 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
728 first.timeOdds = appData.timeOdds[0]/norm;
729 second.timeOdds = appData.timeOdds[1]/norm;
732 if(programVersion) free(programVersion);
733 if (appData.noChessProgram) {
734 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
735 sprintf(programVersion, "%s", PACKAGE_STRING);
737 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
738 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
739 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
744 UnloadEngine (ChessProgramState *cps)
746 /* Kill off first chess program */
747 if (cps->isr != NULL)
748 RemoveInputSource(cps->isr);
751 if (cps->pr != NoProc) {
753 DoSleep( appData.delayBeforeQuit );
754 SendToProgram("quit\n", cps);
755 DoSleep( appData.delayAfterQuit );
756 DestroyChildProcess(cps->pr, cps->useSigterm);
759 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
763 ClearOptions (ChessProgramState *cps)
766 cps->nrOptions = cps->comboCnt = 0;
767 for(i=0; i<MAX_OPTIONS; i++) {
768 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
769 cps->option[i].textValue = 0;
773 char *engineNames[] = {
774 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
775 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
777 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
778 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
783 InitEngine (ChessProgramState *cps, int n)
784 { // [HGM] all engine initialiation put in a function that does one engine
788 cps->which = engineNames[n];
789 cps->maybeThinking = FALSE;
793 cps->sendDrawOffers = 1;
795 cps->program = appData.chessProgram[n];
796 cps->host = appData.host[n];
797 cps->dir = appData.directory[n];
798 cps->initString = appData.engInitString[n];
799 cps->computerString = appData.computerString[n];
800 cps->useSigint = TRUE;
801 cps->useSigterm = TRUE;
802 cps->reuse = appData.reuse[n];
803 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
804 cps->useSetboard = FALSE;
806 cps->usePing = FALSE;
809 cps->usePlayother = FALSE;
810 cps->useColors = TRUE;
811 cps->useUsermove = FALSE;
812 cps->sendICS = FALSE;
813 cps->sendName = appData.icsActive;
814 cps->sdKludge = FALSE;
815 cps->stKludge = FALSE;
816 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
817 TidyProgramName(cps->program, cps->host, cps->tidy);
819 ASSIGN(cps->variants, appData.variant);
820 cps->analysisSupport = 2; /* detect */
821 cps->analyzing = FALSE;
822 cps->initDone = FALSE;
825 /* New features added by Tord: */
826 cps->useFEN960 = FALSE;
827 cps->useOOCastle = TRUE;
828 /* End of new features added by Tord. */
829 cps->fenOverride = appData.fenOverride[n];
831 /* [HGM] time odds: set factor for each machine */
832 cps->timeOdds = appData.timeOdds[n];
834 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
835 cps->accumulateTC = appData.accumulateTC[n];
836 cps->maxNrOfSessions = 1;
841 cps->supportsNPS = UNKNOWN;
842 cps->memSize = FALSE;
843 cps->maxCores = FALSE;
844 ASSIGN(cps->egtFormats, "");
847 cps->optionSettings = appData.engOptions[n];
849 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
850 cps->isUCI = appData.isUCI[n]; /* [AS] */
851 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
854 if (appData.protocolVersion[n] > PROTOVER
855 || appData.protocolVersion[n] < 1)
860 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
861 appData.protocolVersion[n]);
862 if( (len >= MSG_SIZ) && appData.debugMode )
863 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
865 DisplayFatalError(buf, 0, 2);
869 cps->protocolVersion = appData.protocolVersion[n];
872 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
873 ParseFeatures(appData.featureDefaults, cps);
876 ChessProgramState *savCps;
884 if(WaitForEngine(savCps, LoadEngine)) return;
885 CommonEngineInit(); // recalculate time odds
886 if(gameInfo.variant != StringToVariant(appData.variant)) {
887 // we changed variant when loading the engine; this forces us to reset
888 Reset(TRUE, savCps != &first);
889 oldMode = BeginningOfGame; // to prevent restoring old mode
891 InitChessProgram(savCps, FALSE);
892 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
893 DisplayMessage("", "");
894 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
895 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
898 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
902 ReplaceEngine (ChessProgramState *cps, int n)
904 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
906 if(oldMode != BeginningOfGame) EditGameEvent();
909 appData.noChessProgram = FALSE;
910 appData.clockMode = TRUE;
913 if(n) return; // only startup first engine immediately; second can wait
914 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
918 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
919 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
921 static char resetOptions[] =
922 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
923 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
924 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
925 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
928 FloatToFront(char **list, char *engineLine)
930 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
932 if(appData.recentEngines <= 0) return;
933 TidyProgramName(engineLine, "localhost", tidy+1);
934 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
935 strncpy(buf+1, *list, MSG_SIZ-50);
936 if(p = strstr(buf, tidy)) { // tidy name appears in list
937 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
938 while(*p++ = *++q); // squeeze out
940 strcat(tidy, buf+1); // put list behind tidy name
941 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
942 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
943 ASSIGN(*list, tidy+1);
946 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
949 Load (ChessProgramState *cps, int i)
951 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
952 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
953 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
954 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
955 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
956 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
957 appData.firstProtocolVersion = PROTOVER;
958 ParseArgsFromString(buf);
960 ReplaceEngine(cps, i);
961 FloatToFront(&appData.recentEngineList, engineLine);
965 while(q = strchr(p, SLASH)) p = q+1;
966 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
967 if(engineDir[0] != NULLCHAR) {
968 ASSIGN(appData.directory[i], engineDir); p = engineName;
969 } else if(p != engineName) { // derive directory from engine path, when not given
971 ASSIGN(appData.directory[i], engineName);
973 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
974 } else { ASSIGN(appData.directory[i], "."); }
975 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
977 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
978 snprintf(command, MSG_SIZ, "%s %s", p, params);
981 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
982 ASSIGN(appData.chessProgram[i], p);
983 appData.isUCI[i] = isUCI;
984 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
985 appData.hasOwnBookUCI[i] = hasBook;
986 if(!nickName[0]) useNick = FALSE;
987 if(useNick) ASSIGN(appData.pgnName[i], nickName);
991 q = firstChessProgramNames;
992 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
993 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
994 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
995 quote, p, quote, appData.directory[i],
996 useNick ? " -fn \"" : "",
997 useNick ? nickName : "",
999 v1 ? " -firstProtocolVersion 1" : "",
1000 hasBook ? "" : " -fNoOwnBookUCI",
1001 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1002 storeVariant ? " -variant " : "",
1003 storeVariant ? VariantName(gameInfo.variant) : "");
1004 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1005 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1006 if(insert != q) insert[-1] = NULLCHAR;
1007 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1009 FloatToFront(&appData.recentEngineList, buf);
1011 ReplaceEngine(cps, i);
1017 int matched, min, sec;
1019 * Parse timeControl resource
1021 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1022 appData.movesPerSession)) {
1024 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1025 DisplayFatalError(buf, 0, 2);
1029 * Parse searchTime resource
1031 if (*appData.searchTime != NULLCHAR) {
1032 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1034 searchTime = min * 60;
1035 } else if (matched == 2) {
1036 searchTime = min * 60 + sec;
1039 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1040 DisplayFatalError(buf, 0, 2);
1049 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1050 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1052 GetTimeMark(&programStartTime);
1053 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1054 appData.seedBase = random() + (random()<<15);
1055 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1057 ClearProgramStats();
1058 programStats.ok_to_send = 1;
1059 programStats.seen_stat = 0;
1062 * Initialize game list
1068 * Internet chess server status
1070 if (appData.icsActive) {
1071 appData.matchMode = FALSE;
1072 appData.matchGames = 0;
1074 appData.noChessProgram = !appData.zippyPlay;
1076 appData.zippyPlay = FALSE;
1077 appData.zippyTalk = FALSE;
1078 appData.noChessProgram = TRUE;
1080 if (*appData.icsHelper != NULLCHAR) {
1081 appData.useTelnet = TRUE;
1082 appData.telnetProgram = appData.icsHelper;
1085 appData.zippyTalk = appData.zippyPlay = FALSE;
1088 /* [AS] Initialize pv info list [HGM] and game state */
1092 for( i=0; i<=framePtr; i++ ) {
1093 pvInfoList[i].depth = -1;
1094 boards[i][EP_STATUS] = EP_NONE;
1095 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1101 /* [AS] Adjudication threshold */
1102 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1104 InitEngine(&first, 0);
1105 InitEngine(&second, 1);
1108 pairing.which = "pairing"; // pairing engine
1109 pairing.pr = NoProc;
1111 pairing.program = appData.pairingEngine;
1112 pairing.host = "localhost";
1115 if (appData.icsActive) {
1116 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1117 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1118 appData.clockMode = FALSE;
1119 first.sendTime = second.sendTime = 0;
1123 /* Override some settings from environment variables, for backward
1124 compatibility. Unfortunately it's not feasible to have the env
1125 vars just set defaults, at least in xboard. Ugh.
1127 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1132 if (!appData.icsActive) {
1136 /* Check for variants that are supported only in ICS mode,
1137 or not at all. Some that are accepted here nevertheless
1138 have bugs; see comments below.
1140 VariantClass variant = StringToVariant(appData.variant);
1142 case VariantBughouse: /* need four players and two boards */
1143 case VariantKriegspiel: /* need to hide pieces and move details */
1144 /* case VariantFischeRandom: (Fabien: moved below) */
1145 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1146 if( (len >= MSG_SIZ) && appData.debugMode )
1147 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1149 DisplayFatalError(buf, 0, 2);
1152 case VariantUnknown:
1153 case VariantLoadable:
1163 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1164 if( (len >= MSG_SIZ) && appData.debugMode )
1165 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1167 DisplayFatalError(buf, 0, 2);
1170 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1171 case VariantFairy: /* [HGM] TestLegality definitely off! */
1172 case VariantGothic: /* [HGM] should work */
1173 case VariantCapablanca: /* [HGM] should work */
1174 case VariantCourier: /* [HGM] initial forced moves not implemented */
1175 case VariantShogi: /* [HGM] could still mate with pawn drop */
1176 case VariantChu: /* [HGM] experimental */
1177 case VariantKnightmate: /* [HGM] should work */
1178 case VariantCylinder: /* [HGM] untested */
1179 case VariantFalcon: /* [HGM] untested */
1180 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1181 offboard interposition not understood */
1182 case VariantNormal: /* definitely works! */
1183 case VariantWildCastle: /* pieces not automatically shuffled */
1184 case VariantNoCastle: /* pieces not automatically shuffled */
1185 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1186 case VariantLosers: /* should work except for win condition,
1187 and doesn't know captures are mandatory */
1188 case VariantSuicide: /* should work except for win condition,
1189 and doesn't know captures are mandatory */
1190 case VariantGiveaway: /* should work except for win condition,
1191 and doesn't know captures are mandatory */
1192 case VariantTwoKings: /* should work */
1193 case VariantAtomic: /* should work except for win condition */
1194 case Variant3Check: /* should work except for win condition */
1195 case VariantShatranj: /* should work except for all win conditions */
1196 case VariantMakruk: /* should work except for draw countdown */
1197 case VariantASEAN : /* should work except for draw countdown */
1198 case VariantBerolina: /* might work if TestLegality is off */
1199 case VariantCapaRandom: /* should work */
1200 case VariantJanus: /* should work */
1201 case VariantSuper: /* experimental */
1202 case VariantGreat: /* experimental, requires legality testing to be off */
1203 case VariantSChess: /* S-Chess, should work */
1204 case VariantGrand: /* should work */
1205 case VariantSpartan: /* should work */
1206 case VariantLion: /* should work */
1214 NextIntegerFromString (char ** str, long * value)
1219 while( *s == ' ' || *s == '\t' ) {
1225 if( *s >= '0' && *s <= '9' ) {
1226 while( *s >= '0' && *s <= '9' ) {
1227 *value = *value * 10 + (*s - '0');
1240 NextTimeControlFromString (char ** str, long * value)
1243 int result = NextIntegerFromString( str, &temp );
1246 *value = temp * 60; /* Minutes */
1247 if( **str == ':' ) {
1249 result = NextIntegerFromString( str, &temp );
1250 *value += temp; /* Seconds */
1258 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1259 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1260 int result = -1, type = 0; long temp, temp2;
1262 if(**str != ':') return -1; // old params remain in force!
1264 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1265 if( NextIntegerFromString( str, &temp ) ) return -1;
1266 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1269 /* time only: incremental or sudden-death time control */
1270 if(**str == '+') { /* increment follows; read it */
1272 if(**str == '!') type = *(*str)++; // Bronstein TC
1273 if(result = NextIntegerFromString( str, &temp2)) return -1;
1274 *inc = temp2 * 1000;
1275 if(**str == '.') { // read fraction of increment
1276 char *start = ++(*str);
1277 if(result = NextIntegerFromString( str, &temp2)) return -1;
1279 while(start++ < *str) temp2 /= 10;
1283 *moves = 0; *tc = temp * 1000; *incType = type;
1287 (*str)++; /* classical time control */
1288 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1300 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1301 { /* [HGM] get time to add from the multi-session time-control string */
1302 int incType, moves=1; /* kludge to force reading of first session */
1303 long time, increment;
1306 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1308 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1309 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1310 if(movenr == -1) return time; /* last move before new session */
1311 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1312 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1313 if(!moves) return increment; /* current session is incremental */
1314 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1315 } while(movenr >= -1); /* try again for next session */
1317 return 0; // no new time quota on this move
1321 ParseTimeControl (char *tc, float ti, int mps)
1325 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1328 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1329 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1330 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1334 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1336 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1339 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1341 snprintf(buf, MSG_SIZ, ":%s", mytc);
1343 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1345 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1350 /* Parse second time control */
1353 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1361 timeControl_2 = tc2 * 1000;
1371 timeControl = tc1 * 1000;
1374 timeIncrement = ti * 1000; /* convert to ms */
1375 movesPerSession = 0;
1378 movesPerSession = mps;
1386 if (appData.debugMode) {
1387 # ifdef __GIT_VERSION
1388 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1390 fprintf(debugFP, "Version: %s\n", programVersion);
1393 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1395 set_cont_sequence(appData.wrapContSeq);
1396 if (appData.matchGames > 0) {
1397 appData.matchMode = TRUE;
1398 } else if (appData.matchMode) {
1399 appData.matchGames = 1;
1401 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1402 appData.matchGames = appData.sameColorGames;
1403 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1404 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1405 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1408 if (appData.noChessProgram || first.protocolVersion == 1) {
1411 /* kludge: allow timeout for initial "feature" commands */
1413 DisplayMessage("", _("Starting chess program"));
1414 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1419 CalculateIndex (int index, int gameNr)
1420 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1422 if(index > 0) return index; // fixed nmber
1423 if(index == 0) return 1;
1424 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1425 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1430 LoadGameOrPosition (int gameNr)
1431 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1432 if (*appData.loadGameFile != NULLCHAR) {
1433 if (!LoadGameFromFile(appData.loadGameFile,
1434 CalculateIndex(appData.loadGameIndex, gameNr),
1435 appData.loadGameFile, FALSE)) {
1436 DisplayFatalError(_("Bad game file"), 0, 1);
1439 } else if (*appData.loadPositionFile != NULLCHAR) {
1440 if (!LoadPositionFromFile(appData.loadPositionFile,
1441 CalculateIndex(appData.loadPositionIndex, gameNr),
1442 appData.loadPositionFile)) {
1443 DisplayFatalError(_("Bad position file"), 0, 1);
1451 ReserveGame (int gameNr, char resChar)
1453 FILE *tf = fopen(appData.tourneyFile, "r+");
1454 char *p, *q, c, buf[MSG_SIZ];
1455 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1456 safeStrCpy(buf, lastMsg, MSG_SIZ);
1457 DisplayMessage(_("Pick new game"), "");
1458 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1459 ParseArgsFromFile(tf);
1460 p = q = appData.results;
1461 if(appData.debugMode) {
1462 char *r = appData.participants;
1463 fprintf(debugFP, "results = '%s'\n", p);
1464 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1465 fprintf(debugFP, "\n");
1467 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1469 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1470 safeStrCpy(q, p, strlen(p) + 2);
1471 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1472 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1473 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1474 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1477 fseek(tf, -(strlen(p)+4), SEEK_END);
1479 if(c != '"') // depending on DOS or Unix line endings we can be one off
1480 fseek(tf, -(strlen(p)+2), SEEK_END);
1481 else fseek(tf, -(strlen(p)+3), SEEK_END);
1482 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1483 DisplayMessage(buf, "");
1484 free(p); appData.results = q;
1485 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1486 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1487 int round = appData.defaultMatchGames * appData.tourneyType;
1488 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1489 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1490 UnloadEngine(&first); // next game belongs to other pairing;
1491 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1493 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1497 MatchEvent (int mode)
1498 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1500 if(matchMode) { // already in match mode: switch it off
1502 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1505 // if(gameMode != BeginningOfGame) {
1506 // DisplayError(_("You can only start a match from the initial position."), 0);
1510 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1511 /* Set up machine vs. machine match */
1513 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1514 if(appData.tourneyFile[0]) {
1516 if(nextGame > appData.matchGames) {
1518 if(strchr(appData.results, '*') == NULL) {
1520 appData.tourneyCycles++;
1521 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1523 NextTourneyGame(-1, &dummy);
1525 if(nextGame <= appData.matchGames) {
1526 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1528 ScheduleDelayedEvent(NextMatchGame, 10000);
1533 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1534 DisplayError(buf, 0);
1535 appData.tourneyFile[0] = 0;
1539 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1540 DisplayFatalError(_("Can't have a match with no chess programs"),
1545 matchGame = roundNr = 1;
1546 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1550 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1553 InitBackEnd3 P((void))
1555 GameMode initialMode;
1559 InitChessProgram(&first, startedFromSetupPosition);
1561 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1562 free(programVersion);
1563 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1564 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1565 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1568 if (appData.icsActive) {
1570 /* [DM] Make a console window if needed [HGM] merged ifs */
1576 if (*appData.icsCommPort != NULLCHAR)
1577 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1578 appData.icsCommPort);
1580 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1581 appData.icsHost, appData.icsPort);
1583 if( (len >= MSG_SIZ) && appData.debugMode )
1584 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1586 DisplayFatalError(buf, err, 1);
1591 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1593 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1594 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1595 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1596 } else if (appData.noChessProgram) {
1602 if (*appData.cmailGameName != NULLCHAR) {
1604 OpenLoopback(&cmailPR);
1606 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1610 DisplayMessage("", "");
1611 if (StrCaseCmp(appData.initialMode, "") == 0) {
1612 initialMode = BeginningOfGame;
1613 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1614 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1615 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1616 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1619 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1620 initialMode = TwoMachinesPlay;
1621 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1622 initialMode = AnalyzeFile;
1623 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1624 initialMode = AnalyzeMode;
1625 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1626 initialMode = MachinePlaysWhite;
1627 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1628 initialMode = MachinePlaysBlack;
1629 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1630 initialMode = EditGame;
1631 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1632 initialMode = EditPosition;
1633 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1634 initialMode = Training;
1636 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1637 if( (len >= MSG_SIZ) && appData.debugMode )
1638 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1640 DisplayFatalError(buf, 0, 2);
1644 if (appData.matchMode) {
1645 if(appData.tourneyFile[0]) { // start tourney from command line
1647 if(f = fopen(appData.tourneyFile, "r")) {
1648 ParseArgsFromFile(f); // make sure tourney parmeters re known
1650 appData.clockMode = TRUE;
1652 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1655 } else if (*appData.cmailGameName != NULLCHAR) {
1656 /* Set up cmail mode */
1657 ReloadCmailMsgEvent(TRUE);
1659 /* Set up other modes */
1660 if (initialMode == AnalyzeFile) {
1661 if (*appData.loadGameFile == NULLCHAR) {
1662 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1666 if (*appData.loadGameFile != NULLCHAR) {
1667 (void) LoadGameFromFile(appData.loadGameFile,
1668 appData.loadGameIndex,
1669 appData.loadGameFile, TRUE);
1670 } else if (*appData.loadPositionFile != NULLCHAR) {
1671 (void) LoadPositionFromFile(appData.loadPositionFile,
1672 appData.loadPositionIndex,
1673 appData.loadPositionFile);
1674 /* [HGM] try to make self-starting even after FEN load */
1675 /* to allow automatic setup of fairy variants with wtm */
1676 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1677 gameMode = BeginningOfGame;
1678 setboardSpoiledMachineBlack = 1;
1680 /* [HGM] loadPos: make that every new game uses the setup */
1681 /* from file as long as we do not switch variant */
1682 if(!blackPlaysFirst) {
1683 startedFromPositionFile = TRUE;
1684 CopyBoard(filePosition, boards[0]);
1687 if (initialMode == AnalyzeMode) {
1688 if (appData.noChessProgram) {
1689 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1692 if (appData.icsActive) {
1693 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1697 } else if (initialMode == AnalyzeFile) {
1698 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1699 ShowThinkingEvent();
1701 AnalysisPeriodicEvent(1);
1702 } else if (initialMode == MachinePlaysWhite) {
1703 if (appData.noChessProgram) {
1704 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1708 if (appData.icsActive) {
1709 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1713 MachineWhiteEvent();
1714 } else if (initialMode == MachinePlaysBlack) {
1715 if (appData.noChessProgram) {
1716 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1720 if (appData.icsActive) {
1721 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1725 MachineBlackEvent();
1726 } else if (initialMode == TwoMachinesPlay) {
1727 if (appData.noChessProgram) {
1728 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1732 if (appData.icsActive) {
1733 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1738 } else if (initialMode == EditGame) {
1740 } else if (initialMode == EditPosition) {
1741 EditPositionEvent();
1742 } else if (initialMode == Training) {
1743 if (*appData.loadGameFile == NULLCHAR) {
1744 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1753 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1755 DisplayBook(current+1);
1757 MoveHistorySet( movelist, first, last, current, pvInfoList );
1759 EvalGraphSet( first, last, current, pvInfoList );
1761 MakeEngineOutputTitle();
1765 * Establish will establish a contact to a remote host.port.
1766 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1767 * used to talk to the host.
1768 * Returns 0 if okay, error code if not.
1775 if (*appData.icsCommPort != NULLCHAR) {
1776 /* Talk to the host through a serial comm port */
1777 return OpenCommPort(appData.icsCommPort, &icsPR);
1779 } else if (*appData.gateway != NULLCHAR) {
1780 if (*appData.remoteShell == NULLCHAR) {
1781 /* Use the rcmd protocol to run telnet program on a gateway host */
1782 snprintf(buf, sizeof(buf), "%s %s %s",
1783 appData.telnetProgram, appData.icsHost, appData.icsPort);
1784 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1787 /* Use the rsh program to run telnet program on a gateway host */
1788 if (*appData.remoteUser == NULLCHAR) {
1789 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1790 appData.gateway, appData.telnetProgram,
1791 appData.icsHost, appData.icsPort);
1793 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1794 appData.remoteShell, appData.gateway,
1795 appData.remoteUser, appData.telnetProgram,
1796 appData.icsHost, appData.icsPort);
1798 return StartChildProcess(buf, "", &icsPR);
1801 } else if (appData.useTelnet) {
1802 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1805 /* TCP socket interface differs somewhat between
1806 Unix and NT; handle details in the front end.
1808 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1813 EscapeExpand (char *p, char *q)
1814 { // [HGM] initstring: routine to shape up string arguments
1815 while(*p++ = *q++) if(p[-1] == '\\')
1817 case 'n': p[-1] = '\n'; break;
1818 case 'r': p[-1] = '\r'; break;
1819 case 't': p[-1] = '\t'; break;
1820 case '\\': p[-1] = '\\'; break;
1821 case 0: *p = 0; return;
1822 default: p[-1] = q[-1]; break;
1827 show_bytes (FILE *fp, char *buf, int count)
1830 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1831 fprintf(fp, "\\%03o", *buf & 0xff);
1840 /* Returns an errno value */
1842 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1844 char buf[8192], *p, *q, *buflim;
1845 int left, newcount, outcount;
1847 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1848 *appData.gateway != NULLCHAR) {
1849 if (appData.debugMode) {
1850 fprintf(debugFP, ">ICS: ");
1851 show_bytes(debugFP, message, count);
1852 fprintf(debugFP, "\n");
1854 return OutputToProcess(pr, message, count, outError);
1857 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1864 if (appData.debugMode) {
1865 fprintf(debugFP, ">ICS: ");
1866 show_bytes(debugFP, buf, newcount);
1867 fprintf(debugFP, "\n");
1869 outcount = OutputToProcess(pr, buf, newcount, outError);
1870 if (outcount < newcount) return -1; /* to be sure */
1877 } else if (((unsigned char) *p) == TN_IAC) {
1878 *q++ = (char) TN_IAC;
1885 if (appData.debugMode) {
1886 fprintf(debugFP, ">ICS: ");
1887 show_bytes(debugFP, buf, newcount);
1888 fprintf(debugFP, "\n");
1890 outcount = OutputToProcess(pr, buf, newcount, outError);
1891 if (outcount < newcount) return -1; /* to be sure */
1896 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1898 int outError, outCount;
1899 static int gotEof = 0;
1902 /* Pass data read from player on to ICS */
1905 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1906 if (outCount < count) {
1907 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1909 if(have_sent_ICS_logon == 2) {
1910 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1911 fprintf(ini, "%s", message);
1912 have_sent_ICS_logon = 3;
1914 have_sent_ICS_logon = 1;
1915 } else if(have_sent_ICS_logon == 3) {
1916 fprintf(ini, "%s", message);
1918 have_sent_ICS_logon = 1;
1920 } else if (count < 0) {
1921 RemoveInputSource(isr);
1922 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1923 } else if (gotEof++ > 0) {
1924 RemoveInputSource(isr);
1925 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1931 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1932 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1933 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1934 SendToICS("date\n");
1935 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1938 /* added routine for printf style output to ics */
1940 ics_printf (char *format, ...)
1942 char buffer[MSG_SIZ];
1945 va_start(args, format);
1946 vsnprintf(buffer, sizeof(buffer), format, args);
1947 buffer[sizeof(buffer)-1] = '\0';
1955 int count, outCount, outError;
1957 if (icsPR == NoProc) return;
1960 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1961 if (outCount < count) {
1962 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1966 /* This is used for sending logon scripts to the ICS. Sending
1967 without a delay causes problems when using timestamp on ICC
1968 (at least on my machine). */
1970 SendToICSDelayed (char *s, long msdelay)
1972 int count, outCount, outError;
1974 if (icsPR == NoProc) return;
1977 if (appData.debugMode) {
1978 fprintf(debugFP, ">ICS: ");
1979 show_bytes(debugFP, s, count);
1980 fprintf(debugFP, "\n");
1982 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1984 if (outCount < count) {
1985 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1990 /* Remove all highlighting escape sequences in s
1991 Also deletes any suffix starting with '('
1994 StripHighlightAndTitle (char *s)
1996 static char retbuf[MSG_SIZ];
1999 while (*s != NULLCHAR) {
2000 while (*s == '\033') {
2001 while (*s != NULLCHAR && !isalpha(*s)) s++;
2002 if (*s != NULLCHAR) s++;
2004 while (*s != NULLCHAR && *s != '\033') {
2005 if (*s == '(' || *s == '[') {
2016 /* Remove all highlighting escape sequences in s */
2018 StripHighlight (char *s)
2020 static char retbuf[MSG_SIZ];
2023 while (*s != NULLCHAR) {
2024 while (*s == '\033') {
2025 while (*s != NULLCHAR && !isalpha(*s)) s++;
2026 if (*s != NULLCHAR) s++;
2028 while (*s != NULLCHAR && *s != '\033') {
2036 char engineVariant[MSG_SIZ];
2037 char *variantNames[] = VARIANT_NAMES;
2039 VariantName (VariantClass v)
2041 if(v == VariantUnknown || *engineVariant) return engineVariant;
2042 return variantNames[v];
2046 /* Identify a variant from the strings the chess servers use or the
2047 PGN Variant tag names we use. */
2049 StringToVariant (char *e)
2053 VariantClass v = VariantNormal;
2054 int i, found = FALSE;
2060 /* [HGM] skip over optional board-size prefixes */
2061 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2062 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2063 while( *e++ != '_');
2066 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2070 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2071 if (StrCaseStr(e, variantNames[i])) {
2072 v = (VariantClass) i;
2079 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2080 || StrCaseStr(e, "wild/fr")
2081 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2082 v = VariantFischeRandom;
2083 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2084 (i = 1, p = StrCaseStr(e, "w"))) {
2086 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2093 case 0: /* FICS only, actually */
2095 /* Castling legal even if K starts on d-file */
2096 v = VariantWildCastle;
2101 /* Castling illegal even if K & R happen to start in
2102 normal positions. */
2103 v = VariantNoCastle;
2116 /* Castling legal iff K & R start in normal positions */
2122 /* Special wilds for position setup; unclear what to do here */
2123 v = VariantLoadable;
2126 /* Bizarre ICC game */
2127 v = VariantTwoKings;
2130 v = VariantKriegspiel;
2136 v = VariantFischeRandom;
2139 v = VariantCrazyhouse;
2142 v = VariantBughouse;
2148 /* Not quite the same as FICS suicide! */
2149 v = VariantGiveaway;
2155 v = VariantShatranj;
2158 /* Temporary names for future ICC types. The name *will* change in
2159 the next xboard/WinBoard release after ICC defines it. */
2197 v = VariantCapablanca;
2200 v = VariantKnightmate;
2206 v = VariantCylinder;
2212 v = VariantCapaRandom;
2215 v = VariantBerolina;
2227 /* Found "wild" or "w" in the string but no number;
2228 must assume it's normal chess. */
2232 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2233 if( (len >= MSG_SIZ) && appData.debugMode )
2234 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2236 DisplayError(buf, 0);
2242 if (appData.debugMode) {
2243 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2244 e, wnum, VariantName(v));
2249 static int leftover_start = 0, leftover_len = 0;
2250 char star_match[STAR_MATCH_N][MSG_SIZ];
2252 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2253 advance *index beyond it, and set leftover_start to the new value of
2254 *index; else return FALSE. If pattern contains the character '*', it
2255 matches any sequence of characters not containing '\r', '\n', or the
2256 character following the '*' (if any), and the matched sequence(s) are
2257 copied into star_match.
2260 looking_at ( char *buf, int *index, char *pattern)
2262 char *bufp = &buf[*index], *patternp = pattern;
2264 char *matchp = star_match[0];
2267 if (*patternp == NULLCHAR) {
2268 *index = leftover_start = bufp - buf;
2272 if (*bufp == NULLCHAR) return FALSE;
2273 if (*patternp == '*') {
2274 if (*bufp == *(patternp + 1)) {
2276 matchp = star_match[++star_count];
2280 } else if (*bufp == '\n' || *bufp == '\r') {
2282 if (*patternp == NULLCHAR)
2287 *matchp++ = *bufp++;
2291 if (*patternp != *bufp) return FALSE;
2298 SendToPlayer (char *data, int length)
2300 int error, outCount;
2301 outCount = OutputToProcess(NoProc, data, length, &error);
2302 if (outCount < length) {
2303 DisplayFatalError(_("Error writing to display"), error, 1);
2308 PackHolding (char packed[], char *holding)
2318 switch (runlength) {
2329 sprintf(q, "%d", runlength);
2341 /* Telnet protocol requests from the front end */
2343 TelnetRequest (unsigned char ddww, unsigned char option)
2345 unsigned char msg[3];
2346 int outCount, outError;
2348 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2350 if (appData.debugMode) {
2351 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2367 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2376 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2379 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2384 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2386 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2393 if (!appData.icsActive) return;
2394 TelnetRequest(TN_DO, TN_ECHO);
2400 if (!appData.icsActive) return;
2401 TelnetRequest(TN_DONT, TN_ECHO);
2405 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2407 /* put the holdings sent to us by the server on the board holdings area */
2408 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2412 if(gameInfo.holdingsWidth < 2) return;
2413 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2414 return; // prevent overwriting by pre-board holdings
2416 if( (int)lowestPiece >= BlackPawn ) {
2419 holdingsStartRow = BOARD_HEIGHT-1;
2422 holdingsColumn = BOARD_WIDTH-1;
2423 countsColumn = BOARD_WIDTH-2;
2424 holdingsStartRow = 0;
2428 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2429 board[i][holdingsColumn] = EmptySquare;
2430 board[i][countsColumn] = (ChessSquare) 0;
2432 while( (p=*holdings++) != NULLCHAR ) {
2433 piece = CharToPiece( ToUpper(p) );
2434 if(piece == EmptySquare) continue;
2435 /*j = (int) piece - (int) WhitePawn;*/
2436 j = PieceToNumber(piece);
2437 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2438 if(j < 0) continue; /* should not happen */
2439 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2440 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2441 board[holdingsStartRow+j*direction][countsColumn]++;
2447 VariantSwitch (Board board, VariantClass newVariant)
2449 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2450 static Board oldBoard;
2452 startedFromPositionFile = FALSE;
2453 if(gameInfo.variant == newVariant) return;
2455 /* [HGM] This routine is called each time an assignment is made to
2456 * gameInfo.variant during a game, to make sure the board sizes
2457 * are set to match the new variant. If that means adding or deleting
2458 * holdings, we shift the playing board accordingly
2459 * This kludge is needed because in ICS observe mode, we get boards
2460 * of an ongoing game without knowing the variant, and learn about the
2461 * latter only later. This can be because of the move list we requested,
2462 * in which case the game history is refilled from the beginning anyway,
2463 * but also when receiving holdings of a crazyhouse game. In the latter
2464 * case we want to add those holdings to the already received position.
2468 if (appData.debugMode) {
2469 fprintf(debugFP, "Switch board from %s to %s\n",
2470 VariantName(gameInfo.variant), VariantName(newVariant));
2471 setbuf(debugFP, NULL);
2473 shuffleOpenings = 0; /* [HGM] shuffle */
2474 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2478 newWidth = 9; newHeight = 9;
2479 gameInfo.holdingsSize = 7;
2480 case VariantBughouse:
2481 case VariantCrazyhouse:
2482 newHoldingsWidth = 2; break;
2486 newHoldingsWidth = 2;
2487 gameInfo.holdingsSize = 8;
2490 case VariantCapablanca:
2491 case VariantCapaRandom:
2494 newHoldingsWidth = gameInfo.holdingsSize = 0;
2497 if(newWidth != gameInfo.boardWidth ||
2498 newHeight != gameInfo.boardHeight ||
2499 newHoldingsWidth != gameInfo.holdingsWidth ) {
2501 /* shift position to new playing area, if needed */
2502 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2503 for(i=0; i<BOARD_HEIGHT; i++)
2504 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2505 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2507 for(i=0; i<newHeight; i++) {
2508 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2509 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2511 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2512 for(i=0; i<BOARD_HEIGHT; i++)
2513 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2514 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2517 board[HOLDINGS_SET] = 0;
2518 gameInfo.boardWidth = newWidth;
2519 gameInfo.boardHeight = newHeight;
2520 gameInfo.holdingsWidth = newHoldingsWidth;
2521 gameInfo.variant = newVariant;
2522 InitDrawingSizes(-2, 0);
2523 } else gameInfo.variant = newVariant;
2524 CopyBoard(oldBoard, board); // remember correctly formatted board
2525 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2526 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2529 static int loggedOn = FALSE;
2531 /*-- Game start info cache: --*/
2533 char gs_kind[MSG_SIZ];
2534 static char player1Name[128] = "";
2535 static char player2Name[128] = "";
2536 static char cont_seq[] = "\n\\ ";
2537 static int player1Rating = -1;
2538 static int player2Rating = -1;
2539 /*----------------------------*/
2541 ColorClass curColor = ColorNormal;
2542 int suppressKibitz = 0;
2545 Boolean soughtPending = FALSE;
2546 Boolean seekGraphUp;
2547 #define MAX_SEEK_ADS 200
2549 char *seekAdList[MAX_SEEK_ADS];
2550 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2551 float tcList[MAX_SEEK_ADS];
2552 char colorList[MAX_SEEK_ADS];
2553 int nrOfSeekAds = 0;
2554 int minRating = 1010, maxRating = 2800;
2555 int hMargin = 10, vMargin = 20, h, w;
2556 extern int squareSize, lineGap;
2561 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2562 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2563 if(r < minRating+100 && r >=0 ) r = minRating+100;
2564 if(r > maxRating) r = maxRating;
2565 if(tc < 1.f) tc = 1.f;
2566 if(tc > 95.f) tc = 95.f;
2567 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2568 y = ((double)r - minRating)/(maxRating - minRating)
2569 * (h-vMargin-squareSize/8-1) + vMargin;
2570 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2571 if(strstr(seekAdList[i], " u ")) color = 1;
2572 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2573 !strstr(seekAdList[i], "bullet") &&
2574 !strstr(seekAdList[i], "blitz") &&
2575 !strstr(seekAdList[i], "standard") ) color = 2;
2576 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2577 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2581 PlotSingleSeekAd (int i)
2587 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2589 char buf[MSG_SIZ], *ext = "";
2590 VariantClass v = StringToVariant(type);
2591 if(strstr(type, "wild")) {
2592 ext = type + 4; // append wild number
2593 if(v == VariantFischeRandom) type = "chess960"; else
2594 if(v == VariantLoadable) type = "setup"; else
2595 type = VariantName(v);
2597 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2598 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2599 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2600 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2601 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2602 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2603 seekNrList[nrOfSeekAds] = nr;
2604 zList[nrOfSeekAds] = 0;
2605 seekAdList[nrOfSeekAds++] = StrSave(buf);
2606 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2611 EraseSeekDot (int i)
2613 int x = xList[i], y = yList[i], d=squareSize/4, k;
2614 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2615 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2616 // now replot every dot that overlapped
2617 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2618 int xx = xList[k], yy = yList[k];
2619 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2620 DrawSeekDot(xx, yy, colorList[k]);
2625 RemoveSeekAd (int nr)
2628 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2630 if(seekAdList[i]) free(seekAdList[i]);
2631 seekAdList[i] = seekAdList[--nrOfSeekAds];
2632 seekNrList[i] = seekNrList[nrOfSeekAds];
2633 ratingList[i] = ratingList[nrOfSeekAds];
2634 colorList[i] = colorList[nrOfSeekAds];
2635 tcList[i] = tcList[nrOfSeekAds];
2636 xList[i] = xList[nrOfSeekAds];
2637 yList[i] = yList[nrOfSeekAds];
2638 zList[i] = zList[nrOfSeekAds];
2639 seekAdList[nrOfSeekAds] = NULL;
2645 MatchSoughtLine (char *line)
2647 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2648 int nr, base, inc, u=0; char dummy;
2650 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2651 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2653 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2654 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2655 // match: compact and save the line
2656 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2666 if(!seekGraphUp) return FALSE;
2667 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2668 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2670 DrawSeekBackground(0, 0, w, h);
2671 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2672 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2673 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2674 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2676 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2679 snprintf(buf, MSG_SIZ, "%d", i);
2680 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2683 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2684 for(i=1; i<100; i+=(i<10?1:5)) {
2685 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2686 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2687 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2689 snprintf(buf, MSG_SIZ, "%d", i);
2690 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2693 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2698 SeekGraphClick (ClickType click, int x, int y, int moving)
2700 static int lastDown = 0, displayed = 0, lastSecond;
2701 if(y < 0) return FALSE;
2702 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2703 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2704 if(!seekGraphUp) return FALSE;
2705 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2706 DrawPosition(TRUE, NULL);
2709 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2710 if(click == Release || moving) return FALSE;
2712 soughtPending = TRUE;
2713 SendToICS(ics_prefix);
2714 SendToICS("sought\n"); // should this be "sought all"?
2715 } else { // issue challenge based on clicked ad
2716 int dist = 10000; int i, closest = 0, second = 0;
2717 for(i=0; i<nrOfSeekAds; i++) {
2718 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2719 if(d < dist) { dist = d; closest = i; }
2720 second += (d - zList[i] < 120); // count in-range ads
2721 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2725 second = (second > 1);
2726 if(displayed != closest || second != lastSecond) {
2727 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2728 lastSecond = second; displayed = closest;
2730 if(click == Press) {
2731 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2734 } // on press 'hit', only show info
2735 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2736 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2737 SendToICS(ics_prefix);
2739 return TRUE; // let incoming board of started game pop down the graph
2740 } else if(click == Release) { // release 'miss' is ignored
2741 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2742 if(moving == 2) { // right up-click
2743 nrOfSeekAds = 0; // refresh graph
2744 soughtPending = TRUE;
2745 SendToICS(ics_prefix);
2746 SendToICS("sought\n"); // should this be "sought all"?
2749 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2750 // press miss or release hit 'pop down' seek graph
2751 seekGraphUp = FALSE;
2752 DrawPosition(TRUE, NULL);
2758 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2760 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2761 #define STARTED_NONE 0
2762 #define STARTED_MOVES 1
2763 #define STARTED_BOARD 2
2764 #define STARTED_OBSERVE 3
2765 #define STARTED_HOLDINGS 4
2766 #define STARTED_CHATTER 5
2767 #define STARTED_COMMENT 6
2768 #define STARTED_MOVES_NOHIDE 7
2770 static int started = STARTED_NONE;
2771 static char parse[20000];
2772 static int parse_pos = 0;
2773 static char buf[BUF_SIZE + 1];
2774 static int firstTime = TRUE, intfSet = FALSE;
2775 static ColorClass prevColor = ColorNormal;
2776 static int savingComment = FALSE;
2777 static int cmatch = 0; // continuation sequence match
2784 int backup; /* [DM] For zippy color lines */
2786 char talker[MSG_SIZ]; // [HGM] chat
2789 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2791 if (appData.debugMode) {
2793 fprintf(debugFP, "<ICS: ");
2794 show_bytes(debugFP, data, count);
2795 fprintf(debugFP, "\n");
2799 if (appData.debugMode) { int f = forwardMostMove;
2800 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2801 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2802 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2805 /* If last read ended with a partial line that we couldn't parse,
2806 prepend it to the new read and try again. */
2807 if (leftover_len > 0) {
2808 for (i=0; i<leftover_len; i++)
2809 buf[i] = buf[leftover_start + i];
2812 /* copy new characters into the buffer */
2813 bp = buf + leftover_len;
2814 buf_len=leftover_len;
2815 for (i=0; i<count; i++)
2818 if (data[i] == '\r')
2821 // join lines split by ICS?
2822 if (!appData.noJoin)
2825 Joining just consists of finding matches against the
2826 continuation sequence, and discarding that sequence
2827 if found instead of copying it. So, until a match
2828 fails, there's nothing to do since it might be the
2829 complete sequence, and thus, something we don't want
2832 if (data[i] == cont_seq[cmatch])
2835 if (cmatch == strlen(cont_seq))
2837 cmatch = 0; // complete match. just reset the counter
2840 it's possible for the ICS to not include the space
2841 at the end of the last word, making our [correct]
2842 join operation fuse two separate words. the server
2843 does this when the space occurs at the width setting.
2845 if (!buf_len || buf[buf_len-1] != ' ')
2856 match failed, so we have to copy what matched before
2857 falling through and copying this character. In reality,
2858 this will only ever be just the newline character, but
2859 it doesn't hurt to be precise.
2861 strncpy(bp, cont_seq, cmatch);
2873 buf[buf_len] = NULLCHAR;
2874 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2879 while (i < buf_len) {
2880 /* Deal with part of the TELNET option negotiation
2881 protocol. We refuse to do anything beyond the
2882 defaults, except that we allow the WILL ECHO option,
2883 which ICS uses to turn off password echoing when we are
2884 directly connected to it. We reject this option
2885 if localLineEditing mode is on (always on in xboard)
2886 and we are talking to port 23, which might be a real
2887 telnet server that will try to keep WILL ECHO on permanently.
2889 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2890 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2891 unsigned char option;
2893 switch ((unsigned char) buf[++i]) {
2895 if (appData.debugMode)
2896 fprintf(debugFP, "\n<WILL ");
2897 switch (option = (unsigned char) buf[++i]) {
2899 if (appData.debugMode)
2900 fprintf(debugFP, "ECHO ");
2901 /* Reply only if this is a change, according
2902 to the protocol rules. */
2903 if (remoteEchoOption) break;
2904 if (appData.localLineEditing &&
2905 atoi(appData.icsPort) == TN_PORT) {
2906 TelnetRequest(TN_DONT, TN_ECHO);
2909 TelnetRequest(TN_DO, TN_ECHO);
2910 remoteEchoOption = TRUE;
2914 if (appData.debugMode)
2915 fprintf(debugFP, "%d ", option);
2916 /* Whatever this is, we don't want it. */
2917 TelnetRequest(TN_DONT, option);
2922 if (appData.debugMode)
2923 fprintf(debugFP, "\n<WONT ");
2924 switch (option = (unsigned char) buf[++i]) {
2926 if (appData.debugMode)
2927 fprintf(debugFP, "ECHO ");
2928 /* Reply only if this is a change, according
2929 to the protocol rules. */
2930 if (!remoteEchoOption) break;
2932 TelnetRequest(TN_DONT, TN_ECHO);
2933 remoteEchoOption = FALSE;
2936 if (appData.debugMode)
2937 fprintf(debugFP, "%d ", (unsigned char) option);
2938 /* Whatever this is, it must already be turned
2939 off, because we never agree to turn on
2940 anything non-default, so according to the
2941 protocol rules, we don't reply. */
2946 if (appData.debugMode)
2947 fprintf(debugFP, "\n<DO ");
2948 switch (option = (unsigned char) buf[++i]) {
2950 /* Whatever this is, we refuse to do it. */
2951 if (appData.debugMode)
2952 fprintf(debugFP, "%d ", option);
2953 TelnetRequest(TN_WONT, option);
2958 if (appData.debugMode)
2959 fprintf(debugFP, "\n<DONT ");
2960 switch (option = (unsigned char) buf[++i]) {
2962 if (appData.debugMode)
2963 fprintf(debugFP, "%d ", option);
2964 /* Whatever this is, we are already not doing
2965 it, because we never agree to do anything
2966 non-default, so according to the protocol
2967 rules, we don't reply. */
2972 if (appData.debugMode)
2973 fprintf(debugFP, "\n<IAC ");
2974 /* Doubled IAC; pass it through */
2978 if (appData.debugMode)
2979 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2980 /* Drop all other telnet commands on the floor */
2983 if (oldi > next_out)
2984 SendToPlayer(&buf[next_out], oldi - next_out);
2990 /* OK, this at least will *usually* work */
2991 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2995 if (loggedOn && !intfSet) {
2996 if (ics_type == ICS_ICC) {
2997 snprintf(str, MSG_SIZ,
2998 "/set-quietly interface %s\n/set-quietly style 12\n",
3000 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3001 strcat(str, "/set-2 51 1\n/set seek 1\n");
3002 } else if (ics_type == ICS_CHESSNET) {
3003 snprintf(str, MSG_SIZ, "/style 12\n");
3005 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3006 strcat(str, programVersion);
3007 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3008 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3009 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3011 strcat(str, "$iset nohighlight 1\n");
3013 strcat(str, "$iset lock 1\n$style 12\n");
3016 NotifyFrontendLogin();
3020 if (started == STARTED_COMMENT) {
3021 /* Accumulate characters in comment */
3022 parse[parse_pos++] = buf[i];
3023 if (buf[i] == '\n') {
3024 parse[parse_pos] = NULLCHAR;
3025 if(chattingPartner>=0) {
3027 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3028 OutputChatMessage(chattingPartner, mess);
3029 chattingPartner = -1;
3030 next_out = i+1; // [HGM] suppress printing in ICS window
3032 if(!suppressKibitz) // [HGM] kibitz
3033 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3034 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3035 int nrDigit = 0, nrAlph = 0, j;
3036 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3037 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3038 parse[parse_pos] = NULLCHAR;
3039 // try to be smart: if it does not look like search info, it should go to
3040 // ICS interaction window after all, not to engine-output window.
3041 for(j=0; j<parse_pos; j++) { // count letters and digits
3042 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3043 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3044 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3046 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3047 int depth=0; float score;
3048 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3049 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3050 pvInfoList[forwardMostMove-1].depth = depth;
3051 pvInfoList[forwardMostMove-1].score = 100*score;
3053 OutputKibitz(suppressKibitz, parse);
3056 if(gameMode == IcsObserving) // restore original ICS messages
3057 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3058 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3060 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3061 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3062 SendToPlayer(tmp, strlen(tmp));
3064 next_out = i+1; // [HGM] suppress printing in ICS window
3066 started = STARTED_NONE;
3068 /* Don't match patterns against characters in comment */
3073 if (started == STARTED_CHATTER) {
3074 if (buf[i] != '\n') {
3075 /* Don't match patterns against characters in chatter */
3079 started = STARTED_NONE;
3080 if(suppressKibitz) next_out = i+1;
3083 /* Kludge to deal with rcmd protocol */
3084 if (firstTime && looking_at(buf, &i, "\001*")) {
3085 DisplayFatalError(&buf[1], 0, 1);
3091 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3094 if (appData.debugMode)
3095 fprintf(debugFP, "ics_type %d\n", ics_type);
3098 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3099 ics_type = ICS_FICS;
3101 if (appData.debugMode)
3102 fprintf(debugFP, "ics_type %d\n", ics_type);
3105 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3106 ics_type = ICS_CHESSNET;
3108 if (appData.debugMode)
3109 fprintf(debugFP, "ics_type %d\n", ics_type);
3114 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3115 looking_at(buf, &i, "Logging you in as \"*\"") ||
3116 looking_at(buf, &i, "will be \"*\""))) {
3117 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3121 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3123 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3124 DisplayIcsInteractionTitle(buf);
3125 have_set_title = TRUE;
3128 /* skip finger notes */
3129 if (started == STARTED_NONE &&
3130 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3131 (buf[i] == '1' && buf[i+1] == '0')) &&
3132 buf[i+2] == ':' && buf[i+3] == ' ') {
3133 started = STARTED_CHATTER;
3139 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3140 if(appData.seekGraph) {
3141 if(soughtPending && MatchSoughtLine(buf+i)) {
3142 i = strstr(buf+i, "rated") - buf;
3143 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144 next_out = leftover_start = i;
3145 started = STARTED_CHATTER;
3146 suppressKibitz = TRUE;
3149 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3150 && looking_at(buf, &i, "* ads displayed")) {
3151 soughtPending = FALSE;
3156 if(appData.autoRefresh) {
3157 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3158 int s = (ics_type == ICS_ICC); // ICC format differs
3160 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3161 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3162 looking_at(buf, &i, "*% "); // eat prompt
3163 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3164 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3165 next_out = i; // suppress
3168 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3169 char *p = star_match[0];
3171 if(seekGraphUp) RemoveSeekAd(atoi(p));
3172 while(*p && *p++ != ' '); // next
3174 looking_at(buf, &i, "*% "); // eat prompt
3175 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3182 /* skip formula vars */
3183 if (started == STARTED_NONE &&
3184 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3185 started = STARTED_CHATTER;
3190 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3191 if (appData.autoKibitz && started == STARTED_NONE &&
3192 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3193 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3194 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3195 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3196 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3197 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3198 suppressKibitz = TRUE;
3199 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3201 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3202 && (gameMode == IcsPlayingWhite)) ||
3203 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3204 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3205 started = STARTED_CHATTER; // own kibitz we simply discard
3207 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3208 parse_pos = 0; parse[0] = NULLCHAR;
3209 savingComment = TRUE;
3210 suppressKibitz = gameMode != IcsObserving ? 2 :
3211 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3215 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3216 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3217 && atoi(star_match[0])) {
3218 // suppress the acknowledgements of our own autoKibitz
3220 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3222 SendToPlayer(star_match[0], strlen(star_match[0]));
3223 if(looking_at(buf, &i, "*% ")) // eat prompt
3224 suppressKibitz = FALSE;
3228 } // [HGM] kibitz: end of patch
3230 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3232 // [HGM] chat: intercept tells by users for which we have an open chat window
3234 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3235 looking_at(buf, &i, "* whispers:") ||
3236 looking_at(buf, &i, "* kibitzes:") ||
3237 looking_at(buf, &i, "* shouts:") ||
3238 looking_at(buf, &i, "* c-shouts:") ||
3239 looking_at(buf, &i, "--> * ") ||
3240 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3241 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3242 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3243 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3245 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3246 chattingPartner = -1;
3248 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3249 for(p=0; p<MAX_CHAT; p++) {
3250 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3251 talker[0] = '['; strcat(talker, "] ");
3252 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3253 chattingPartner = p; break;
3256 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3257 for(p=0; p<MAX_CHAT; p++) {
3258 if(!strcmp("kibitzes", chatPartner[p])) {
3259 talker[0] = '['; strcat(talker, "] ");
3260 chattingPartner = p; break;
3263 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3264 for(p=0; p<MAX_CHAT; p++) {
3265 if(!strcmp("whispers", chatPartner[p])) {
3266 talker[0] = '['; strcat(talker, "] ");
3267 chattingPartner = p; break;
3270 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3271 if(buf[i-8] == '-' && buf[i-3] == 't')
3272 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3273 if(!strcmp("c-shouts", chatPartner[p])) {
3274 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3275 chattingPartner = p; break;
3278 if(chattingPartner < 0)
3279 for(p=0; p<MAX_CHAT; p++) {
3280 if(!strcmp("shouts", chatPartner[p])) {
3281 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3282 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3283 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3284 chattingPartner = p; break;
3288 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3289 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3290 talker[0] = 0; Colorize(ColorTell, FALSE);
3291 chattingPartner = p; break;
3293 if(chattingPartner<0) i = oldi; else {
3294 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3295 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3296 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3297 started = STARTED_COMMENT;
3298 parse_pos = 0; parse[0] = NULLCHAR;
3299 savingComment = 3 + chattingPartner; // counts as TRUE
3300 suppressKibitz = TRUE;
3303 } // [HGM] chat: end of patch
3306 if (appData.zippyTalk || appData.zippyPlay) {
3307 /* [DM] Backup address for color zippy lines */
3309 if (loggedOn == TRUE)
3310 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3311 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3313 } // [DM] 'else { ' deleted
3315 /* Regular tells and says */
3316 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3317 looking_at(buf, &i, "* (your partner) tells you: ") ||
3318 looking_at(buf, &i, "* says: ") ||
3319 /* Don't color "message" or "messages" output */
3320 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3321 looking_at(buf, &i, "*. * at *:*: ") ||
3322 looking_at(buf, &i, "--* (*:*): ") ||
3323 /* Message notifications (same color as tells) */
3324 looking_at(buf, &i, "* has left a message ") ||
3325 looking_at(buf, &i, "* just sent you a message:\n") ||
3326 /* Whispers and kibitzes */
3327 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3328 looking_at(buf, &i, "* kibitzes: ") ||
3330 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3332 if (tkind == 1 && strchr(star_match[0], ':')) {
3333 /* Avoid "tells you:" spoofs in channels */
3336 if (star_match[0][0] == NULLCHAR ||
3337 strchr(star_match[0], ' ') ||
3338 (tkind == 3 && strchr(star_match[1], ' '))) {
3339 /* Reject bogus matches */
3342 if (appData.colorize) {
3343 if (oldi > next_out) {
3344 SendToPlayer(&buf[next_out], oldi - next_out);
3349 Colorize(ColorTell, FALSE);
3350 curColor = ColorTell;
3353 Colorize(ColorKibitz, FALSE);
3354 curColor = ColorKibitz;
3357 p = strrchr(star_match[1], '(');
3364 Colorize(ColorChannel1, FALSE);
3365 curColor = ColorChannel1;
3367 Colorize(ColorChannel, FALSE);
3368 curColor = ColorChannel;
3372 curColor = ColorNormal;
3376 if (started == STARTED_NONE && appData.autoComment &&
3377 (gameMode == IcsObserving ||
3378 gameMode == IcsPlayingWhite ||
3379 gameMode == IcsPlayingBlack)) {
3380 parse_pos = i - oldi;
3381 memcpy(parse, &buf[oldi], parse_pos);
3382 parse[parse_pos] = NULLCHAR;
3383 started = STARTED_COMMENT;
3384 savingComment = TRUE;
3386 started = STARTED_CHATTER;
3387 savingComment = FALSE;
3394 if (looking_at(buf, &i, "* s-shouts: ") ||
3395 looking_at(buf, &i, "* c-shouts: ")) {
3396 if (appData.colorize) {
3397 if (oldi > next_out) {
3398 SendToPlayer(&buf[next_out], oldi - next_out);
3401 Colorize(ColorSShout, FALSE);
3402 curColor = ColorSShout;
3405 started = STARTED_CHATTER;
3409 if (looking_at(buf, &i, "--->")) {
3414 if (looking_at(buf, &i, "* shouts: ") ||
3415 looking_at(buf, &i, "--> ")) {
3416 if (appData.colorize) {
3417 if (oldi > next_out) {
3418 SendToPlayer(&buf[next_out], oldi - next_out);
3421 Colorize(ColorShout, FALSE);
3422 curColor = ColorShout;
3425 started = STARTED_CHATTER;
3429 if (looking_at( buf, &i, "Challenge:")) {
3430 if (appData.colorize) {
3431 if (oldi > next_out) {
3432 SendToPlayer(&buf[next_out], oldi - next_out);
3435 Colorize(ColorChallenge, FALSE);
3436 curColor = ColorChallenge;
3442 if (looking_at(buf, &i, "* offers you") ||
3443 looking_at(buf, &i, "* offers to be") ||
3444 looking_at(buf, &i, "* would like to") ||
3445 looking_at(buf, &i, "* requests to") ||
3446 looking_at(buf, &i, "Your opponent offers") ||
3447 looking_at(buf, &i, "Your opponent requests")) {
3449 if (appData.colorize) {
3450 if (oldi > next_out) {
3451 SendToPlayer(&buf[next_out], oldi - next_out);
3454 Colorize(ColorRequest, FALSE);
3455 curColor = ColorRequest;
3460 if (looking_at(buf, &i, "* (*) seeking")) {
3461 if (appData.colorize) {
3462 if (oldi > next_out) {
3463 SendToPlayer(&buf[next_out], oldi - next_out);
3466 Colorize(ColorSeek, FALSE);
3467 curColor = ColorSeek;
3472 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3474 if (looking_at(buf, &i, "\\ ")) {
3475 if (prevColor != ColorNormal) {
3476 if (oldi > next_out) {
3477 SendToPlayer(&buf[next_out], oldi - next_out);
3480 Colorize(prevColor, TRUE);
3481 curColor = prevColor;
3483 if (savingComment) {
3484 parse_pos = i - oldi;
3485 memcpy(parse, &buf[oldi], parse_pos);
3486 parse[parse_pos] = NULLCHAR;
3487 started = STARTED_COMMENT;
3488 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3489 chattingPartner = savingComment - 3; // kludge to remember the box
3491 started = STARTED_CHATTER;
3496 if (looking_at(buf, &i, "Black Strength :") ||
3497 looking_at(buf, &i, "<<< style 10 board >>>") ||
3498 looking_at(buf, &i, "<10>") ||
3499 looking_at(buf, &i, "#@#")) {
3500 /* Wrong board style */
3502 SendToICS(ics_prefix);
3503 SendToICS("set style 12\n");
3504 SendToICS(ics_prefix);
3505 SendToICS("refresh\n");
3509 if (looking_at(buf, &i, "login:")) {
3510 if (!have_sent_ICS_logon) {
3512 have_sent_ICS_logon = 1;
3513 else // no init script was found
3514 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3515 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3516 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3521 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3522 (looking_at(buf, &i, "\n<12> ") ||
3523 looking_at(buf, &i, "<12> "))) {
3525 if (oldi > next_out) {
3526 SendToPlayer(&buf[next_out], oldi - next_out);
3529 started = STARTED_BOARD;
3534 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3535 looking_at(buf, &i, "<b1> ")) {
3536 if (oldi > next_out) {
3537 SendToPlayer(&buf[next_out], oldi - next_out);
3540 started = STARTED_HOLDINGS;
3545 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3547 /* Header for a move list -- first line */
3549 switch (ics_getting_history) {
3553 case BeginningOfGame:
3554 /* User typed "moves" or "oldmoves" while we
3555 were idle. Pretend we asked for these
3556 moves and soak them up so user can step
3557 through them and/or save them.
3560 gameMode = IcsObserving;
3563 ics_getting_history = H_GOT_UNREQ_HEADER;
3565 case EditGame: /*?*/
3566 case EditPosition: /*?*/
3567 /* Should above feature work in these modes too? */
3568 /* For now it doesn't */
3569 ics_getting_history = H_GOT_UNWANTED_HEADER;
3572 ics_getting_history = H_GOT_UNWANTED_HEADER;
3577 /* Is this the right one? */
3578 if (gameInfo.white && gameInfo.black &&
3579 strcmp(gameInfo.white, star_match[0]) == 0 &&
3580 strcmp(gameInfo.black, star_match[2]) == 0) {
3582 ics_getting_history = H_GOT_REQ_HEADER;
3585 case H_GOT_REQ_HEADER:
3586 case H_GOT_UNREQ_HEADER:
3587 case H_GOT_UNWANTED_HEADER:
3588 case H_GETTING_MOVES:
3589 /* Should not happen */
3590 DisplayError(_("Error gathering move list: two headers"), 0);
3591 ics_getting_history = H_FALSE;
3595 /* Save player ratings into gameInfo if needed */
3596 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3597 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3598 (gameInfo.whiteRating == -1 ||
3599 gameInfo.blackRating == -1)) {
3601 gameInfo.whiteRating = string_to_rating(star_match[1]);
3602 gameInfo.blackRating = string_to_rating(star_match[3]);
3603 if (appData.debugMode)
3604 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3605 gameInfo.whiteRating, gameInfo.blackRating);
3610 if (looking_at(buf, &i,
3611 "* * match, initial time: * minute*, increment: * second")) {
3612 /* Header for a move list -- second line */
3613 /* Initial board will follow if this is a wild game */
3614 if (gameInfo.event != NULL) free(gameInfo.event);
3615 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3616 gameInfo.event = StrSave(str);
3617 /* [HGM] we switched variant. Translate boards if needed. */
3618 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3622 if (looking_at(buf, &i, "Move ")) {
3623 /* Beginning of a move list */
3624 switch (ics_getting_history) {
3626 /* Normally should not happen */
3627 /* Maybe user hit reset while we were parsing */
3630 /* Happens if we are ignoring a move list that is not
3631 * the one we just requested. Common if the user
3632 * tries to observe two games without turning off
3635 case H_GETTING_MOVES:
3636 /* Should not happen */
3637 DisplayError(_("Error gathering move list: nested"), 0);
3638 ics_getting_history = H_FALSE;
3640 case H_GOT_REQ_HEADER:
3641 ics_getting_history = H_GETTING_MOVES;
3642 started = STARTED_MOVES;
3644 if (oldi > next_out) {
3645 SendToPlayer(&buf[next_out], oldi - next_out);
3648 case H_GOT_UNREQ_HEADER:
3649 ics_getting_history = H_GETTING_MOVES;
3650 started = STARTED_MOVES_NOHIDE;
3653 case H_GOT_UNWANTED_HEADER:
3654 ics_getting_history = H_FALSE;
3660 if (looking_at(buf, &i, "% ") ||
3661 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3662 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3663 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3664 soughtPending = FALSE;
3668 if(suppressKibitz) next_out = i;
3669 savingComment = FALSE;
3673 case STARTED_MOVES_NOHIDE:
3674 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3675 parse[parse_pos + i - oldi] = NULLCHAR;
3676 ParseGameHistory(parse);
3678 if (appData.zippyPlay && first.initDone) {
3679 FeedMovesToProgram(&first, forwardMostMove);
3680 if (gameMode == IcsPlayingWhite) {
3681 if (WhiteOnMove(forwardMostMove)) {
3682 if (first.sendTime) {
3683 if (first.useColors) {
3684 SendToProgram("black\n", &first);
3686 SendTimeRemaining(&first, TRUE);
3688 if (first.useColors) {
3689 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3691 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3692 first.maybeThinking = TRUE;
3694 if (first.usePlayother) {
3695 if (first.sendTime) {
3696 SendTimeRemaining(&first, TRUE);
3698 SendToProgram("playother\n", &first);
3704 } else if (gameMode == IcsPlayingBlack) {
3705 if (!WhiteOnMove(forwardMostMove)) {
3706 if (first.sendTime) {
3707 if (first.useColors) {
3708 SendToProgram("white\n", &first);
3710 SendTimeRemaining(&first, FALSE);
3712 if (first.useColors) {
3713 SendToProgram("black\n", &first);
3715 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3716 first.maybeThinking = TRUE;
3718 if (first.usePlayother) {
3719 if (first.sendTime) {
3720 SendTimeRemaining(&first, FALSE);
3722 SendToProgram("playother\n", &first);
3731 if (gameMode == IcsObserving && ics_gamenum == -1) {
3732 /* Moves came from oldmoves or moves command
3733 while we weren't doing anything else.
3735 currentMove = forwardMostMove;
3736 ClearHighlights();/*!!could figure this out*/
3737 flipView = appData.flipView;
3738 DrawPosition(TRUE, boards[currentMove]);
3739 DisplayBothClocks();
3740 snprintf(str, MSG_SIZ, "%s %s %s",
3741 gameInfo.white, _("vs."), gameInfo.black);
3745 /* Moves were history of an active game */
3746 if (gameInfo.resultDetails != NULL) {
3747 free(gameInfo.resultDetails);
3748 gameInfo.resultDetails = NULL;
3751 HistorySet(parseList, backwardMostMove,
3752 forwardMostMove, currentMove-1);
3753 DisplayMove(currentMove - 1);
3754 if (started == STARTED_MOVES) next_out = i;
3755 started = STARTED_NONE;
3756 ics_getting_history = H_FALSE;
3759 case STARTED_OBSERVE:
3760 started = STARTED_NONE;
3761 SendToICS(ics_prefix);
3762 SendToICS("refresh\n");
3768 if(bookHit) { // [HGM] book: simulate book reply
3769 static char bookMove[MSG_SIZ]; // a bit generous?
3771 programStats.nodes = programStats.depth = programStats.time =
3772 programStats.score = programStats.got_only_move = 0;
3773 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3775 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3776 strcat(bookMove, bookHit);
3777 HandleMachineMove(bookMove, &first);
3782 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3783 started == STARTED_HOLDINGS ||
3784 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3785 /* Accumulate characters in move list or board */
3786 parse[parse_pos++] = buf[i];
3789 /* Start of game messages. Mostly we detect start of game
3790 when the first board image arrives. On some versions
3791 of the ICS, though, we need to do a "refresh" after starting
3792 to observe in order to get the current board right away. */
3793 if (looking_at(buf, &i, "Adding game * to observation list")) {
3794 started = STARTED_OBSERVE;
3798 /* Handle auto-observe */
3799 if (appData.autoObserve &&
3800 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3801 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3803 /* Choose the player that was highlighted, if any. */
3804 if (star_match[0][0] == '\033' ||
3805 star_match[1][0] != '\033') {
3806 player = star_match[0];
3808 player = star_match[2];
3810 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3811 ics_prefix, StripHighlightAndTitle(player));
3814 /* Save ratings from notify string */
3815 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3816 player1Rating = string_to_rating(star_match[1]);
3817 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3818 player2Rating = string_to_rating(star_match[3]);
3820 if (appData.debugMode)
3822 "Ratings from 'Game notification:' %s %d, %s %d\n",
3823 player1Name, player1Rating,
3824 player2Name, player2Rating);
3829 /* Deal with automatic examine mode after a game,
3830 and with IcsObserving -> IcsExamining transition */
3831 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3832 looking_at(buf, &i, "has made you an examiner of game *")) {
3834 int gamenum = atoi(star_match[0]);
3835 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3836 gamenum == ics_gamenum) {
3837 /* We were already playing or observing this game;
3838 no need to refetch history */
3839 gameMode = IcsExamining;
3841 pauseExamForwardMostMove = forwardMostMove;
3842 } else if (currentMove < forwardMostMove) {
3843 ForwardInner(forwardMostMove);
3846 /* I don't think this case really can happen */
3847 SendToICS(ics_prefix);
3848 SendToICS("refresh\n");
3853 /* Error messages */
3854 // if (ics_user_moved) {
3855 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3856 if (looking_at(buf, &i, "Illegal move") ||
3857 looking_at(buf, &i, "Not a legal move") ||
3858 looking_at(buf, &i, "Your king is in check") ||
3859 looking_at(buf, &i, "It isn't your turn") ||
3860 looking_at(buf, &i, "It is not your move")) {
3862 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3863 currentMove = forwardMostMove-1;
3864 DisplayMove(currentMove - 1); /* before DMError */
3865 DrawPosition(FALSE, boards[currentMove]);
3866 SwitchClocks(forwardMostMove-1); // [HGM] race
3867 DisplayBothClocks();
3869 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3875 if (looking_at(buf, &i, "still have time") ||
3876 looking_at(buf, &i, "not out of time") ||
3877 looking_at(buf, &i, "either player is out of time") ||
3878 looking_at(buf, &i, "has timeseal; checking")) {
3879 /* We must have called his flag a little too soon */
3880 whiteFlag = blackFlag = FALSE;
3884 if (looking_at(buf, &i, "added * seconds to") ||
3885 looking_at(buf, &i, "seconds were added to")) {
3886 /* Update the clocks */
3887 SendToICS(ics_prefix);
3888 SendToICS("refresh\n");
3892 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3893 ics_clock_paused = TRUE;
3898 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3899 ics_clock_paused = FALSE;
3904 /* Grab player ratings from the Creating: message.
3905 Note we have to check for the special case when
3906 the ICS inserts things like [white] or [black]. */
3907 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3908 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3910 0 player 1 name (not necessarily white)
3912 2 empty, white, or black (IGNORED)
3913 3 player 2 name (not necessarily black)
3916 The names/ratings are sorted out when the game
3917 actually starts (below).
3919 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3920 player1Rating = string_to_rating(star_match[1]);
3921 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3922 player2Rating = string_to_rating(star_match[4]);
3924 if (appData.debugMode)
3926 "Ratings from 'Creating:' %s %d, %s %d\n",
3927 player1Name, player1Rating,
3928 player2Name, player2Rating);
3933 /* Improved generic start/end-of-game messages */
3934 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3935 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3936 /* If tkind == 0: */
3937 /* star_match[0] is the game number */
3938 /* [1] is the white player's name */
3939 /* [2] is the black player's name */
3940 /* For end-of-game: */
3941 /* [3] is the reason for the game end */
3942 /* [4] is a PGN end game-token, preceded by " " */
3943 /* For start-of-game: */
3944 /* [3] begins with "Creating" or "Continuing" */
3945 /* [4] is " *" or empty (don't care). */
3946 int gamenum = atoi(star_match[0]);
3947 char *whitename, *blackname, *why, *endtoken;
3948 ChessMove endtype = EndOfFile;
3951 whitename = star_match[1];
3952 blackname = star_match[2];
3953 why = star_match[3];
3954 endtoken = star_match[4];
3956 whitename = star_match[1];
3957 blackname = star_match[3];
3958 why = star_match[5];
3959 endtoken = star_match[6];
3962 /* Game start messages */
3963 if (strncmp(why, "Creating ", 9) == 0 ||
3964 strncmp(why, "Continuing ", 11) == 0) {
3965 gs_gamenum = gamenum;
3966 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3967 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3968 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3970 if (appData.zippyPlay) {
3971 ZippyGameStart(whitename, blackname);
3974 partnerBoardValid = FALSE; // [HGM] bughouse
3978 /* Game end messages */
3979 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3980 ics_gamenum != gamenum) {
3983 while (endtoken[0] == ' ') endtoken++;
3984 switch (endtoken[0]) {
3987 endtype = GameUnfinished;
3990 endtype = BlackWins;
3993 if (endtoken[1] == '/')
3994 endtype = GameIsDrawn;
3996 endtype = WhiteWins;
3999 GameEnds(endtype, why, GE_ICS);
4001 if (appData.zippyPlay && first.initDone) {
4002 ZippyGameEnd(endtype, why);
4003 if (first.pr == NoProc) {
4004 /* Start the next process early so that we'll
4005 be ready for the next challenge */
4006 StartChessProgram(&first);
4008 /* Send "new" early, in case this command takes
4009 a long time to finish, so that we'll be ready
4010 for the next challenge. */
4011 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4015 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4019 if (looking_at(buf, &i, "Removing game * from observation") ||
4020 looking_at(buf, &i, "no longer observing game *") ||
4021 looking_at(buf, &i, "Game * (*) has no examiners")) {
4022 if (gameMode == IcsObserving &&
4023 atoi(star_match[0]) == ics_gamenum)
4025 /* icsEngineAnalyze */
4026 if (appData.icsEngineAnalyze) {
4033 ics_user_moved = FALSE;
4038 if (looking_at(buf, &i, "no longer examining game *")) {
4039 if (gameMode == IcsExamining &&
4040 atoi(star_match[0]) == ics_gamenum)
4044 ics_user_moved = FALSE;
4049 /* Advance leftover_start past any newlines we find,
4050 so only partial lines can get reparsed */
4051 if (looking_at(buf, &i, "\n")) {
4052 prevColor = curColor;
4053 if (curColor != ColorNormal) {
4054 if (oldi > next_out) {
4055 SendToPlayer(&buf[next_out], oldi - next_out);
4058 Colorize(ColorNormal, FALSE);
4059 curColor = ColorNormal;
4061 if (started == STARTED_BOARD) {
4062 started = STARTED_NONE;
4063 parse[parse_pos] = NULLCHAR;
4064 ParseBoard12(parse);
4067 /* Send premove here */
4068 if (appData.premove) {
4070 if (currentMove == 0 &&
4071 gameMode == IcsPlayingWhite &&
4072 appData.premoveWhite) {
4073 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4074 if (appData.debugMode)
4075 fprintf(debugFP, "Sending premove:\n");
4077 } else if (currentMove == 1 &&
4078 gameMode == IcsPlayingBlack &&
4079 appData.premoveBlack) {
4080 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4081 if (appData.debugMode)
4082 fprintf(debugFP, "Sending premove:\n");
4084 } else if (gotPremove) {
4086 ClearPremoveHighlights();
4087 if (appData.debugMode)
4088 fprintf(debugFP, "Sending premove:\n");
4089 UserMoveEvent(premoveFromX, premoveFromY,
4090 premoveToX, premoveToY,
4095 /* Usually suppress following prompt */
4096 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4097 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4098 if (looking_at(buf, &i, "*% ")) {
4099 savingComment = FALSE;
4104 } else if (started == STARTED_HOLDINGS) {
4106 char new_piece[MSG_SIZ];
4107 started = STARTED_NONE;
4108 parse[parse_pos] = NULLCHAR;
4109 if (appData.debugMode)
4110 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4111 parse, currentMove);
4112 if (sscanf(parse, " game %d", &gamenum) == 1) {
4113 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4114 if (gameInfo.variant == VariantNormal) {
4115 /* [HGM] We seem to switch variant during a game!
4116 * Presumably no holdings were displayed, so we have
4117 * to move the position two files to the right to
4118 * create room for them!
4120 VariantClass newVariant;
4121 switch(gameInfo.boardWidth) { // base guess on board width
4122 case 9: newVariant = VariantShogi; break;
4123 case 10: newVariant = VariantGreat; break;
4124 default: newVariant = VariantCrazyhouse; break;
4126 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4127 /* Get a move list just to see the header, which
4128 will tell us whether this is really bug or zh */
4129 if (ics_getting_history == H_FALSE) {
4130 ics_getting_history = H_REQUESTED;
4131 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4135 new_piece[0] = NULLCHAR;
4136 sscanf(parse, "game %d white [%s black [%s <- %s",
4137 &gamenum, white_holding, black_holding,
4139 white_holding[strlen(white_holding)-1] = NULLCHAR;
4140 black_holding[strlen(black_holding)-1] = NULLCHAR;
4141 /* [HGM] copy holdings to board holdings area */
4142 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4143 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4144 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4146 if (appData.zippyPlay && first.initDone) {
4147 ZippyHoldings(white_holding, black_holding,
4151 if (tinyLayout || smallLayout) {
4152 char wh[16], bh[16];
4153 PackHolding(wh, white_holding);
4154 PackHolding(bh, black_holding);
4155 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4156 gameInfo.white, gameInfo.black);
4158 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4159 gameInfo.white, white_holding, _("vs."),
4160 gameInfo.black, black_holding);
4162 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4163 DrawPosition(FALSE, boards[currentMove]);
4165 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4166 sscanf(parse, "game %d white [%s black [%s <- %s",
4167 &gamenum, white_holding, black_holding,
4169 white_holding[strlen(white_holding)-1] = NULLCHAR;
4170 black_holding[strlen(black_holding)-1] = NULLCHAR;
4171 /* [HGM] copy holdings to partner-board holdings area */
4172 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4173 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4174 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4175 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4176 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4179 /* Suppress following prompt */
4180 if (looking_at(buf, &i, "*% ")) {
4181 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4182 savingComment = FALSE;
4190 i++; /* skip unparsed character and loop back */
4193 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4194 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4195 // SendToPlayer(&buf[next_out], i - next_out);
4196 started != STARTED_HOLDINGS && leftover_start > next_out) {
4197 SendToPlayer(&buf[next_out], leftover_start - next_out);
4201 leftover_len = buf_len - leftover_start;
4202 /* if buffer ends with something we couldn't parse,
4203 reparse it after appending the next read */
4205 } else if (count == 0) {
4206 RemoveInputSource(isr);
4207 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4209 DisplayFatalError(_("Error reading from ICS"), error, 1);
4214 /* Board style 12 looks like this:
4216 <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
4218 * The "<12> " is stripped before it gets to this routine. The two
4219 * trailing 0's (flip state and clock ticking) are later addition, and
4220 * some chess servers may not have them, or may have only the first.
4221 * Additional trailing fields may be added in the future.
4224 #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"
4226 #define RELATION_OBSERVING_PLAYED 0
4227 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4228 #define RELATION_PLAYING_MYMOVE 1
4229 #define RELATION_PLAYING_NOTMYMOVE -1
4230 #define RELATION_EXAMINING 2
4231 #define RELATION_ISOLATED_BOARD -3
4232 #define RELATION_STARTING_POSITION -4 /* FICS only */
4235 ParseBoard12 (char *string)
4239 char *bookHit = NULL; // [HGM] book
4241 GameMode newGameMode;
4242 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4243 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4244 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4245 char to_play, board_chars[200];
4246 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4247 char black[32], white[32];
4249 int prevMove = currentMove;
4252 int fromX, fromY, toX, toY;
4254 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4255 Boolean weird = FALSE, reqFlag = FALSE;
4257 fromX = fromY = toX = toY = -1;
4261 if (appData.debugMode)
4262 fprintf(debugFP, "Parsing board: %s\n", string);
4264 move_str[0] = NULLCHAR;
4265 elapsed_time[0] = NULLCHAR;
4266 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4268 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4269 if(string[i] == ' ') { ranks++; files = 0; }
4271 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4274 for(j = 0; j <i; j++) board_chars[j] = string[j];
4275 board_chars[i] = '\0';
4278 n = sscanf(string, PATTERN, &to_play, &double_push,
4279 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4280 &gamenum, white, black, &relation, &basetime, &increment,
4281 &white_stren, &black_stren, &white_time, &black_time,
4282 &moveNum, str, elapsed_time, move_str, &ics_flip,
4286 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4287 DisplayError(str, 0);
4291 /* Convert the move number to internal form */
4292 moveNum = (moveNum - 1) * 2;
4293 if (to_play == 'B') moveNum++;
4294 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4295 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4301 case RELATION_OBSERVING_PLAYED:
4302 case RELATION_OBSERVING_STATIC:
4303 if (gamenum == -1) {
4304 /* Old ICC buglet */
4305 relation = RELATION_OBSERVING_STATIC;
4307 newGameMode = IcsObserving;
4309 case RELATION_PLAYING_MYMOVE:
4310 case RELATION_PLAYING_NOTMYMOVE:
4312 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4313 IcsPlayingWhite : IcsPlayingBlack;
4314 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4316 case RELATION_EXAMINING:
4317 newGameMode = IcsExamining;
4319 case RELATION_ISOLATED_BOARD:
4321 /* Just display this board. If user was doing something else,
4322 we will forget about it until the next board comes. */
4323 newGameMode = IcsIdle;
4325 case RELATION_STARTING_POSITION:
4326 newGameMode = gameMode;
4330 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4331 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4332 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4333 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4334 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4335 static int lastBgGame = -1;
4337 for (k = 0; k < ranks; k++) {
4338 for (j = 0; j < files; j++)
4339 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4340 if(gameInfo.holdingsWidth > 1) {
4341 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4342 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4345 CopyBoard(partnerBoard, board);
4346 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4347 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4348 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4349 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4350 if(toSqr = strchr(str, '-')) {
4351 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4352 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4353 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4354 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4355 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4356 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4358 DisplayWhiteClock(white_time*fac, to_play == 'W');
4359 DisplayBlackClock(black_time*fac, to_play != 'W');
4360 activePartner = to_play;
4361 if(gamenum != lastBgGame) {
4363 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4366 lastBgGame = gamenum;
4367 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4368 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4369 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4370 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4371 if(!twoBoards) DisplayMessage(partnerStatus, "");
4372 partnerBoardValid = TRUE;
4376 if(appData.dualBoard && appData.bgObserve) {
4377 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4378 SendToICS(ics_prefix), SendToICS("pobserve\n");
4379 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4381 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4386 /* Modify behavior for initial board display on move listing
4389 switch (ics_getting_history) {
4393 case H_GOT_REQ_HEADER:
4394 case H_GOT_UNREQ_HEADER:
4395 /* This is the initial position of the current game */
4396 gamenum = ics_gamenum;
4397 moveNum = 0; /* old ICS bug workaround */
4398 if (to_play == 'B') {
4399 startedFromSetupPosition = TRUE;
4400 blackPlaysFirst = TRUE;
4402 if (forwardMostMove == 0) forwardMostMove = 1;
4403 if (backwardMostMove == 0) backwardMostMove = 1;
4404 if (currentMove == 0) currentMove = 1;
4406 newGameMode = gameMode;
4407 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4409 case H_GOT_UNWANTED_HEADER:
4410 /* This is an initial board that we don't want */
4412 case H_GETTING_MOVES:
4413 /* Should not happen */
4414 DisplayError(_("Error gathering move list: extra board"), 0);
4415 ics_getting_history = H_FALSE;
4419 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4420 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4421 weird && (int)gameInfo.variant < (int)VariantShogi) {
4422 /* [HGM] We seem to have switched variant unexpectedly
4423 * Try to guess new variant from board size
4425 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4426 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4427 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4428 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4429 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4430 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4431 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4432 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4433 /* Get a move list just to see the header, which
4434 will tell us whether this is really bug or zh */
4435 if (ics_getting_history == H_FALSE) {
4436 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4437 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4442 /* Take action if this is the first board of a new game, or of a
4443 different game than is currently being displayed. */
4444 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4445 relation == RELATION_ISOLATED_BOARD) {
4447 /* Forget the old game and get the history (if any) of the new one */
4448 if (gameMode != BeginningOfGame) {
4452 if (appData.autoRaiseBoard) BoardToTop();
4454 if (gamenum == -1) {
4455 newGameMode = IcsIdle;
4456 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4457 appData.getMoveList && !reqFlag) {
4458 /* Need to get game history */
4459 ics_getting_history = H_REQUESTED;
4460 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4464 /* Initially flip the board to have black on the bottom if playing
4465 black or if the ICS flip flag is set, but let the user change
4466 it with the Flip View button. */
4467 flipView = appData.autoFlipView ?
4468 (newGameMode == IcsPlayingBlack) || ics_flip :
4471 /* Done with values from previous mode; copy in new ones */
4472 gameMode = newGameMode;
4474 ics_gamenum = gamenum;
4475 if (gamenum == gs_gamenum) {
4476 int klen = strlen(gs_kind);
4477 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4478 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4479 gameInfo.event = StrSave(str);
4481 gameInfo.event = StrSave("ICS game");
4483 gameInfo.site = StrSave(appData.icsHost);
4484 gameInfo.date = PGNDate();
4485 gameInfo.round = StrSave("-");
4486 gameInfo.white = StrSave(white);
4487 gameInfo.black = StrSave(black);
4488 timeControl = basetime * 60 * 1000;
4490 timeIncrement = increment * 1000;
4491 movesPerSession = 0;
4492 gameInfo.timeControl = TimeControlTagValue();
4493 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4494 if (appData.debugMode) {
4495 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4496 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4497 setbuf(debugFP, NULL);
4500 gameInfo.outOfBook = NULL;
4502 /* Do we have the ratings? */
4503 if (strcmp(player1Name, white) == 0 &&
4504 strcmp(player2Name, black) == 0) {
4505 if (appData.debugMode)
4506 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4507 player1Rating, player2Rating);
4508 gameInfo.whiteRating = player1Rating;
4509 gameInfo.blackRating = player2Rating;
4510 } else if (strcmp(player2Name, white) == 0 &&
4511 strcmp(player1Name, black) == 0) {
4512 if (appData.debugMode)
4513 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4514 player2Rating, player1Rating);
4515 gameInfo.whiteRating = player2Rating;
4516 gameInfo.blackRating = player1Rating;
4518 player1Name[0] = player2Name[0] = NULLCHAR;
4520 /* Silence shouts if requested */
4521 if (appData.quietPlay &&
4522 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4523 SendToICS(ics_prefix);
4524 SendToICS("set shout 0\n");
4528 /* Deal with midgame name changes */
4530 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4531 if (gameInfo.white) free(gameInfo.white);
4532 gameInfo.white = StrSave(white);
4534 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4535 if (gameInfo.black) free(gameInfo.black);
4536 gameInfo.black = StrSave(black);
4540 /* Throw away game result if anything actually changes in examine mode */
4541 if (gameMode == IcsExamining && !newGame) {
4542 gameInfo.result = GameUnfinished;
4543 if (gameInfo.resultDetails != NULL) {
4544 free(gameInfo.resultDetails);
4545 gameInfo.resultDetails = NULL;
4549 /* In pausing && IcsExamining mode, we ignore boards coming
4550 in if they are in a different variation than we are. */
4551 if (pauseExamInvalid) return;
4552 if (pausing && gameMode == IcsExamining) {
4553 if (moveNum <= pauseExamForwardMostMove) {
4554 pauseExamInvalid = TRUE;
4555 forwardMostMove = pauseExamForwardMostMove;
4560 if (appData.debugMode) {
4561 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4563 /* Parse the board */
4564 for (k = 0; k < ranks; k++) {
4565 for (j = 0; j < files; j++)
4566 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4567 if(gameInfo.holdingsWidth > 1) {
4568 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4569 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4572 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4573 board[5][BOARD_RGHT+1] = WhiteAngel;
4574 board[6][BOARD_RGHT+1] = WhiteMarshall;
4575 board[1][0] = BlackMarshall;
4576 board[2][0] = BlackAngel;
4577 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4579 CopyBoard(boards[moveNum], board);
4580 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4582 startedFromSetupPosition =
4583 !CompareBoards(board, initialPosition);
4584 if(startedFromSetupPosition)
4585 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4588 /* [HGM] Set castling rights. Take the outermost Rooks,
4589 to make it also work for FRC opening positions. Note that board12
4590 is really defective for later FRC positions, as it has no way to
4591 indicate which Rook can castle if they are on the same side of King.
4592 For the initial position we grant rights to the outermost Rooks,
4593 and remember thos rights, and we then copy them on positions
4594 later in an FRC game. This means WB might not recognize castlings with
4595 Rooks that have moved back to their original position as illegal,
4596 but in ICS mode that is not its job anyway.
4598 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4599 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4601 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4602 if(board[0][i] == WhiteRook) j = i;
4603 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4604 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4605 if(board[0][i] == WhiteRook) j = i;
4606 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4607 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4608 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4609 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4610 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4611 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4612 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4614 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4615 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4616 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4617 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4618 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4619 if(board[BOARD_HEIGHT-1][k] == bKing)
4620 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4621 if(gameInfo.variant == VariantTwoKings) {
4622 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4623 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4624 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4627 r = boards[moveNum][CASTLING][0] = initialRights[0];
4628 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4629 r = boards[moveNum][CASTLING][1] = initialRights[1];
4630 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4631 r = boards[moveNum][CASTLING][3] = initialRights[3];
4632 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4633 r = boards[moveNum][CASTLING][4] = initialRights[4];
4634 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4635 /* wildcastle kludge: always assume King has rights */
4636 r = boards[moveNum][CASTLING][2] = initialRights[2];
4637 r = boards[moveNum][CASTLING][5] = initialRights[5];
4639 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4640 boards[moveNum][EP_STATUS] = EP_NONE;
4641 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4642 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4643 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4646 if (ics_getting_history == H_GOT_REQ_HEADER ||
4647 ics_getting_history == H_GOT_UNREQ_HEADER) {
4648 /* This was an initial position from a move list, not
4649 the current position */
4653 /* Update currentMove and known move number limits */
4654 newMove = newGame || moveNum > forwardMostMove;
4657 forwardMostMove = backwardMostMove = currentMove = moveNum;
4658 if (gameMode == IcsExamining && moveNum == 0) {
4659 /* Workaround for ICS limitation: we are not told the wild
4660 type when starting to examine a game. But if we ask for
4661 the move list, the move list header will tell us */
4662 ics_getting_history = H_REQUESTED;
4663 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4666 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4667 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4669 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4670 /* [HGM] applied this also to an engine that is silently watching */
4671 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4672 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4673 gameInfo.variant == currentlyInitializedVariant) {
4674 takeback = forwardMostMove - moveNum;
4675 for (i = 0; i < takeback; i++) {
4676 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4677 SendToProgram("undo\n", &first);
4682 forwardMostMove = moveNum;
4683 if (!pausing || currentMove > forwardMostMove)
4684 currentMove = forwardMostMove;
4686 /* New part of history that is not contiguous with old part */
4687 if (pausing && gameMode == IcsExamining) {
4688 pauseExamInvalid = TRUE;
4689 forwardMostMove = pauseExamForwardMostMove;
4692 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4694 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4695 // [HGM] when we will receive the move list we now request, it will be
4696 // fed to the engine from the first move on. So if the engine is not
4697 // in the initial position now, bring it there.
4698 InitChessProgram(&first, 0);
4701 ics_getting_history = H_REQUESTED;
4702 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4705 forwardMostMove = backwardMostMove = currentMove = moveNum;
4708 /* Update the clocks */
4709 if (strchr(elapsed_time, '.')) {
4711 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4712 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4714 /* Time is in seconds */
4715 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4716 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4721 if (appData.zippyPlay && newGame &&
4722 gameMode != IcsObserving && gameMode != IcsIdle &&
4723 gameMode != IcsExamining)
4724 ZippyFirstBoard(moveNum, basetime, increment);
4727 /* Put the move on the move list, first converting
4728 to canonical algebraic form. */
4730 if (appData.debugMode) {
4731 int f = forwardMostMove;
4732 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4733 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4734 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4735 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4736 fprintf(debugFP, "moveNum = %d\n", moveNum);
4737 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4738 setbuf(debugFP, NULL);
4740 if (moveNum <= backwardMostMove) {
4741 /* We don't know what the board looked like before
4743 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4744 strcat(parseList[moveNum - 1], " ");
4745 strcat(parseList[moveNum - 1], elapsed_time);
4746 moveList[moveNum - 1][0] = NULLCHAR;
4747 } else if (strcmp(move_str, "none") == 0) {
4748 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4749 /* Again, we don't know what the board looked like;
4750 this is really the start of the game. */
4751 parseList[moveNum - 1][0] = NULLCHAR;
4752 moveList[moveNum - 1][0] = NULLCHAR;
4753 backwardMostMove = moveNum;
4754 startedFromSetupPosition = TRUE;
4755 fromX = fromY = toX = toY = -1;
4757 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4758 // So we parse the long-algebraic move string in stead of the SAN move
4759 int valid; char buf[MSG_SIZ], *prom;
4761 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4762 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4763 // str looks something like "Q/a1-a2"; kill the slash
4765 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4766 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4767 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4768 strcat(buf, prom); // long move lacks promo specification!
4769 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4770 if(appData.debugMode)
4771 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4772 safeStrCpy(move_str, buf, MSG_SIZ);
4774 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4775 &fromX, &fromY, &toX, &toY, &promoChar)
4776 || ParseOneMove(buf, moveNum - 1, &moveType,
4777 &fromX, &fromY, &toX, &toY, &promoChar);
4778 // end of long SAN patch
4780 (void) CoordsToAlgebraic(boards[moveNum - 1],
4781 PosFlags(moveNum - 1),
4782 fromY, fromX, toY, toX, promoChar,
4783 parseList[moveNum-1]);
4784 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4790 if(gameInfo.variant != VariantShogi)
4791 strcat(parseList[moveNum - 1], "+");
4794 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4795 strcat(parseList[moveNum - 1], "#");
4798 strcat(parseList[moveNum - 1], " ");
4799 strcat(parseList[moveNum - 1], elapsed_time);
4800 /* currentMoveString is set as a side-effect of ParseOneMove */
4801 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4802 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4803 strcat(moveList[moveNum - 1], "\n");
4805 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4806 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4807 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4808 ChessSquare old, new = boards[moveNum][k][j];
4809 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4810 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4811 if(old == new) continue;
4812 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4813 else if(new == WhiteWazir || new == BlackWazir) {
4814 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4815 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4816 else boards[moveNum][k][j] = old; // preserve type of Gold
4817 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4818 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4821 /* Move from ICS was illegal!? Punt. */
4822 if (appData.debugMode) {
4823 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4824 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4826 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4827 strcat(parseList[moveNum - 1], " ");
4828 strcat(parseList[moveNum - 1], elapsed_time);
4829 moveList[moveNum - 1][0] = NULLCHAR;
4830 fromX = fromY = toX = toY = -1;
4833 if (appData.debugMode) {
4834 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4835 setbuf(debugFP, NULL);
4839 /* Send move to chess program (BEFORE animating it). */
4840 if (appData.zippyPlay && !newGame && newMove &&
4841 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4843 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4844 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4845 if (moveList[moveNum - 1][0] == NULLCHAR) {
4846 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4848 DisplayError(str, 0);
4850 if (first.sendTime) {
4851 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4853 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4854 if (firstMove && !bookHit) {
4856 if (first.useColors) {
4857 SendToProgram(gameMode == IcsPlayingWhite ?
4859 "black\ngo\n", &first);
4861 SendToProgram("go\n", &first);
4863 first.maybeThinking = TRUE;
4866 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4867 if (moveList[moveNum - 1][0] == NULLCHAR) {
4868 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4869 DisplayError(str, 0);
4871 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4872 SendMoveToProgram(moveNum - 1, &first);
4879 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4880 /* If move comes from a remote source, animate it. If it
4881 isn't remote, it will have already been animated. */
4882 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4883 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4885 if (!pausing && appData.highlightLastMove) {
4886 SetHighlights(fromX, fromY, toX, toY);
4890 /* Start the clocks */
4891 whiteFlag = blackFlag = FALSE;
4892 appData.clockMode = !(basetime == 0 && increment == 0);
4894 ics_clock_paused = TRUE;
4896 } else if (ticking == 1) {
4897 ics_clock_paused = FALSE;
4899 if (gameMode == IcsIdle ||
4900 relation == RELATION_OBSERVING_STATIC ||
4901 relation == RELATION_EXAMINING ||
4903 DisplayBothClocks();
4907 /* Display opponents and material strengths */
4908 if (gameInfo.variant != VariantBughouse &&
4909 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4910 if (tinyLayout || smallLayout) {
4911 if(gameInfo.variant == VariantNormal)
4912 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4913 gameInfo.white, white_stren, gameInfo.black, black_stren,
4914 basetime, increment);
4916 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4917 gameInfo.white, white_stren, gameInfo.black, black_stren,
4918 basetime, increment, (int) gameInfo.variant);
4920 if(gameInfo.variant == VariantNormal)
4921 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4922 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4923 basetime, increment);
4925 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4926 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4927 basetime, increment, VariantName(gameInfo.variant));
4930 if (appData.debugMode) {
4931 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4936 /* Display the board */
4937 if (!pausing && !appData.noGUI) {
4939 if (appData.premove)
4941 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4942 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4943 ClearPremoveHighlights();
4945 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4946 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4947 DrawPosition(j, boards[currentMove]);
4949 DisplayMove(moveNum - 1);
4950 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4951 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4952 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4953 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4957 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4959 if(bookHit) { // [HGM] book: simulate book reply
4960 static char bookMove[MSG_SIZ]; // a bit generous?
4962 programStats.nodes = programStats.depth = programStats.time =
4963 programStats.score = programStats.got_only_move = 0;
4964 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4966 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4967 strcat(bookMove, bookHit);
4968 HandleMachineMove(bookMove, &first);
4977 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4978 ics_getting_history = H_REQUESTED;
4979 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4985 SendToBoth (char *msg)
4986 { // to make it easy to keep two engines in step in dual analysis
4987 SendToProgram(msg, &first);
4988 if(second.analyzing) SendToProgram(msg, &second);
4992 AnalysisPeriodicEvent (int force)
4994 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4995 && !force) || !appData.periodicUpdates)
4998 /* Send . command to Crafty to collect stats */
5001 /* Don't send another until we get a response (this makes
5002 us stop sending to old Crafty's which don't understand
5003 the "." command (sending illegal cmds resets node count & time,
5004 which looks bad)) */
5005 programStats.ok_to_send = 0;
5009 ics_update_width (int new_width)
5011 ics_printf("set width %d\n", new_width);
5015 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5019 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5020 if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChu) {
5021 sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5022 SendToProgram(buf, cps);
5025 // null move in variant where engine does not understand it (for analysis purposes)
5026 SendBoard(cps, moveNum + 1); // send position after move in stead.
5029 if (cps->useUsermove) {
5030 SendToProgram("usermove ", cps);
5034 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5035 int len = space - parseList[moveNum];
5036 memcpy(buf, parseList[moveNum], len);
5038 buf[len] = NULLCHAR;
5040 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5042 SendToProgram(buf, cps);
5044 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5045 AlphaRank(moveList[moveNum], 4);
5046 SendToProgram(moveList[moveNum], cps);
5047 AlphaRank(moveList[moveNum], 4); // and back
5049 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5050 * the engine. It would be nice to have a better way to identify castle
5052 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5053 && cps->useOOCastle) {
5054 int fromX = moveList[moveNum][0] - AAA;
5055 int fromY = moveList[moveNum][1] - ONE;
5056 int toX = moveList[moveNum][2] - AAA;
5057 int toY = moveList[moveNum][3] - ONE;
5058 if((boards[moveNum][fromY][fromX] == WhiteKing
5059 && boards[moveNum][toY][toX] == WhiteRook)
5060 || (boards[moveNum][fromY][fromX] == BlackKing
5061 && boards[moveNum][toY][toX] == BlackRook)) {
5062 if(toX > fromX) SendToProgram("O-O\n", cps);
5063 else SendToProgram("O-O-O\n", cps);
5065 else SendToProgram(moveList[moveNum], cps);
5067 if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5068 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5069 moveList[moveNum][5], moveList[moveNum][6] - '0',
5070 moveList[moveNum][5], moveList[moveNum][6] - '0',
5071 moveList[moveNum][2], moveList[moveNum][3] - '0');
5072 SendToProgram(buf, cps);
5074 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5075 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5076 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5077 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5078 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5080 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5081 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5082 SendToProgram(buf, cps);
5084 else SendToProgram(moveList[moveNum], cps);
5085 /* End of additions by Tord */
5088 /* [HGM] setting up the opening has brought engine in force mode! */
5089 /* Send 'go' if we are in a mode where machine should play. */
5090 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5091 (gameMode == TwoMachinesPlay ||
5093 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5095 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5096 SendToProgram("go\n", cps);
5097 if (appData.debugMode) {
5098 fprintf(debugFP, "(extra)\n");
5101 setboardSpoiledMachineBlack = 0;
5105 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5107 char user_move[MSG_SIZ];
5110 if(gameInfo.variant == VariantSChess && promoChar) {
5111 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5112 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5113 } else suffix[0] = NULLCHAR;
5117 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5118 (int)moveType, fromX, fromY, toX, toY);
5119 DisplayError(user_move + strlen("say "), 0);
5121 case WhiteKingSideCastle:
5122 case BlackKingSideCastle:
5123 case WhiteQueenSideCastleWild:
5124 case BlackQueenSideCastleWild:
5126 case WhiteHSideCastleFR:
5127 case BlackHSideCastleFR:
5129 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5131 case WhiteQueenSideCastle:
5132 case BlackQueenSideCastle:
5133 case WhiteKingSideCastleWild:
5134 case BlackKingSideCastleWild:
5136 case WhiteASideCastleFR:
5137 case BlackASideCastleFR:
5139 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5141 case WhiteNonPromotion:
5142 case BlackNonPromotion:
5143 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5145 case WhitePromotion:
5146 case BlackPromotion:
5147 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5148 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5149 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5150 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5151 PieceToChar(WhiteFerz));
5152 else if(gameInfo.variant == VariantGreat)
5153 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5154 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5155 PieceToChar(WhiteMan));
5157 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5158 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5164 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5165 ToUpper(PieceToChar((ChessSquare) fromX)),
5166 AAA + toX, ONE + toY);
5168 case IllegalMove: /* could be a variant we don't quite understand */
5169 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5171 case WhiteCapturesEnPassant:
5172 case BlackCapturesEnPassant:
5173 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5174 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5177 SendToICS(user_move);
5178 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5179 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5184 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5185 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5186 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5187 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5188 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5191 if(gameMode != IcsExamining) { // is this ever not the case?
5192 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5194 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5195 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5196 } else { // on FICS we must first go to general examine mode
5197 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5199 if(gameInfo.variant != VariantNormal) {
5200 // try figure out wild number, as xboard names are not always valid on ICS
5201 for(i=1; i<=36; i++) {
5202 snprintf(buf, MSG_SIZ, "wild/%d", i);
5203 if(StringToVariant(buf) == gameInfo.variant) break;
5205 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5206 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5207 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5208 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5209 SendToICS(ics_prefix);
5211 if(startedFromSetupPosition || backwardMostMove != 0) {
5212 fen = PositionToFEN(backwardMostMove, NULL, 1);
5213 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5214 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5216 } else { // FICS: everything has to set by separate bsetup commands
5217 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5218 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5220 if(!WhiteOnMove(backwardMostMove)) {
5221 SendToICS("bsetup tomove black\n");
5223 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5224 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5226 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5227 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5229 i = boards[backwardMostMove][EP_STATUS];
5230 if(i >= 0) { // set e.p.
5231 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5237 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5238 SendToICS("bsetup done\n"); // switch to normal examining.
5240 for(i = backwardMostMove; i<last; i++) {
5242 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5243 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5244 int len = strlen(moveList[i]);
5245 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5246 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5250 SendToICS(ics_prefix);
5251 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5254 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5257 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5259 if (rf == DROP_RANK) {
5260 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5261 sprintf(move, "%c@%c%c\n",
5262 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5264 if (promoChar == 'x' || promoChar == NULLCHAR) {
5265 sprintf(move, "%c%c%c%c\n",
5266 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5267 if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5269 sprintf(move, "%c%c%c%c%c\n",
5270 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5276 ProcessICSInitScript (FILE *f)
5280 while (fgets(buf, MSG_SIZ, f)) {
5281 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5288 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5290 static ClickType lastClickType;
5295 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5296 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5297 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5298 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5299 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5300 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5303 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5304 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5305 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5306 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5307 if(!step) step = -1;
5308 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5309 appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion) ||
5310 IS_SHOGI(gameInfo.variant) && promoSweep != CHUPROMOTED last && last != CHUPROMOTED promoSweep && last != promoSweep);
5312 int victim = boards[currentMove][toY][toX];
5313 boards[currentMove][toY][toX] = promoSweep;
5314 DrawPosition(FALSE, boards[currentMove]);
5315 boards[currentMove][toY][toX] = victim;
5317 ChangeDragPiece(promoSweep);
5321 PromoScroll (int x, int y)
5325 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5326 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5327 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5328 if(!step) return FALSE;
5329 lastX = x; lastY = y;
5330 if((promoSweep < BlackPawn) == flipView) step = -step;
5331 if(step > 0) selectFlag = 1;
5332 if(!selectFlag) Sweep(step);
5337 NextPiece (int step)
5339 ChessSquare piece = boards[currentMove][toY][toX];
5342 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5343 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5344 if(!step) step = -1;
5345 } while(PieceToChar(pieceSweep) == '.');
5346 boards[currentMove][toY][toX] = pieceSweep;
5347 DrawPosition(FALSE, boards[currentMove]);
5348 boards[currentMove][toY][toX] = piece;
5350 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5352 AlphaRank (char *move, int n)
5354 // char *p = move, c; int x, y;
5356 if (appData.debugMode) {
5357 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5361 move[2]>='0' && move[2]<='9' &&
5362 move[3]>='a' && move[3]<='x' ) {
5364 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5365 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5367 if(move[0]>='0' && move[0]<='9' &&
5368 move[1]>='a' && move[1]<='x' &&
5369 move[2]>='0' && move[2]<='9' &&
5370 move[3]>='a' && move[3]<='x' ) {
5371 /* input move, Shogi -> normal */
5372 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5373 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5374 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5375 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5378 move[3]>='0' && move[3]<='9' &&
5379 move[2]>='a' && move[2]<='x' ) {
5381 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5382 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5385 move[0]>='a' && move[0]<='x' &&
5386 move[3]>='0' && move[3]<='9' &&
5387 move[2]>='a' && move[2]<='x' ) {
5388 /* output move, normal -> Shogi */
5389 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5390 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5391 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5392 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5393 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5395 if (appData.debugMode) {
5396 fprintf(debugFP, " out = '%s'\n", move);
5400 char yy_textstr[8000];
5402 /* Parser for moves from gnuchess, ICS, or user typein box */
5404 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5406 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5408 switch (*moveType) {
5409 case WhitePromotion:
5410 case BlackPromotion:
5411 case WhiteNonPromotion:
5412 case BlackNonPromotion:
5415 case WhiteCapturesEnPassant:
5416 case BlackCapturesEnPassant:
5417 case WhiteKingSideCastle:
5418 case WhiteQueenSideCastle:
5419 case BlackKingSideCastle:
5420 case BlackQueenSideCastle:
5421 case WhiteKingSideCastleWild:
5422 case WhiteQueenSideCastleWild:
5423 case BlackKingSideCastleWild:
5424 case BlackQueenSideCastleWild:
5425 /* Code added by Tord: */
5426 case WhiteHSideCastleFR:
5427 case WhiteASideCastleFR:
5428 case BlackHSideCastleFR:
5429 case BlackASideCastleFR:
5430 /* End of code added by Tord */
5431 case IllegalMove: /* bug or odd chess variant */
5432 *fromX = currentMoveString[0] - AAA;
5433 *fromY = currentMoveString[1] - ONE;
5434 *toX = currentMoveString[2] - AAA;
5435 *toY = currentMoveString[3] - ONE;
5436 *promoChar = currentMoveString[4];
5437 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5438 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5439 if (appData.debugMode) {
5440 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5442 *fromX = *fromY = *toX = *toY = 0;
5445 if (appData.testLegality) {
5446 return (*moveType != IllegalMove);
5448 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5449 // [HGM] lion: if this is a double move we are less critical
5450 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5455 *fromX = *moveType == WhiteDrop ?
5456 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5457 (int) CharToPiece(ToLower(currentMoveString[0]));
5459 *toX = currentMoveString[2] - AAA;
5460 *toY = currentMoveString[3] - ONE;
5461 *promoChar = NULLCHAR;
5465 case ImpossibleMove:
5475 if (appData.debugMode) {
5476 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5479 *fromX = *fromY = *toX = *toY = 0;
5480 *promoChar = NULLCHAR;
5485 Boolean pushed = FALSE;
5486 char *lastParseAttempt;
5489 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5490 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5491 int fromX, fromY, toX, toY; char promoChar;
5496 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5497 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5498 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5501 endPV = forwardMostMove;
5503 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5504 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5505 lastParseAttempt = pv;
5506 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5507 if(!valid && nr == 0 &&
5508 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5509 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5510 // Hande case where played move is different from leading PV move
5511 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5512 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5513 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5514 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5515 endPV += 2; // if position different, keep this
5516 moveList[endPV-1][0] = fromX + AAA;
5517 moveList[endPV-1][1] = fromY + ONE;
5518 moveList[endPV-1][2] = toX + AAA;
5519 moveList[endPV-1][3] = toY + ONE;
5520 parseList[endPV-1][0] = NULLCHAR;
5521 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5524 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5525 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5526 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5527 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5528 valid++; // allow comments in PV
5532 if(endPV+1 > framePtr) break; // no space, truncate
5535 CopyBoard(boards[endPV], boards[endPV-1]);
5536 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5537 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5538 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5539 CoordsToAlgebraic(boards[endPV - 1],
5540 PosFlags(endPV - 1),
5541 fromY, fromX, toY, toX, promoChar,
5542 parseList[endPV - 1]);
5544 if(atEnd == 2) return; // used hidden, for PV conversion
5545 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5546 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5547 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5548 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5549 DrawPosition(TRUE, boards[currentMove]);
5553 MultiPV (ChessProgramState *cps)
5554 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5556 for(i=0; i<cps->nrOptions; i++)
5557 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5562 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5565 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5567 int startPV, multi, lineStart, origIndex = index;
5568 char *p, buf2[MSG_SIZ];
5569 ChessProgramState *cps = (pane ? &second : &first);
5571 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5572 lastX = x; lastY = y;
5573 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5574 lineStart = startPV = index;
5575 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5576 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5578 do{ while(buf[index] && buf[index] != '\n') index++;
5579 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5581 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5582 int n = cps->option[multi].value;
5583 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5584 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5585 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5586 cps->option[multi].value = n;
5589 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5590 ExcludeClick(origIndex - lineStart);
5593 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5594 *start = startPV; *end = index-1;
5595 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5602 static char buf[10*MSG_SIZ];
5603 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5605 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5606 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5607 for(i = forwardMostMove; i<endPV; i++){
5608 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5609 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5612 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5613 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5614 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5620 LoadPV (int x, int y)
5621 { // called on right mouse click to load PV
5622 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5623 lastX = x; lastY = y;
5624 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5632 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5633 if(endPV < 0) return;
5634 if(appData.autoCopyPV) CopyFENToClipboard();
5636 if(extendGame && currentMove > forwardMostMove) {
5637 Boolean saveAnimate = appData.animate;
5639 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5640 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5641 } else storedGames--; // abandon shelved tail of original game
5644 forwardMostMove = currentMove;
5645 currentMove = oldFMM;
5646 appData.animate = FALSE;
5647 ToNrEvent(forwardMostMove);
5648 appData.animate = saveAnimate;
5650 currentMove = forwardMostMove;
5651 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5652 ClearPremoveHighlights();
5653 DrawPosition(TRUE, boards[currentMove]);
5657 MovePV (int x, int y, int h)
5658 { // step through PV based on mouse coordinates (called on mouse move)
5659 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5661 // we must somehow check if right button is still down (might be released off board!)
5662 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5663 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5664 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5666 lastX = x; lastY = y;
5668 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5669 if(endPV < 0) return;
5670 if(y < margin) step = 1; else
5671 if(y > h - margin) step = -1;
5672 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5673 currentMove += step;
5674 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5675 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5676 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5677 DrawPosition(FALSE, boards[currentMove]);
5681 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5682 // All positions will have equal probability, but the current method will not provide a unique
5683 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5689 int piecesLeft[(int)BlackPawn];
5690 int seed, nrOfShuffles;
5693 GetPositionNumber ()
5694 { // sets global variable seed
5697 seed = appData.defaultFrcPosition;
5698 if(seed < 0) { // randomize based on time for negative FRC position numbers
5699 for(i=0; i<50; i++) seed += random();
5700 seed = random() ^ random() >> 8 ^ random() << 8;
5701 if(seed<0) seed = -seed;
5706 put (Board board, int pieceType, int rank, int n, int shade)
5707 // put the piece on the (n-1)-th empty squares of the given shade
5711 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5712 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5713 board[rank][i] = (ChessSquare) pieceType;
5714 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5716 piecesLeft[pieceType]--;
5725 AddOnePiece (Board board, int pieceType, int rank, int shade)
5726 // calculate where the next piece goes, (any empty square), and put it there
5730 i = seed % squaresLeft[shade];
5731 nrOfShuffles *= squaresLeft[shade];
5732 seed /= squaresLeft[shade];
5733 put(board, pieceType, rank, i, shade);
5737 AddTwoPieces (Board board, int pieceType, int rank)
5738 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5740 int i, n=squaresLeft[ANY], j=n-1, k;
5742 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5743 i = seed % k; // pick one
5746 while(i >= j) i -= j--;
5747 j = n - 1 - j; i += j;
5748 put(board, pieceType, rank, j, ANY);
5749 put(board, pieceType, rank, i, ANY);
5753 SetUpShuffle (Board board, int number)
5757 GetPositionNumber(); nrOfShuffles = 1;
5759 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5760 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5761 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5763 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5765 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5766 p = (int) board[0][i];
5767 if(p < (int) BlackPawn) piecesLeft[p] ++;
5768 board[0][i] = EmptySquare;
5771 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5772 // shuffles restricted to allow normal castling put KRR first
5773 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5774 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5775 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5776 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5777 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5778 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5779 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5780 put(board, WhiteRook, 0, 0, ANY);
5781 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5784 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5785 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5786 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5787 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5788 while(piecesLeft[p] >= 2) {
5789 AddOnePiece(board, p, 0, LITE);
5790 AddOnePiece(board, p, 0, DARK);
5792 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5795 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5796 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5797 // but we leave King and Rooks for last, to possibly obey FRC restriction
5798 if(p == (int)WhiteRook) continue;
5799 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5800 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5803 // now everything is placed, except perhaps King (Unicorn) and Rooks
5805 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5806 // Last King gets castling rights
5807 while(piecesLeft[(int)WhiteUnicorn]) {
5808 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5809 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5812 while(piecesLeft[(int)WhiteKing]) {
5813 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5814 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5819 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5820 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5823 // Only Rooks can be left; simply place them all
5824 while(piecesLeft[(int)WhiteRook]) {
5825 i = put(board, WhiteRook, 0, 0, ANY);
5826 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5829 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5831 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5834 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5835 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5838 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5842 SetCharTable (char *table, const char * map)
5843 /* [HGM] moved here from winboard.c because of its general usefulness */
5844 /* Basically a safe strcpy that uses the last character as King */
5846 int result = FALSE; int NrPieces;
5848 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5849 && NrPieces >= 12 && !(NrPieces&1)) {
5850 int i; /* [HGM] Accept even length from 12 to 34 */
5852 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5853 for( i=0; i<NrPieces/2-1; i++ ) {
5855 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5857 table[(int) WhiteKing] = map[NrPieces/2-1];
5858 table[(int) BlackKing] = map[NrPieces-1];
5867 Prelude (Board board)
5868 { // [HGM] superchess: random selection of exo-pieces
5869 int i, j, k; ChessSquare p;
5870 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5872 GetPositionNumber(); // use FRC position number
5874 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5875 SetCharTable(pieceToChar, appData.pieceToCharTable);
5876 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5877 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5880 j = seed%4; seed /= 4;
5881 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5882 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5883 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5884 j = seed%3 + (seed%3 >= j); seed /= 3;
5885 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5886 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5887 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5888 j = seed%3; seed /= 3;
5889 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5890 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5891 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5892 j = seed%2 + (seed%2 >= j); seed /= 2;
5893 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5894 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5895 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5896 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5897 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5898 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5899 put(board, exoPieces[0], 0, 0, ANY);
5900 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5904 InitPosition (int redraw)
5906 ChessSquare (* pieces)[BOARD_FILES];
5907 int i, j, pawnRow=1, pieceRows=1, overrule,
5908 oldx = gameInfo.boardWidth,
5909 oldy = gameInfo.boardHeight,
5910 oldh = gameInfo.holdingsWidth;
5913 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5915 /* [AS] Initialize pv info list [HGM] and game status */
5917 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5918 pvInfoList[i].depth = 0;
5919 boards[i][EP_STATUS] = EP_NONE;
5920 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5923 initialRulePlies = 0; /* 50-move counter start */
5925 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5926 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5930 /* [HGM] logic here is completely changed. In stead of full positions */
5931 /* the initialized data only consist of the two backranks. The switch */
5932 /* selects which one we will use, which is than copied to the Board */
5933 /* initialPosition, which for the rest is initialized by Pawns and */
5934 /* empty squares. This initial position is then copied to boards[0], */
5935 /* possibly after shuffling, so that it remains available. */
5937 gameInfo.holdingsWidth = 0; /* default board sizes */
5938 gameInfo.boardWidth = 8;
5939 gameInfo.boardHeight = 8;
5940 gameInfo.holdingsSize = 0;
5941 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5942 for(i=0; i<BOARD_FILES-2; i++)
5943 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5944 initialPosition[EP_STATUS] = EP_NONE;
5945 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5946 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5947 SetCharTable(pieceNickName, appData.pieceNickNames);
5948 else SetCharTable(pieceNickName, "............");
5951 switch (gameInfo.variant) {
5952 case VariantFischeRandom:
5953 shuffleOpenings = TRUE;
5956 case VariantShatranj:
5957 pieces = ShatranjArray;
5958 nrCastlingRights = 0;
5959 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5962 pieces = makrukArray;
5963 nrCastlingRights = 0;
5964 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5967 pieces = aseanArray;
5968 nrCastlingRights = 0;
5969 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5971 case VariantTwoKings:
5972 pieces = twoKingsArray;
5975 pieces = GrandArray;
5976 nrCastlingRights = 0;
5977 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5978 gameInfo.boardWidth = 10;
5979 gameInfo.boardHeight = 10;
5980 gameInfo.holdingsSize = 7;
5982 case VariantCapaRandom:
5983 shuffleOpenings = TRUE;
5984 case VariantCapablanca:
5985 pieces = CapablancaArray;
5986 gameInfo.boardWidth = 10;
5987 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5990 pieces = GothicArray;
5991 gameInfo.boardWidth = 10;
5992 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5995 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5996 gameInfo.holdingsSize = 7;
5997 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6000 pieces = JanusArray;
6001 gameInfo.boardWidth = 10;
6002 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6003 nrCastlingRights = 6;
6004 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6005 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6006 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6007 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6008 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6009 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6012 pieces = FalconArray;
6013 gameInfo.boardWidth = 10;
6014 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6016 case VariantXiangqi:
6017 pieces = XiangqiArray;
6018 gameInfo.boardWidth = 9;
6019 gameInfo.boardHeight = 10;
6020 nrCastlingRights = 0;
6021 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6024 pieces = ShogiArray;
6025 gameInfo.boardWidth = 9;
6026 gameInfo.boardHeight = 9;
6027 gameInfo.holdingsSize = 7;
6028 nrCastlingRights = 0;
6029 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6032 pieces = ChuArray; pieceRows = 3;
6033 gameInfo.boardWidth = 12;
6034 gameInfo.boardHeight = 12;
6035 nrCastlingRights = 0;
6036 SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6037 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6039 case VariantCourier:
6040 pieces = CourierArray;
6041 gameInfo.boardWidth = 12;
6042 nrCastlingRights = 0;
6043 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6045 case VariantKnightmate:
6046 pieces = KnightmateArray;
6047 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6049 case VariantSpartan:
6050 pieces = SpartanArray;
6051 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6055 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6058 pieces = fairyArray;
6059 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6062 pieces = GreatArray;
6063 gameInfo.boardWidth = 10;
6064 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6065 gameInfo.holdingsSize = 8;
6069 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6070 gameInfo.holdingsSize = 8;
6071 startedFromSetupPosition = TRUE;
6073 case VariantCrazyhouse:
6074 case VariantBughouse:
6076 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6077 gameInfo.holdingsSize = 5;
6079 case VariantWildCastle:
6081 /* !!?shuffle with kings guaranteed to be on d or e file */
6082 shuffleOpenings = 1;
6084 case VariantNoCastle:
6086 nrCastlingRights = 0;
6087 /* !!?unconstrained back-rank shuffle */
6088 shuffleOpenings = 1;
6093 if(appData.NrFiles >= 0) {
6094 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6095 gameInfo.boardWidth = appData.NrFiles;
6097 if(appData.NrRanks >= 0) {
6098 gameInfo.boardHeight = appData.NrRanks;
6100 if(appData.holdingsSize >= 0) {
6101 i = appData.holdingsSize;
6102 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6103 gameInfo.holdingsSize = i;
6105 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6106 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6107 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6109 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6110 if(pawnRow < 1) pawnRow = 1;
6111 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6112 if(gameInfo.variant == VariantChu) pawnRow = 3;
6114 /* User pieceToChar list overrules defaults */
6115 if(appData.pieceToCharTable != NULL)
6116 SetCharTable(pieceToChar, appData.pieceToCharTable);
6118 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6120 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6121 s = (ChessSquare) 0; /* account holding counts in guard band */
6122 for( i=0; i<BOARD_HEIGHT; i++ )
6123 initialPosition[i][j] = s;
6125 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6126 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6127 initialPosition[pawnRow][j] = WhitePawn;
6128 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6129 if(gameInfo.variant == VariantXiangqi) {
6131 initialPosition[pawnRow][j] =
6132 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6133 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6134 initialPosition[2][j] = WhiteCannon;
6135 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6139 if(gameInfo.variant == VariantChu) {
6140 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6141 initialPosition[pawnRow+1][j] = WhiteCobra,
6142 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6143 for(i=1; i<pieceRows; i++) {
6144 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6145 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6148 if(gameInfo.variant == VariantGrand) {
6149 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6150 initialPosition[0][j] = WhiteRook;
6151 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6154 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
6156 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6159 initialPosition[1][j] = WhiteBishop;
6160 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6162 initialPosition[1][j] = WhiteRook;
6163 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6166 if( nrCastlingRights == -1) {
6167 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6168 /* This sets default castling rights from none to normal corners */
6169 /* Variants with other castling rights must set them themselves above */
6170 nrCastlingRights = 6;
6172 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6173 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6174 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6175 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6176 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6177 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6180 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6181 if(gameInfo.variant == VariantGreat) { // promotion commoners
6182 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6183 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6184 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6185 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6187 if( gameInfo.variant == VariantSChess ) {
6188 initialPosition[1][0] = BlackMarshall;
6189 initialPosition[2][0] = BlackAngel;
6190 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6191 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6192 initialPosition[1][1] = initialPosition[2][1] =
6193 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6195 if (appData.debugMode) {
6196 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6198 if(shuffleOpenings) {
6199 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6200 startedFromSetupPosition = TRUE;
6202 if(startedFromPositionFile) {
6203 /* [HGM] loadPos: use PositionFile for every new game */
6204 CopyBoard(initialPosition, filePosition);
6205 for(i=0; i<nrCastlingRights; i++)
6206 initialRights[i] = filePosition[CASTLING][i];
6207 startedFromSetupPosition = TRUE;
6210 CopyBoard(boards[0], initialPosition);
6212 if(oldx != gameInfo.boardWidth ||
6213 oldy != gameInfo.boardHeight ||
6214 oldv != gameInfo.variant ||
6215 oldh != gameInfo.holdingsWidth
6217 InitDrawingSizes(-2 ,0);
6219 oldv = gameInfo.variant;
6221 DrawPosition(TRUE, boards[currentMove]);
6225 SendBoard (ChessProgramState *cps, int moveNum)
6227 char message[MSG_SIZ];
6229 if (cps->useSetboard) {
6230 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6231 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6232 SendToProgram(message, cps);
6237 int i, j, left=0, right=BOARD_WIDTH;
6238 /* Kludge to set black to move, avoiding the troublesome and now
6239 * deprecated "black" command.
6241 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6242 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6244 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6246 SendToProgram("edit\n", cps);
6247 SendToProgram("#\n", cps);
6248 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6249 bp = &boards[moveNum][i][left];
6250 for (j = left; j < right; j++, bp++) {
6251 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6252 if ((int) *bp < (int) BlackPawn) {
6253 if(j == BOARD_RGHT+1)
6254 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6255 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6256 if(message[0] == '+' || message[0] == '~') {
6257 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6258 PieceToChar((ChessSquare)(DEMOTED *bp)),
6261 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6262 message[1] = BOARD_RGHT - 1 - j + '1';
6263 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6265 SendToProgram(message, cps);
6270 SendToProgram("c\n", cps);
6271 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6272 bp = &boards[moveNum][i][left];
6273 for (j = left; j < right; j++, bp++) {
6274 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6275 if (((int) *bp != (int) EmptySquare)
6276 && ((int) *bp >= (int) BlackPawn)) {
6277 if(j == BOARD_LEFT-2)
6278 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6279 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6281 if(message[0] == '+' || message[0] == '~') {
6282 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6283 PieceToChar((ChessSquare)(DEMOTED *bp)),
6286 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6287 message[1] = BOARD_RGHT - 1 - j + '1';
6288 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6290 SendToProgram(message, cps);
6295 SendToProgram(".\n", cps);
6297 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6300 char exclusionHeader[MSG_SIZ];
6301 int exCnt, excludePtr;
6302 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6303 static Exclusion excluTab[200];
6304 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6310 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6311 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6317 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6318 excludePtr = 24; exCnt = 0;
6323 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6324 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6325 char buf[2*MOVE_LEN], *p;
6326 Exclusion *e = excluTab;
6328 for(i=0; i<exCnt; i++)
6329 if(e[i].ff == fromX && e[i].fr == fromY &&
6330 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6331 if(i == exCnt) { // was not in exclude list; add it
6332 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6333 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6334 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6337 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6338 excludePtr++; e[i].mark = excludePtr++;
6339 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6342 exclusionHeader[e[i].mark] = state;
6346 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6347 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6351 if((signed char)promoChar == -1) { // kludge to indicate best move
6352 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6353 return 1; // if unparsable, abort
6355 // update exclusion map (resolving toggle by consulting existing state)
6356 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6358 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6359 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6360 excludeMap[k] |= 1<<j;
6361 else excludeMap[k] &= ~(1<<j);
6363 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6365 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6366 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6368 return (state == '+');
6372 ExcludeClick (int index)
6375 Exclusion *e = excluTab;
6376 if(index < 25) { // none, best or tail clicked
6377 if(index < 13) { // none: include all
6378 WriteMap(0); // clear map
6379 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6380 SendToBoth("include all\n"); // and inform engine
6381 } else if(index > 18) { // tail
6382 if(exclusionHeader[19] == '-') { // tail was excluded
6383 SendToBoth("include all\n");
6384 WriteMap(0); // clear map completely
6385 // now re-exclude selected moves
6386 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6387 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6388 } else { // tail was included or in mixed state
6389 SendToBoth("exclude all\n");
6390 WriteMap(0xFF); // fill map completely
6391 // now re-include selected moves
6392 j = 0; // count them
6393 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6394 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6395 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6398 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6401 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6402 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6403 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6410 DefaultPromoChoice (int white)
6413 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6414 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6415 result = WhiteFerz; // no choice
6416 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6417 result= WhiteKing; // in Suicide Q is the last thing we want
6418 else if(gameInfo.variant == VariantSpartan)
6419 result = white ? WhiteQueen : WhiteAngel;
6420 else result = WhiteQueen;
6421 if(!white) result = WHITE_TO_BLACK result;
6425 static int autoQueen; // [HGM] oneclick
6428 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6430 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6431 /* [HGM] add Shogi promotions */
6432 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6437 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6438 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6440 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6441 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6444 piece = boards[currentMove][fromY][fromX];
6445 if(gameInfo.variant == VariantChu) {
6446 int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6447 promotionZoneSize = BOARD_HEIGHT/3;
6448 highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6449 } else if(gameInfo.variant == VariantShogi) {
6450 promotionZoneSize = BOARD_HEIGHT/3;
6451 highestPromotingPiece = (int)WhiteAlfil;
6452 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6453 promotionZoneSize = 3;
6456 // Treat Lance as Pawn when it is not representing Amazon
6457 if(gameInfo.variant != VariantSuper) {
6458 if(piece == WhiteLance) piece = WhitePawn; else
6459 if(piece == BlackLance) piece = BlackPawn;
6462 // next weed out all moves that do not touch the promotion zone at all
6463 if((int)piece >= BlackPawn) {
6464 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6466 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6468 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6469 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6472 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6474 // weed out mandatory Shogi promotions
6475 if(gameInfo.variant == VariantShogi) {
6476 if(piece >= BlackPawn) {
6477 if(toY == 0 && piece == BlackPawn ||
6478 toY == 0 && piece == BlackQueen ||
6479 toY <= 1 && piece == BlackKnight) {
6484 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6485 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6486 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6493 // weed out obviously illegal Pawn moves
6494 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6495 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6496 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6497 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6498 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6499 // note we are not allowed to test for valid (non-)capture, due to premove
6502 // we either have a choice what to promote to, or (in Shogi) whether to promote
6503 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6504 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6505 *promoChoice = PieceToChar(BlackFerz); // no choice
6508 // no sense asking what we must promote to if it is going to explode...
6509 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6510 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6513 // give caller the default choice even if we will not make it
6514 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6515 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6516 if( sweepSelect && gameInfo.variant != VariantGreat
6517 && gameInfo.variant != VariantGrand
6518 && gameInfo.variant != VariantSuper) return FALSE;
6519 if(autoQueen) return FALSE; // predetermined
6521 // suppress promotion popup on illegal moves that are not premoves
6522 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6523 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6524 if(appData.testLegality && !premove) {
6525 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6526 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) ? '+' : NULLCHAR);
6527 if(moveType != WhitePromotion && moveType != BlackPromotion)
6535 InPalace (int row, int column)
6536 { /* [HGM] for Xiangqi */
6537 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6538 column < (BOARD_WIDTH + 4)/2 &&
6539 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6544 PieceForSquare (int x, int y)
6546 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6549 return boards[currentMove][y][x];
6553 OKToStartUserMove (int x, int y)
6555 ChessSquare from_piece;
6558 if (matchMode) return FALSE;
6559 if (gameMode == EditPosition) return TRUE;
6561 if (x >= 0 && y >= 0)
6562 from_piece = boards[currentMove][y][x];
6564 from_piece = EmptySquare;
6566 if (from_piece == EmptySquare) return FALSE;
6568 white_piece = (int)from_piece >= (int)WhitePawn &&
6569 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6573 case TwoMachinesPlay:
6581 case MachinePlaysWhite:
6582 case IcsPlayingBlack:
6583 if (appData.zippyPlay) return FALSE;
6585 DisplayMoveError(_("You are playing Black"));
6590 case MachinePlaysBlack:
6591 case IcsPlayingWhite:
6592 if (appData.zippyPlay) return FALSE;
6594 DisplayMoveError(_("You are playing White"));
6599 case PlayFromGameFile:
6600 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6602 if (!white_piece && WhiteOnMove(currentMove)) {
6603 DisplayMoveError(_("It is White's turn"));
6606 if (white_piece && !WhiteOnMove(currentMove)) {
6607 DisplayMoveError(_("It is Black's turn"));
6610 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6611 /* Editing correspondence game history */
6612 /* Could disallow this or prompt for confirmation */
6617 case BeginningOfGame:
6618 if (appData.icsActive) return FALSE;
6619 if (!appData.noChessProgram) {
6621 DisplayMoveError(_("You are playing White"));
6628 if (!white_piece && WhiteOnMove(currentMove)) {
6629 DisplayMoveError(_("It is White's turn"));
6632 if (white_piece && !WhiteOnMove(currentMove)) {
6633 DisplayMoveError(_("It is Black's turn"));
6642 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6643 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6644 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6645 && gameMode != AnalyzeFile && gameMode != Training) {
6646 DisplayMoveError(_("Displayed position is not current"));
6653 OnlyMove (int *x, int *y, Boolean captures)
6655 DisambiguateClosure cl;
6656 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6658 case MachinePlaysBlack:
6659 case IcsPlayingWhite:
6660 case BeginningOfGame:
6661 if(!WhiteOnMove(currentMove)) return FALSE;
6663 case MachinePlaysWhite:
6664 case IcsPlayingBlack:
6665 if(WhiteOnMove(currentMove)) return FALSE;
6672 cl.pieceIn = EmptySquare;
6677 cl.promoCharIn = NULLCHAR;
6678 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6679 if( cl.kind == NormalMove ||
6680 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6681 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6682 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6689 if(cl.kind != ImpossibleMove) return FALSE;
6690 cl.pieceIn = EmptySquare;
6695 cl.promoCharIn = NULLCHAR;
6696 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6697 if( cl.kind == NormalMove ||
6698 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6699 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6700 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6705 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6711 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6712 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6713 int lastLoadGameUseList = FALSE;
6714 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6715 ChessMove lastLoadGameStart = EndOfFile;
6719 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6723 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6725 /* Check if the user is playing in turn. This is complicated because we
6726 let the user "pick up" a piece before it is his turn. So the piece he
6727 tried to pick up may have been captured by the time he puts it down!
6728 Therefore we use the color the user is supposed to be playing in this
6729 test, not the color of the piece that is currently on the starting
6730 square---except in EditGame mode, where the user is playing both
6731 sides; fortunately there the capture race can't happen. (It can
6732 now happen in IcsExamining mode, but that's just too bad. The user
6733 will get a somewhat confusing message in that case.)
6738 case TwoMachinesPlay:
6742 /* We switched into a game mode where moves are not accepted,
6743 perhaps while the mouse button was down. */
6746 case MachinePlaysWhite:
6747 /* User is moving for Black */
6748 if (WhiteOnMove(currentMove)) {
6749 DisplayMoveError(_("It is White's turn"));
6754 case MachinePlaysBlack:
6755 /* User is moving for White */
6756 if (!WhiteOnMove(currentMove)) {
6757 DisplayMoveError(_("It is Black's turn"));
6762 case PlayFromGameFile:
6763 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6766 case BeginningOfGame:
6769 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6770 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6771 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6772 /* User is moving for Black */
6773 if (WhiteOnMove(currentMove)) {
6774 DisplayMoveError(_("It is White's turn"));
6778 /* User is moving for White */
6779 if (!WhiteOnMove(currentMove)) {
6780 DisplayMoveError(_("It is Black's turn"));
6786 case IcsPlayingBlack:
6787 /* User is moving for Black */
6788 if (WhiteOnMove(currentMove)) {
6789 if (!appData.premove) {
6790 DisplayMoveError(_("It is White's turn"));
6791 } else if (toX >= 0 && toY >= 0) {
6794 premoveFromX = fromX;
6795 premoveFromY = fromY;
6796 premovePromoChar = promoChar;
6798 if (appData.debugMode)
6799 fprintf(debugFP, "Got premove: fromX %d,"
6800 "fromY %d, toX %d, toY %d\n",
6801 fromX, fromY, toX, toY);
6807 case IcsPlayingWhite:
6808 /* User is moving for White */
6809 if (!WhiteOnMove(currentMove)) {
6810 if (!appData.premove) {
6811 DisplayMoveError(_("It is Black's turn"));
6812 } else if (toX >= 0 && toY >= 0) {
6815 premoveFromX = fromX;
6816 premoveFromY = fromY;
6817 premovePromoChar = promoChar;
6819 if (appData.debugMode)
6820 fprintf(debugFP, "Got premove: fromX %d,"
6821 "fromY %d, toX %d, toY %d\n",
6822 fromX, fromY, toX, toY);
6832 /* EditPosition, empty square, or different color piece;
6833 click-click move is possible */
6834 if (toX == -2 || toY == -2) {
6835 boards[0][fromY][fromX] = EmptySquare;
6836 DrawPosition(FALSE, boards[currentMove]);
6838 } else if (toX >= 0 && toY >= 0) {
6839 boards[0][toY][toX] = boards[0][fromY][fromX];
6840 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6841 if(boards[0][fromY][0] != EmptySquare) {
6842 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6843 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6846 if(fromX == BOARD_RGHT+1) {
6847 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6848 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6849 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6852 boards[0][fromY][fromX] = gatingPiece;
6853 DrawPosition(FALSE, boards[currentMove]);
6859 if(toX < 0 || toY < 0) return;
6860 pup = boards[currentMove][toY][toX];
6862 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6863 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6864 if( pup != EmptySquare ) return;
6865 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6866 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6867 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6868 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6869 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6870 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6871 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6875 /* [HGM] always test for legality, to get promotion info */
6876 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6877 fromY, fromX, toY, toX, promoChar);
6879 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6881 /* [HGM] but possibly ignore an IllegalMove result */
6882 if (appData.testLegality) {
6883 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6884 DisplayMoveError(_("Illegal move"));
6889 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6890 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6891 ClearPremoveHighlights(); // was included
6892 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6896 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6899 /* Common tail of UserMoveEvent and DropMenuEvent */
6901 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6905 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6906 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6907 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6908 if(WhiteOnMove(currentMove)) {
6909 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6911 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6915 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6916 move type in caller when we know the move is a legal promotion */
6917 if(moveType == NormalMove && promoChar)
6918 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6920 /* [HGM] <popupFix> The following if has been moved here from
6921 UserMoveEvent(). Because it seemed to belong here (why not allow
6922 piece drops in training games?), and because it can only be
6923 performed after it is known to what we promote. */
6924 if (gameMode == Training) {
6925 /* compare the move played on the board to the next move in the
6926 * game. If they match, display the move and the opponent's response.
6927 * If they don't match, display an error message.
6931 CopyBoard(testBoard, boards[currentMove]);
6932 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6934 if (CompareBoards(testBoard, boards[currentMove+1])) {
6935 ForwardInner(currentMove+1);
6937 /* Autoplay the opponent's response.
6938 * if appData.animate was TRUE when Training mode was entered,
6939 * the response will be animated.
6941 saveAnimate = appData.animate;
6942 appData.animate = animateTraining;
6943 ForwardInner(currentMove+1);
6944 appData.animate = saveAnimate;
6946 /* check for the end of the game */
6947 if (currentMove >= forwardMostMove) {
6948 gameMode = PlayFromGameFile;
6950 SetTrainingModeOff();
6951 DisplayInformation(_("End of game"));
6954 DisplayError(_("Incorrect move"), 0);
6959 /* Ok, now we know that the move is good, so we can kill
6960 the previous line in Analysis Mode */
6961 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6962 && currentMove < forwardMostMove) {
6963 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6964 else forwardMostMove = currentMove;
6969 /* If we need the chess program but it's dead, restart it */
6970 ResurrectChessProgram();
6972 /* A user move restarts a paused game*/
6976 thinkOutput[0] = NULLCHAR;
6978 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6980 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6981 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6985 if (gameMode == BeginningOfGame) {
6986 if (appData.noChessProgram) {
6987 gameMode = EditGame;
6991 gameMode = MachinePlaysBlack;
6994 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6996 if (first.sendName) {
6997 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6998 SendToProgram(buf, &first);
7005 /* Relay move to ICS or chess engine */
7006 if (appData.icsActive) {
7007 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7008 gameMode == IcsExamining) {
7009 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7010 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7012 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7014 // also send plain move, in case ICS does not understand atomic claims
7015 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7019 if (first.sendTime && (gameMode == BeginningOfGame ||
7020 gameMode == MachinePlaysWhite ||
7021 gameMode == MachinePlaysBlack)) {
7022 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7024 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7025 // [HGM] book: if program might be playing, let it use book
7026 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7027 first.maybeThinking = TRUE;
7028 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7029 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7030 SendBoard(&first, currentMove+1);
7031 if(second.analyzing) {
7032 if(!second.useSetboard) SendToProgram("undo\n", &second);
7033 SendBoard(&second, currentMove+1);
7036 SendMoveToProgram(forwardMostMove-1, &first);
7037 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7039 if (currentMove == cmailOldMove + 1) {
7040 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7044 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7048 if(appData.testLegality)
7049 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7055 if (WhiteOnMove(currentMove)) {
7056 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7058 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7062 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7067 case MachinePlaysBlack:
7068 case MachinePlaysWhite:
7069 /* disable certain menu options while machine is thinking */
7070 SetMachineThinkingEnables();
7077 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7078 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7080 if(bookHit) { // [HGM] book: simulate book reply
7081 static char bookMove[MSG_SIZ]; // a bit generous?
7083 programStats.nodes = programStats.depth = programStats.time =
7084 programStats.score = programStats.got_only_move = 0;
7085 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7087 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7088 strcat(bookMove, bookHit);
7089 HandleMachineMove(bookMove, &first);
7095 MarkByFEN(char *fen)
7098 if(!appData.markers || !appData.highlightDragging) return;
7099 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7100 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7104 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7105 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7106 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7107 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7108 if(*fen == 'T') marker[r][f++] = 0; else
7109 if(*fen == 'Y') marker[r][f++] = 1; else
7110 if(*fen == 'G') marker[r][f++] = 3; else
7111 if(*fen == 'B') marker[r][f++] = 4; else
7112 if(*fen == 'C') marker[r][f++] = 5; else
7113 if(*fen == 'M') marker[r][f++] = 6; else
7114 if(*fen == 'W') marker[r][f++] = 7; else
7115 if(*fen == 'D') marker[r][f++] = 8; else
7116 if(*fen == 'R') marker[r][f++] = 2; else {
7117 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7120 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7124 DrawPosition(TRUE, NULL);
7127 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7130 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7132 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7133 Markers *m = (Markers *) closure;
7134 if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7135 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7136 || kind == WhiteCapturesEnPassant
7137 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7138 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7142 MarkTargetSquares (int clear)
7145 if(clear) { // no reason to ever suppress clearing
7146 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = baseMarker[y][x] = 0;
7147 if(!sum) return; // nothing was cleared,no redraw needed
7150 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7151 !appData.testLegality || gameMode == EditPosition) return;
7152 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7153 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7154 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7156 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7159 DrawPosition(FALSE, NULL);
7163 Explode (Board board, int fromX, int fromY, int toX, int toY)
7165 if(gameInfo.variant == VariantAtomic &&
7166 (board[toY][toX] != EmptySquare || // capture?
7167 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7168 board[fromY][fromX] == BlackPawn )
7170 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7176 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7179 CanPromote (ChessSquare piece, int y)
7181 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7182 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7183 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7184 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7185 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7186 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7187 return (piece == BlackPawn && y == 1 ||
7188 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7189 piece == BlackLance && y == 1 ||
7190 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7194 HoverEvent (int xPix, int yPix, int x, int y)
7196 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7198 if(!first.highlight) return;
7199 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7200 if(x == oldX && y == oldY) return; // only do something if we enter new square
7201 oldFromX = fromX; oldFromY = fromY;
7202 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7203 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7204 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7205 else if(oldX != x || oldY != y) {
7206 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7207 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7208 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7209 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7211 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7212 SendToProgram(buf, &first);
7215 // SetHighlights(fromX, fromY, x, y);
7219 void ReportClick(char *action, int x, int y)
7221 char buf[MSG_SIZ]; // Inform engine of what user does
7223 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7224 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7225 if(!first.highlight || gameMode == EditPosition) return;
7226 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7227 SendToProgram(buf, &first);
7231 LeftClick (ClickType clickType, int xPix, int yPix)
7234 Boolean saveAnimate;
7235 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7236 char promoChoice = NULLCHAR;
7238 static TimeMark lastClickTime, prevClickTime;
7240 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7242 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7244 if (clickType == Press) ErrorPopDown();
7245 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7247 x = EventToSquare(xPix, BOARD_WIDTH);
7248 y = EventToSquare(yPix, BOARD_HEIGHT);
7249 if (!flipView && y >= 0) {
7250 y = BOARD_HEIGHT - 1 - y;
7252 if (flipView && x >= 0) {
7253 x = BOARD_WIDTH - 1 - x;
7256 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7257 defaultPromoChoice = promoSweep;
7258 promoSweep = EmptySquare; // terminate sweep
7259 promoDefaultAltered = TRUE;
7260 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7263 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7264 if(clickType == Release) return; // ignore upclick of click-click destination
7265 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7266 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7267 if(gameInfo.holdingsWidth &&
7268 (WhiteOnMove(currentMove)
7269 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7270 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7271 // click in right holdings, for determining promotion piece
7272 ChessSquare p = boards[currentMove][y][x];
7273 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7274 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7275 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7276 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7281 DrawPosition(FALSE, boards[currentMove]);
7285 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7286 if(clickType == Press
7287 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7288 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7289 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7292 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7293 // could be static click on premove from-square: abort premove
7295 ClearPremoveHighlights();
7298 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7299 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7301 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7302 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7303 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7304 defaultPromoChoice = DefaultPromoChoice(side);
7307 autoQueen = appData.alwaysPromoteToQueen;
7311 gatingPiece = EmptySquare;
7312 if (clickType != Press) {
7313 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7314 DragPieceEnd(xPix, yPix); dragging = 0;
7315 DrawPosition(FALSE, NULL);
7319 doubleClick = FALSE;
7320 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7321 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7323 fromX = x; fromY = y; toX = toY = killX = killY = -1;
7324 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7325 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7326 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7328 if (OKToStartUserMove(fromX, fromY)) {
7330 ReportClick("lift", x, y);
7331 MarkTargetSquares(0);
7332 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7333 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7334 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7335 promoSweep = defaultPromoChoice;
7336 selectFlag = 0; lastX = xPix; lastY = yPix;
7337 Sweep(0); // Pawn that is going to promote: preview promotion piece
7338 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7340 if (appData.highlightDragging) {
7341 SetHighlights(fromX, fromY, -1, -1);
7345 } else fromX = fromY = -1;
7351 if (clickType == Press && gameMode != EditPosition) {
7356 // ignore off-board to clicks
7357 if(y < 0 || x < 0) return;
7359 /* Check if clicking again on the same color piece */
7360 fromP = boards[currentMove][fromY][fromX];
7361 toP = boards[currentMove][y][x];
7362 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7363 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7364 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7365 WhitePawn <= toP && toP <= WhiteKing &&
7366 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7367 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7368 (BlackPawn <= fromP && fromP <= BlackKing &&
7369 BlackPawn <= toP && toP <= BlackKing &&
7370 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7371 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7372 /* Clicked again on same color piece -- changed his mind */
7373 second = (x == fromX && y == fromY);
7375 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7376 second = FALSE; // first double-click rather than scond click
7377 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7379 promoDefaultAltered = FALSE;
7380 MarkTargetSquares(1);
7381 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7382 if (appData.highlightDragging) {
7383 SetHighlights(x, y, -1, -1);
7387 if (OKToStartUserMove(x, y)) {
7388 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7389 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7390 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7391 gatingPiece = boards[currentMove][fromY][fromX];
7392 else gatingPiece = doubleClick ? fromP : EmptySquare;
7394 fromY = y; dragging = 1;
7395 ReportClick("lift", x, y);
7396 MarkTargetSquares(0);
7397 DragPieceBegin(xPix, yPix, FALSE);
7398 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7399 promoSweep = defaultPromoChoice;
7400 selectFlag = 0; lastX = xPix; lastY = yPix;
7401 Sweep(0); // Pawn that is going to promote: preview promotion piece
7405 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7408 // ignore clicks on holdings
7409 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7412 if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7413 DragPieceEnd(xPix, yPix); dragging = 0;
7415 // a deferred attempt to click-click move an empty square on top of a piece
7416 boards[currentMove][y][x] = EmptySquare;
7418 DrawPosition(FALSE, boards[currentMove]);
7419 fromX = fromY = -1; clearFlag = 0;
7422 if (appData.animateDragging) {
7423 /* Undo animation damage if any */
7424 DrawPosition(FALSE, NULL);
7426 if (second || sweepSelecting) {
7427 /* Second up/down in same square; just abort move */
7428 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7429 second = sweepSelecting = 0;
7431 gatingPiece = EmptySquare;
7432 MarkTargetSquares(1);
7435 ClearPremoveHighlights();
7437 /* First upclick in same square; start click-click mode */
7438 SetHighlights(x, y, -1, -1);
7445 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7446 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7447 DisplayMessage(_("only marked squares are legal"),"");
7448 DrawPosition(TRUE, NULL);
7449 return; // ignore to-click
7452 /* we now have a different from- and (possibly off-board) to-square */
7453 /* Completed move */
7454 if(!sweepSelecting) {
7459 saveAnimate = appData.animate;
7460 if (clickType == Press) {
7461 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7462 // must be Edit Position mode with empty-square selected
7463 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7464 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7467 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7470 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7471 killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7473 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7474 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7475 if(appData.sweepSelect) {
7476 ChessSquare piece = boards[currentMove][fromY][fromX];
7477 promoSweep = defaultPromoChoice;
7478 if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7479 selectFlag = 0; lastX = xPix; lastY = yPix;
7480 Sweep(0); // Pawn that is going to promote: preview promotion piece
7482 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7483 MarkTargetSquares(1);
7485 return; // promo popup appears on up-click
7487 /* Finish clickclick move */
7488 if (appData.animate || appData.highlightLastMove) {
7489 SetHighlights(fromX, fromY, toX, toY);
7493 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7495 if (appData.animate || appData.highlightLastMove) {
7496 SetHighlights(fromX, fromY, toX, toY);
7502 // [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
7503 /* Finish drag move */
7504 if (appData.highlightLastMove) {
7505 SetHighlights(fromX, fromY, toX, toY);
7510 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7511 dragging *= 2; // flag button-less dragging if we are dragging
7512 MarkTargetSquares(1);
7513 if(x == killX && y == killY) killX = killY = -1; else {
7514 killX = x; killY = y; //remeber this square as intermediate
7515 ReportClick("put", x, y); // and inform engine
7516 ReportClick("lift", x, y);
7517 MarkTargetSquares(0);
7521 DragPieceEnd(xPix, yPix); dragging = 0;
7522 /* Don't animate move and drag both */
7523 appData.animate = FALSE;
7526 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7527 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7528 ChessSquare piece = boards[currentMove][fromY][fromX];
7529 if(gameMode == EditPosition && piece != EmptySquare &&
7530 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7533 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7534 n = PieceToNumber(piece - (int)BlackPawn);
7535 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7536 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7537 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7539 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7540 n = PieceToNumber(piece);
7541 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7542 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7543 boards[currentMove][n][BOARD_WIDTH-2]++;
7545 boards[currentMove][fromY][fromX] = EmptySquare;
7549 MarkTargetSquares(1);
7550 DrawPosition(TRUE, boards[currentMove]);
7554 // off-board moves should not be highlighted
7555 if(x < 0 || y < 0) ClearHighlights();
7556 else ReportClick("put", x, y);
7558 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7560 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7561 SetHighlights(fromX, fromY, toX, toY);
7562 MarkTargetSquares(1);
7563 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7564 // [HGM] super: promotion to captured piece selected from holdings
7565 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7566 promotionChoice = TRUE;
7567 // kludge follows to temporarily execute move on display, without promoting yet
7568 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7569 boards[currentMove][toY][toX] = p;
7570 DrawPosition(FALSE, boards[currentMove]);
7571 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7572 boards[currentMove][toY][toX] = q;
7573 DisplayMessage("Click in holdings to choose piece", "");
7578 int oldMove = currentMove;
7579 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7580 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7581 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7582 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7583 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7584 DrawPosition(TRUE, boards[currentMove]);
7585 MarkTargetSquares(1);
7588 appData.animate = saveAnimate;
7589 if (appData.animate || appData.animateDragging) {
7590 /* Undo animation damage if needed */
7591 DrawPosition(FALSE, NULL);
7596 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7597 { // front-end-free part taken out of PieceMenuPopup
7598 int whichMenu; int xSqr, ySqr;
7600 if(seekGraphUp) { // [HGM] seekgraph
7601 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7602 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7606 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7607 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7608 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7609 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7610 if(action == Press) {
7611 originalFlip = flipView;
7612 flipView = !flipView; // temporarily flip board to see game from partners perspective
7613 DrawPosition(TRUE, partnerBoard);
7614 DisplayMessage(partnerStatus, "");
7616 } else if(action == Release) {
7617 flipView = originalFlip;
7618 DrawPosition(TRUE, boards[currentMove]);
7624 xSqr = EventToSquare(x, BOARD_WIDTH);
7625 ySqr = EventToSquare(y, BOARD_HEIGHT);
7626 if (action == Release) {
7627 if(pieceSweep != EmptySquare) {
7628 EditPositionMenuEvent(pieceSweep, toX, toY);
7629 pieceSweep = EmptySquare;
7630 } else UnLoadPV(); // [HGM] pv
7632 if (action != Press) return -2; // return code to be ignored
7635 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7637 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7638 if (xSqr < 0 || ySqr < 0) return -1;
7639 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7640 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7641 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7642 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7646 if(!appData.icsEngineAnalyze) return -1;
7647 case IcsPlayingWhite:
7648 case IcsPlayingBlack:
7649 if(!appData.zippyPlay) goto noZip;
7652 case MachinePlaysWhite:
7653 case MachinePlaysBlack:
7654 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7655 if (!appData.dropMenu) {
7657 return 2; // flag front-end to grab mouse events
7659 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7660 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7663 if (xSqr < 0 || ySqr < 0) return -1;
7664 if (!appData.dropMenu || appData.testLegality &&
7665 gameInfo.variant != VariantBughouse &&
7666 gameInfo.variant != VariantCrazyhouse) return -1;
7667 whichMenu = 1; // drop menu
7673 if (((*fromX = xSqr) < 0) ||
7674 ((*fromY = ySqr) < 0)) {
7675 *fromX = *fromY = -1;
7679 *fromX = BOARD_WIDTH - 1 - *fromX;
7681 *fromY = BOARD_HEIGHT - 1 - *fromY;
7687 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7689 // char * hint = lastHint;
7690 FrontEndProgramStats stats;
7692 stats.which = cps == &first ? 0 : 1;
7693 stats.depth = cpstats->depth;
7694 stats.nodes = cpstats->nodes;
7695 stats.score = cpstats->score;
7696 stats.time = cpstats->time;
7697 stats.pv = cpstats->movelist;
7698 stats.hint = lastHint;
7699 stats.an_move_index = 0;
7700 stats.an_move_count = 0;
7702 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7703 stats.hint = cpstats->move_name;
7704 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7705 stats.an_move_count = cpstats->nr_moves;
7708 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
7710 SetProgramStats( &stats );
7714 ClearEngineOutputPane (int which)
7716 static FrontEndProgramStats dummyStats;
7717 dummyStats.which = which;
7718 dummyStats.pv = "#";
7719 SetProgramStats( &dummyStats );
7722 #define MAXPLAYERS 500
7725 TourneyStandings (int display)
7727 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7728 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7729 char result, *p, *names[MAXPLAYERS];
7731 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7732 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7733 names[0] = p = strdup(appData.participants);
7734 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7736 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7738 while(result = appData.results[nr]) {
7739 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7740 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7741 wScore = bScore = 0;
7743 case '+': wScore = 2; break;
7744 case '-': bScore = 2; break;
7745 case '=': wScore = bScore = 1; break;
7747 case '*': return strdup("busy"); // tourney not finished
7755 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7756 for(w=0; w<nPlayers; w++) {
7758 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7759 ranking[w] = b; points[w] = bScore; score[b] = -2;
7761 p = malloc(nPlayers*34+1);
7762 for(w=0; w<nPlayers && w<display; w++)
7763 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7769 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7770 { // count all piece types
7772 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7773 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7774 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7777 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7778 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7779 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7780 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7781 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7782 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7787 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7789 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7790 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7792 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7793 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7794 if(myPawns == 2 && nMine == 3) // KPP
7795 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7796 if(myPawns == 1 && nMine == 2) // KP
7797 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7798 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7799 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7800 if(myPawns) return FALSE;
7801 if(pCnt[WhiteRook+side])
7802 return pCnt[BlackRook-side] ||
7803 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7804 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7805 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7806 if(pCnt[WhiteCannon+side]) {
7807 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7808 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7810 if(pCnt[WhiteKnight+side])
7811 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7816 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7818 VariantClass v = gameInfo.variant;
7820 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7821 if(v == VariantShatranj) return TRUE; // always winnable through baring
7822 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7823 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7825 if(v == VariantXiangqi) {
7826 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7828 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7829 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7830 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7831 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7832 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7833 if(stale) // we have at least one last-rank P plus perhaps C
7834 return majors // KPKX
7835 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7837 return pCnt[WhiteFerz+side] // KCAK
7838 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7839 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7840 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7842 } else if(v == VariantKnightmate) {
7843 if(nMine == 1) return FALSE;
7844 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7845 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7846 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7848 if(nMine == 1) return FALSE; // bare King
7849 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
7850 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7851 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7852 // by now we have King + 1 piece (or multiple Bishops on the same color)
7853 if(pCnt[WhiteKnight+side])
7854 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7855 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7856 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7858 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7859 if(pCnt[WhiteAlfil+side])
7860 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7861 if(pCnt[WhiteWazir+side])
7862 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7869 CompareWithRights (Board b1, Board b2)
7872 if(!CompareBoards(b1, b2)) return FALSE;
7873 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7874 /* compare castling rights */
7875 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7876 rights++; /* King lost rights, while rook still had them */
7877 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7878 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7879 rights++; /* but at least one rook lost them */
7881 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7883 if( b1[CASTLING][5] != NoRights ) {
7884 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7891 Adjudicate (ChessProgramState *cps)
7892 { // [HGM] some adjudications useful with buggy engines
7893 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7894 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7895 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7896 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7897 int k, drop, count = 0; static int bare = 1;
7898 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7899 Boolean canAdjudicate = !appData.icsActive;
7901 // most tests only when we understand the game, i.e. legality-checking on
7902 if( appData.testLegality )
7903 { /* [HGM] Some more adjudications for obstinate engines */
7904 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7905 static int moveCount = 6;
7907 char *reason = NULL;
7909 /* Count what is on board. */
7910 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7912 /* Some material-based adjudications that have to be made before stalemate test */
7913 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7914 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7915 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7916 if(canAdjudicate && appData.checkMates) {
7918 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7919 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7920 "Xboard adjudication: King destroyed", GE_XBOARD );
7925 /* Bare King in Shatranj (loses) or Losers (wins) */
7926 if( nrW == 1 || nrB == 1) {
7927 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7928 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7929 if(canAdjudicate && appData.checkMates) {
7931 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7932 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7933 "Xboard adjudication: Bare king", GE_XBOARD );
7937 if( gameInfo.variant == VariantShatranj && --bare < 0)
7939 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7940 if(canAdjudicate && appData.checkMates) {
7941 /* but only adjudicate if adjudication enabled */
7943 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7944 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7945 "Xboard adjudication: Bare king", GE_XBOARD );
7952 // don't wait for engine to announce game end if we can judge ourselves
7953 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7955 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7956 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7957 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7958 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7961 reason = "Xboard adjudication: 3rd check";
7962 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7972 reason = "Xboard adjudication: Stalemate";
7973 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7974 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7975 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7976 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7977 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7978 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7979 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7980 EP_CHECKMATE : EP_WINS);
7981 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7982 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7986 reason = "Xboard adjudication: Checkmate";
7987 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7988 if(gameInfo.variant == VariantShogi) {
7989 if(forwardMostMove > backwardMostMove
7990 && moveList[forwardMostMove-1][1] == '@'
7991 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7992 reason = "XBoard adjudication: pawn-drop mate";
7993 boards[forwardMostMove][EP_STATUS] = EP_WINS;
7999 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8001 result = GameIsDrawn; break;
8003 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8005 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8009 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8011 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8012 GameEnds( result, reason, GE_XBOARD );
8016 /* Next absolutely insufficient mating material. */
8017 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8018 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8019 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8021 /* always flag draws, for judging claims */
8022 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8024 if(canAdjudicate && appData.materialDraws) {
8025 /* but only adjudicate them if adjudication enabled */
8026 if(engineOpponent) {
8027 SendToProgram("force\n", engineOpponent); // suppress reply
8028 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8030 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8035 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8036 if(gameInfo.variant == VariantXiangqi ?
8037 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8039 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8040 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8041 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8042 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8044 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8045 { /* if the first 3 moves do not show a tactical win, declare draw */
8046 if(engineOpponent) {
8047 SendToProgram("force\n", engineOpponent); // suppress reply
8048 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8050 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8053 } else moveCount = 6;
8056 // Repetition draws and 50-move rule can be applied independently of legality testing
8058 /* Check for rep-draws */
8060 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8061 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8062 for(k = forwardMostMove-2;
8063 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8064 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8065 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8068 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8069 /* compare castling rights */
8070 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8071 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8072 rights++; /* King lost rights, while rook still had them */
8073 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8074 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8075 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8076 rights++; /* but at least one rook lost them */
8078 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8079 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8081 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8082 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8083 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8086 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8087 && appData.drawRepeats > 1) {
8088 /* adjudicate after user-specified nr of repeats */
8089 int result = GameIsDrawn;
8090 char *details = "XBoard adjudication: repetition draw";
8091 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8092 // [HGM] xiangqi: check for forbidden perpetuals
8093 int m, ourPerpetual = 1, hisPerpetual = 1;
8094 for(m=forwardMostMove; m>k; m-=2) {
8095 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8096 ourPerpetual = 0; // the current mover did not always check
8097 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8098 hisPerpetual = 0; // the opponent did not always check
8100 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8101 ourPerpetual, hisPerpetual);
8102 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8103 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8104 details = "Xboard adjudication: perpetual checking";
8106 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8107 break; // (or we would have caught him before). Abort repetition-checking loop.
8109 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8110 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8112 details = "Xboard adjudication: repetition";
8114 } else // it must be XQ
8115 // Now check for perpetual chases
8116 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8117 hisPerpetual = PerpetualChase(k, forwardMostMove);
8118 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8119 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8120 static char resdet[MSG_SIZ];
8121 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8123 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8125 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8126 break; // Abort repetition-checking loop.
8128 // if neither of us is checking or chasing all the time, or both are, it is draw
8130 if(engineOpponent) {
8131 SendToProgram("force\n", engineOpponent); // suppress reply
8132 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8134 GameEnds( result, details, GE_XBOARD );
8137 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8138 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8142 /* Now we test for 50-move draws. Determine ply count */
8143 count = forwardMostMove;
8144 /* look for last irreversble move */
8145 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8147 /* if we hit starting position, add initial plies */
8148 if( count == backwardMostMove )
8149 count -= initialRulePlies;
8150 count = forwardMostMove - count;
8151 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8152 // adjust reversible move counter for checks in Xiangqi
8153 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8154 if(i < backwardMostMove) i = backwardMostMove;
8155 while(i <= forwardMostMove) {
8156 lastCheck = inCheck; // check evasion does not count
8157 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8158 if(inCheck || lastCheck) count--; // check does not count
8163 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8164 /* this is used to judge if draw claims are legal */
8165 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8166 if(engineOpponent) {
8167 SendToProgram("force\n", engineOpponent); // suppress reply
8168 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8170 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8174 /* if draw offer is pending, treat it as a draw claim
8175 * when draw condition present, to allow engines a way to
8176 * claim draws before making their move to avoid a race
8177 * condition occurring after their move
8179 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8181 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8182 p = "Draw claim: 50-move rule";
8183 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8184 p = "Draw claim: 3-fold repetition";
8185 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8186 p = "Draw claim: insufficient mating material";
8187 if( p != NULL && canAdjudicate) {
8188 if(engineOpponent) {
8189 SendToProgram("force\n", engineOpponent); // suppress reply
8190 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8192 GameEnds( GameIsDrawn, p, GE_XBOARD );
8197 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8198 if(engineOpponent) {
8199 SendToProgram("force\n", engineOpponent); // suppress reply
8200 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8202 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8209 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8210 { // [HGM] book: this routine intercepts moves to simulate book replies
8211 char *bookHit = NULL;
8213 //first determine if the incoming move brings opponent into his book
8214 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8215 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8216 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8217 if(bookHit != NULL && !cps->bookSuspend) {
8218 // make sure opponent is not going to reply after receiving move to book position
8219 SendToProgram("force\n", cps);
8220 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8222 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8223 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8224 // now arrange restart after book miss
8226 // after a book hit we never send 'go', and the code after the call to this routine
8227 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8228 char buf[MSG_SIZ], *move = bookHit;
8230 int fromX, fromY, toX, toY;
8234 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8235 &fromX, &fromY, &toX, &toY, &promoChar)) {
8236 (void) CoordsToAlgebraic(boards[forwardMostMove],
8237 PosFlags(forwardMostMove),
8238 fromY, fromX, toY, toX, promoChar, move);
8240 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8244 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8245 SendToProgram(buf, cps);
8246 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8247 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8248 SendToProgram("go\n", cps);
8249 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8250 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8251 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8252 SendToProgram("go\n", cps);
8253 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8255 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8259 LoadError (char *errmess, ChessProgramState *cps)
8260 { // unloads engine and switches back to -ncp mode if it was first
8261 if(cps->initDone) return FALSE;
8262 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8263 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8266 appData.noChessProgram = TRUE;
8267 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8268 gameMode = BeginningOfGame; ModeHighlight();
8271 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8272 DisplayMessage("", ""); // erase waiting message
8273 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8278 ChessProgramState *savedState;
8280 DeferredBookMove (void)
8282 if(savedState->lastPing != savedState->lastPong)
8283 ScheduleDelayedEvent(DeferredBookMove, 10);
8285 HandleMachineMove(savedMessage, savedState);
8288 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8289 static ChessProgramState *stalledEngine;
8290 static char stashedInputMove[MSG_SIZ];
8293 HandleMachineMove (char *message, ChessProgramState *cps)
8295 static char firstLeg[20];
8296 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8297 char realname[MSG_SIZ];
8298 int fromX, fromY, toX, toY;
8300 char promoChar, roar;
8302 int machineWhite, oldError;
8305 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8306 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8307 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8308 DisplayError(_("Invalid pairing from pairing engine"), 0);
8311 pairingReceived = 1;
8313 return; // Skim the pairing messages here.
8316 oldError = cps->userError; cps->userError = 0;
8318 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8320 * Kludge to ignore BEL characters
8322 while (*message == '\007') message++;
8325 * [HGM] engine debug message: ignore lines starting with '#' character
8327 if(cps->debug && *message == '#') return;
8330 * Look for book output
8332 if (cps == &first && bookRequested) {
8333 if (message[0] == '\t' || message[0] == ' ') {
8334 /* Part of the book output is here; append it */
8335 strcat(bookOutput, message);
8336 strcat(bookOutput, " \n");
8338 } else if (bookOutput[0] != NULLCHAR) {
8339 /* All of book output has arrived; display it */
8340 char *p = bookOutput;
8341 while (*p != NULLCHAR) {
8342 if (*p == '\t') *p = ' ';
8345 DisplayInformation(bookOutput);
8346 bookRequested = FALSE;
8347 /* Fall through to parse the current output */
8352 * Look for machine move.
8354 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8355 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8357 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8358 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8359 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8360 stalledEngine = cps;
8361 if(appData.ponderNextMove) { // bring opponent out of ponder
8362 if(gameMode == TwoMachinesPlay) {
8363 if(cps->other->pause)
8364 PauseEngine(cps->other);
8366 SendToProgram("easy\n", cps->other);
8373 /* This method is only useful on engines that support ping */
8374 if (cps->lastPing != cps->lastPong) {
8375 if (gameMode == BeginningOfGame) {
8376 /* Extra move from before last new; ignore */
8377 if (appData.debugMode) {
8378 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8381 if (appData.debugMode) {
8382 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8383 cps->which, gameMode);
8386 SendToProgram("undo\n", cps);
8392 case BeginningOfGame:
8393 /* Extra move from before last reset; ignore */
8394 if (appData.debugMode) {
8395 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8402 /* Extra move after we tried to stop. The mode test is
8403 not a reliable way of detecting this problem, but it's
8404 the best we can do on engines that don't support ping.
8406 if (appData.debugMode) {
8407 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8408 cps->which, gameMode);
8410 SendToProgram("undo\n", cps);
8413 case MachinePlaysWhite:
8414 case IcsPlayingWhite:
8415 machineWhite = TRUE;
8418 case MachinePlaysBlack:
8419 case IcsPlayingBlack:
8420 machineWhite = FALSE;
8423 case TwoMachinesPlay:
8424 machineWhite = (cps->twoMachinesColor[0] == 'w');
8427 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8428 if (appData.debugMode) {
8430 "Ignoring move out of turn by %s, gameMode %d"
8431 ", forwardMost %d\n",
8432 cps->which, gameMode, forwardMostMove);
8437 if(cps->alphaRank) AlphaRank(machineMove, 4);
8439 // [HGM] lion: (some very limited) support for Alien protocol
8441 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8442 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8444 } else if(firstLeg[0]) { // there was a previous leg;
8445 // only support case where same piece makes two step (and don't even test that!)
8446 char buf[20], *p = machineMove+1, *q = buf+1, f;
8447 safeStrCpy(buf, machineMove, 20);
8448 while(isdigit(*q)) q++; // find start of to-square
8449 safeStrCpy(machineMove, firstLeg, 20);
8450 while(isdigit(*p)) p++;
8451 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8452 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8453 firstLeg[0] = NULLCHAR;
8456 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8457 &fromX, &fromY, &toX, &toY, &promoChar)) {
8458 /* Machine move could not be parsed; ignore it. */
8459 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8460 machineMove, _(cps->which));
8461 DisplayMoveError(buf1);
8462 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8463 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8464 if (gameMode == TwoMachinesPlay) {
8465 GameEnds(machineWhite ? BlackWins : WhiteWins,
8471 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8472 /* So we have to redo legality test with true e.p. status here, */
8473 /* to make sure an illegal e.p. capture does not slip through, */
8474 /* to cause a forfeit on a justified illegal-move complaint */
8475 /* of the opponent. */
8476 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8478 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8479 fromY, fromX, toY, toX, promoChar);
8480 if(moveType == IllegalMove) {
8481 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8482 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8483 GameEnds(machineWhite ? BlackWins : WhiteWins,
8486 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8487 /* [HGM] Kludge to handle engines that send FRC-style castling
8488 when they shouldn't (like TSCP-Gothic) */
8490 case WhiteASideCastleFR:
8491 case BlackASideCastleFR:
8493 currentMoveString[2]++;
8495 case WhiteHSideCastleFR:
8496 case BlackHSideCastleFR:
8498 currentMoveString[2]--;
8500 default: ; // nothing to do, but suppresses warning of pedantic compilers
8503 hintRequested = FALSE;
8504 lastHint[0] = NULLCHAR;
8505 bookRequested = FALSE;
8506 /* Program may be pondering now */
8507 cps->maybeThinking = TRUE;
8508 if (cps->sendTime == 2) cps->sendTime = 1;
8509 if (cps->offeredDraw) cps->offeredDraw--;
8511 /* [AS] Save move info*/
8512 pvInfoList[ forwardMostMove ].score = programStats.score;
8513 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8514 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8516 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8518 /* Test suites abort the 'game' after one move */
8519 if(*appData.finger) {
8521 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8522 if(!f) f = fopen(appData.finger, "w");
8523 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8524 else { DisplayFatalError("Bad output file", errno, 0); return; }
8526 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8529 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8530 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8533 while( count < adjudicateLossPlies ) {
8534 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8537 score = -score; /* Flip score for winning side */
8540 if( score > adjudicateLossThreshold ) {
8547 if( count >= adjudicateLossPlies ) {
8548 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8550 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8551 "Xboard adjudication",
8558 if(Adjudicate(cps)) {
8559 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8560 return; // [HGM] adjudicate: for all automatic game ends
8564 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8566 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8567 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8569 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8571 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8573 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8574 char buf[3*MSG_SIZ];
8576 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8577 programStats.score / 100.,
8579 programStats.time / 100.,
8580 (unsigned int)programStats.nodes,
8581 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8582 programStats.movelist);
8584 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8589 /* [AS] Clear stats for next move */
8590 ClearProgramStats();
8591 thinkOutput[0] = NULLCHAR;
8592 hiddenThinkOutputState = 0;
8595 if (gameMode == TwoMachinesPlay) {
8596 /* [HGM] relaying draw offers moved to after reception of move */
8597 /* and interpreting offer as claim if it brings draw condition */
8598 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8599 SendToProgram("draw\n", cps->other);
8601 if (cps->other->sendTime) {
8602 SendTimeRemaining(cps->other,
8603 cps->other->twoMachinesColor[0] == 'w');
8605 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8606 if (firstMove && !bookHit) {
8608 if (cps->other->useColors) {
8609 SendToProgram(cps->other->twoMachinesColor, cps->other);
8611 SendToProgram("go\n", cps->other);
8613 cps->other->maybeThinking = TRUE;
8616 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8618 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8620 if (!pausing && appData.ringBellAfterMoves) {
8621 if(!roar) RingBell();
8625 * Reenable menu items that were disabled while
8626 * machine was thinking
8628 if (gameMode != TwoMachinesPlay)
8629 SetUserThinkingEnables();
8631 // [HGM] book: after book hit opponent has received move and is now in force mode
8632 // force the book reply into it, and then fake that it outputted this move by jumping
8633 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8635 static char bookMove[MSG_SIZ]; // a bit generous?
8637 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8638 strcat(bookMove, bookHit);
8641 programStats.nodes = programStats.depth = programStats.time =
8642 programStats.score = programStats.got_only_move = 0;
8643 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8645 if(cps->lastPing != cps->lastPong) {
8646 savedMessage = message; // args for deferred call
8648 ScheduleDelayedEvent(DeferredBookMove, 10);
8657 /* Set special modes for chess engines. Later something general
8658 * could be added here; for now there is just one kludge feature,
8659 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8660 * when "xboard" is given as an interactive command.
8662 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8663 cps->useSigint = FALSE;
8664 cps->useSigterm = FALSE;
8666 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8667 ParseFeatures(message+8, cps);
8668 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8671 if (!strncmp(message, "setup ", 6) &&
8672 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8673 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8674 ) { // [HGM] allow first engine to define opening position
8675 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8676 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8678 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8679 if(startedFromSetupPosition) return;
8680 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8682 while(message[s] && message[s++] != ' ');
8683 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8684 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8685 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8686 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8687 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8688 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8691 ParseFEN(boards[0], &dummy, message+s, FALSE);
8692 DrawPosition(TRUE, boards[0]);
8693 startedFromSetupPosition = TRUE;
8696 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8697 * want this, I was asked to put it in, and obliged.
8699 if (!strncmp(message, "setboard ", 9)) {
8700 Board initial_position;
8702 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8704 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8705 DisplayError(_("Bad FEN received from engine"), 0);
8709 CopyBoard(boards[0], initial_position);
8710 initialRulePlies = FENrulePlies;
8711 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8712 else gameMode = MachinePlaysBlack;
8713 DrawPosition(FALSE, boards[currentMove]);
8719 * Look for communication commands
8721 if (!strncmp(message, "telluser ", 9)) {
8722 if(message[9] == '\\' && message[10] == '\\')
8723 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8725 DisplayNote(message + 9);
8728 if (!strncmp(message, "tellusererror ", 14)) {
8730 if(message[14] == '\\' && message[15] == '\\')
8731 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8733 DisplayError(message + 14, 0);
8736 if (!strncmp(message, "tellopponent ", 13)) {
8737 if (appData.icsActive) {
8739 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8743 DisplayNote(message + 13);
8747 if (!strncmp(message, "tellothers ", 11)) {
8748 if (appData.icsActive) {
8750 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8753 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8756 if (!strncmp(message, "tellall ", 8)) {
8757 if (appData.icsActive) {
8759 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8763 DisplayNote(message + 8);
8767 if (strncmp(message, "warning", 7) == 0) {
8768 /* Undocumented feature, use tellusererror in new code */
8769 DisplayError(message, 0);
8772 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8773 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8774 strcat(realname, " query");
8775 AskQuestion(realname, buf2, buf1, cps->pr);
8778 /* Commands from the engine directly to ICS. We don't allow these to be
8779 * sent until we are logged on. Crafty kibitzes have been known to
8780 * interfere with the login process.
8783 if (!strncmp(message, "tellics ", 8)) {
8784 SendToICS(message + 8);
8788 if (!strncmp(message, "tellicsnoalias ", 15)) {
8789 SendToICS(ics_prefix);
8790 SendToICS(message + 15);
8794 /* The following are for backward compatibility only */
8795 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8796 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8797 SendToICS(ics_prefix);
8803 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8804 if(initPing == cps->lastPong) {
8805 if(gameInfo.variant == VariantUnknown) {
8806 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8807 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8808 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8814 if(!strncmp(message, "highlight ", 10)) {
8815 if(appData.testLegality && appData.markers) return;
8816 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8819 if(!strncmp(message, "click ", 6)) {
8820 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8821 if(appData.testLegality || !appData.oneClick) return;
8822 sscanf(message+6, "%c%d%c", &f, &y, &c);
8823 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8824 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8825 x = x*squareSize + (x+1)*lineGap + squareSize/2;
8826 y = y*squareSize + (y+1)*lineGap + squareSize/2;
8827 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8828 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8829 LeftClick(Release, lastLeftX, lastLeftY);
8830 controlKey = (c == ',');
8831 LeftClick(Press, x, y);
8832 LeftClick(Release, x, y);
8833 first.highlight = f;
8837 * If the move is illegal, cancel it and redraw the board.
8838 * Also deal with other error cases. Matching is rather loose
8839 * here to accommodate engines written before the spec.
8841 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8842 strncmp(message, "Error", 5) == 0) {
8843 if (StrStr(message, "name") ||
8844 StrStr(message, "rating") || StrStr(message, "?") ||
8845 StrStr(message, "result") || StrStr(message, "board") ||
8846 StrStr(message, "bk") || StrStr(message, "computer") ||
8847 StrStr(message, "variant") || StrStr(message, "hint") ||
8848 StrStr(message, "random") || StrStr(message, "depth") ||
8849 StrStr(message, "accepted")) {
8852 if (StrStr(message, "protover")) {
8853 /* Program is responding to input, so it's apparently done
8854 initializing, and this error message indicates it is
8855 protocol version 1. So we don't need to wait any longer
8856 for it to initialize and send feature commands. */
8857 FeatureDone(cps, 1);
8858 cps->protocolVersion = 1;
8861 cps->maybeThinking = FALSE;
8863 if (StrStr(message, "draw")) {
8864 /* Program doesn't have "draw" command */
8865 cps->sendDrawOffers = 0;
8868 if (cps->sendTime != 1 &&
8869 (StrStr(message, "time") || StrStr(message, "otim"))) {
8870 /* Program apparently doesn't have "time" or "otim" command */
8874 if (StrStr(message, "analyze")) {
8875 cps->analysisSupport = FALSE;
8876 cps->analyzing = FALSE;
8877 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8878 EditGameEvent(); // [HGM] try to preserve loaded game
8879 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8880 DisplayError(buf2, 0);
8883 if (StrStr(message, "(no matching move)st")) {
8884 /* Special kludge for GNU Chess 4 only */
8885 cps->stKludge = TRUE;
8886 SendTimeControl(cps, movesPerSession, timeControl,
8887 timeIncrement, appData.searchDepth,
8891 if (StrStr(message, "(no matching move)sd")) {
8892 /* Special kludge for GNU Chess 4 only */
8893 cps->sdKludge = TRUE;
8894 SendTimeControl(cps, movesPerSession, timeControl,
8895 timeIncrement, appData.searchDepth,
8899 if (!StrStr(message, "llegal")) {
8902 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8903 gameMode == IcsIdle) return;
8904 if (forwardMostMove <= backwardMostMove) return;
8905 if (pausing) PauseEvent();
8906 if(appData.forceIllegal) {
8907 // [HGM] illegal: machine refused move; force position after move into it
8908 SendToProgram("force\n", cps);
8909 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8910 // we have a real problem now, as SendBoard will use the a2a3 kludge
8911 // when black is to move, while there might be nothing on a2 or black
8912 // might already have the move. So send the board as if white has the move.
8913 // But first we must change the stm of the engine, as it refused the last move
8914 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8915 if(WhiteOnMove(forwardMostMove)) {
8916 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8917 SendBoard(cps, forwardMostMove); // kludgeless board
8919 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8920 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8921 SendBoard(cps, forwardMostMove+1); // kludgeless board
8923 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8924 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8925 gameMode == TwoMachinesPlay)
8926 SendToProgram("go\n", cps);
8929 if (gameMode == PlayFromGameFile) {
8930 /* Stop reading this game file */
8931 gameMode = EditGame;
8934 /* [HGM] illegal-move claim should forfeit game when Xboard */
8935 /* only passes fully legal moves */
8936 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8937 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8938 "False illegal-move claim", GE_XBOARD );
8939 return; // do not take back move we tested as valid
8941 currentMove = forwardMostMove-1;
8942 DisplayMove(currentMove-1); /* before DisplayMoveError */
8943 SwitchClocks(forwardMostMove-1); // [HGM] race
8944 DisplayBothClocks();
8945 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8946 parseList[currentMove], _(cps->which));
8947 DisplayMoveError(buf1);
8948 DrawPosition(FALSE, boards[currentMove]);
8950 SetUserThinkingEnables();
8953 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8954 /* Program has a broken "time" command that
8955 outputs a string not ending in newline.
8961 * If chess program startup fails, exit with an error message.
8962 * Attempts to recover here are futile. [HGM] Well, we try anyway
8964 if ((StrStr(message, "unknown host") != NULL)
8965 || (StrStr(message, "No remote directory") != NULL)
8966 || (StrStr(message, "not found") != NULL)
8967 || (StrStr(message, "No such file") != NULL)
8968 || (StrStr(message, "can't alloc") != NULL)
8969 || (StrStr(message, "Permission denied") != NULL)) {
8971 cps->maybeThinking = FALSE;
8972 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8973 _(cps->which), cps->program, cps->host, message);
8974 RemoveInputSource(cps->isr);
8975 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8976 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8977 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8983 * Look for hint output
8985 if (sscanf(message, "Hint: %s", buf1) == 1) {
8986 if (cps == &first && hintRequested) {
8987 hintRequested = FALSE;
8988 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8989 &fromX, &fromY, &toX, &toY, &promoChar)) {
8990 (void) CoordsToAlgebraic(boards[forwardMostMove],
8991 PosFlags(forwardMostMove),
8992 fromY, fromX, toY, toX, promoChar, buf1);
8993 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8994 DisplayInformation(buf2);
8996 /* Hint move could not be parsed!? */
8997 snprintf(buf2, sizeof(buf2),
8998 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8999 buf1, _(cps->which));
9000 DisplayError(buf2, 0);
9003 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9009 * Ignore other messages if game is not in progress
9011 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9012 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9015 * look for win, lose, draw, or draw offer
9017 if (strncmp(message, "1-0", 3) == 0) {
9018 char *p, *q, *r = "";
9019 p = strchr(message, '{');
9027 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9029 } else if (strncmp(message, "0-1", 3) == 0) {
9030 char *p, *q, *r = "";
9031 p = strchr(message, '{');
9039 /* Kludge for Arasan 4.1 bug */
9040 if (strcmp(r, "Black resigns") == 0) {
9041 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9044 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9046 } else if (strncmp(message, "1/2", 3) == 0) {
9047 char *p, *q, *r = "";
9048 p = strchr(message, '{');
9057 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9060 } else if (strncmp(message, "White resign", 12) == 0) {
9061 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9063 } else if (strncmp(message, "Black resign", 12) == 0) {
9064 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9066 } else if (strncmp(message, "White matches", 13) == 0 ||
9067 strncmp(message, "Black matches", 13) == 0 ) {
9068 /* [HGM] ignore GNUShogi noises */
9070 } else if (strncmp(message, "White", 5) == 0 &&
9071 message[5] != '(' &&
9072 StrStr(message, "Black") == NULL) {
9073 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9075 } else if (strncmp(message, "Black", 5) == 0 &&
9076 message[5] != '(') {
9077 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9079 } else if (strcmp(message, "resign") == 0 ||
9080 strcmp(message, "computer resigns") == 0) {
9082 case MachinePlaysBlack:
9083 case IcsPlayingBlack:
9084 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9086 case MachinePlaysWhite:
9087 case IcsPlayingWhite:
9088 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9090 case TwoMachinesPlay:
9091 if (cps->twoMachinesColor[0] == 'w')
9092 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9094 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9101 } else if (strncmp(message, "opponent mates", 14) == 0) {
9103 case MachinePlaysBlack:
9104 case IcsPlayingBlack:
9105 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9107 case MachinePlaysWhite:
9108 case IcsPlayingWhite:
9109 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9111 case TwoMachinesPlay:
9112 if (cps->twoMachinesColor[0] == 'w')
9113 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9115 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9122 } else if (strncmp(message, "computer mates", 14) == 0) {
9124 case MachinePlaysBlack:
9125 case IcsPlayingBlack:
9126 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9128 case MachinePlaysWhite:
9129 case IcsPlayingWhite:
9130 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9132 case TwoMachinesPlay:
9133 if (cps->twoMachinesColor[0] == 'w')
9134 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9136 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9143 } else if (strncmp(message, "checkmate", 9) == 0) {
9144 if (WhiteOnMove(forwardMostMove)) {
9145 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9147 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9150 } else if (strstr(message, "Draw") != NULL ||
9151 strstr(message, "game is a draw") != NULL) {
9152 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9154 } else if (strstr(message, "offer") != NULL &&
9155 strstr(message, "draw") != NULL) {
9157 if (appData.zippyPlay && first.initDone) {
9158 /* Relay offer to ICS */
9159 SendToICS(ics_prefix);
9160 SendToICS("draw\n");
9163 cps->offeredDraw = 2; /* valid until this engine moves twice */
9164 if (gameMode == TwoMachinesPlay) {
9165 if (cps->other->offeredDraw) {
9166 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9167 /* [HGM] in two-machine mode we delay relaying draw offer */
9168 /* until after we also have move, to see if it is really claim */
9170 } else if (gameMode == MachinePlaysWhite ||
9171 gameMode == MachinePlaysBlack) {
9172 if (userOfferedDraw) {
9173 DisplayInformation(_("Machine accepts your draw offer"));
9174 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9176 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9183 * Look for thinking output
9185 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9186 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9188 int plylev, mvleft, mvtot, curscore, time;
9189 char mvname[MOVE_LEN];
9193 int prefixHint = FALSE;
9194 mvname[0] = NULLCHAR;
9197 case MachinePlaysBlack:
9198 case IcsPlayingBlack:
9199 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9201 case MachinePlaysWhite:
9202 case IcsPlayingWhite:
9203 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9208 case IcsObserving: /* [DM] icsEngineAnalyze */
9209 if (!appData.icsEngineAnalyze) ignore = TRUE;
9211 case TwoMachinesPlay:
9212 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9222 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9224 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9225 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9227 if (plyext != ' ' && plyext != '\t') {
9231 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9232 if( cps->scoreIsAbsolute &&
9233 ( gameMode == MachinePlaysBlack ||
9234 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9235 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9236 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9237 !WhiteOnMove(currentMove)
9240 curscore = -curscore;
9243 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9245 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9248 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9249 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9250 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9251 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9252 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9253 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9257 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9258 DisplayError(_("failed writing PV"), 0);
9261 tempStats.depth = plylev;
9262 tempStats.nodes = nodes;
9263 tempStats.time = time;
9264 tempStats.score = curscore;
9265 tempStats.got_only_move = 0;
9267 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9270 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9271 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9272 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9273 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9274 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9275 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9276 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9277 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9280 /* Buffer overflow protection */
9281 if (pv[0] != NULLCHAR) {
9282 if (strlen(pv) >= sizeof(tempStats.movelist)
9283 && appData.debugMode) {
9285 "PV is too long; using the first %u bytes.\n",
9286 (unsigned) sizeof(tempStats.movelist) - 1);
9289 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9291 sprintf(tempStats.movelist, " no PV\n");
9294 if (tempStats.seen_stat) {
9295 tempStats.ok_to_send = 1;
9298 if (strchr(tempStats.movelist, '(') != NULL) {
9299 tempStats.line_is_book = 1;
9300 tempStats.nr_moves = 0;
9301 tempStats.moves_left = 0;
9303 tempStats.line_is_book = 0;
9306 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9307 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9309 SendProgramStatsToFrontend( cps, &tempStats );
9312 [AS] Protect the thinkOutput buffer from overflow... this
9313 is only useful if buf1 hasn't overflowed first!
9315 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9317 (gameMode == TwoMachinesPlay ?
9318 ToUpper(cps->twoMachinesColor[0]) : ' '),
9319 ((double) curscore) / 100.0,
9320 prefixHint ? lastHint : "",
9321 prefixHint ? " " : "" );
9323 if( buf1[0] != NULLCHAR ) {
9324 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9326 if( strlen(pv) > max_len ) {
9327 if( appData.debugMode) {
9328 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9330 pv[max_len+1] = '\0';
9333 strcat( thinkOutput, pv);
9336 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9337 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9338 DisplayMove(currentMove - 1);
9342 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9343 /* crafty (9.25+) says "(only move) <move>"
9344 * if there is only 1 legal move
9346 sscanf(p, "(only move) %s", buf1);
9347 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9348 sprintf(programStats.movelist, "%s (only move)", buf1);
9349 programStats.depth = 1;
9350 programStats.nr_moves = 1;
9351 programStats.moves_left = 1;
9352 programStats.nodes = 1;
9353 programStats.time = 1;
9354 programStats.got_only_move = 1;
9356 /* Not really, but we also use this member to
9357 mean "line isn't going to change" (Crafty
9358 isn't searching, so stats won't change) */
9359 programStats.line_is_book = 1;
9361 SendProgramStatsToFrontend( cps, &programStats );
9363 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9364 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9365 DisplayMove(currentMove - 1);
9368 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9369 &time, &nodes, &plylev, &mvleft,
9370 &mvtot, mvname) >= 5) {
9371 /* The stat01: line is from Crafty (9.29+) in response
9372 to the "." command */
9373 programStats.seen_stat = 1;
9374 cps->maybeThinking = TRUE;
9376 if (programStats.got_only_move || !appData.periodicUpdates)
9379 programStats.depth = plylev;
9380 programStats.time = time;
9381 programStats.nodes = nodes;
9382 programStats.moves_left = mvleft;
9383 programStats.nr_moves = mvtot;
9384 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9385 programStats.ok_to_send = 1;
9386 programStats.movelist[0] = '\0';
9388 SendProgramStatsToFrontend( cps, &programStats );
9392 } else if (strncmp(message,"++",2) == 0) {
9393 /* Crafty 9.29+ outputs this */
9394 programStats.got_fail = 2;
9397 } else if (strncmp(message,"--",2) == 0) {
9398 /* Crafty 9.29+ outputs this */
9399 programStats.got_fail = 1;
9402 } else if (thinkOutput[0] != NULLCHAR &&
9403 strncmp(message, " ", 4) == 0) {
9404 unsigned message_len;
9407 while (*p && *p == ' ') p++;
9409 message_len = strlen( p );
9411 /* [AS] Avoid buffer overflow */
9412 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9413 strcat(thinkOutput, " ");
9414 strcat(thinkOutput, p);
9417 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9418 strcat(programStats.movelist, " ");
9419 strcat(programStats.movelist, p);
9422 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9423 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9424 DisplayMove(currentMove - 1);
9432 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9433 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9435 ChessProgramStats cpstats;
9437 if (plyext != ' ' && plyext != '\t') {
9441 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9442 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9443 curscore = -curscore;
9446 cpstats.depth = plylev;
9447 cpstats.nodes = nodes;
9448 cpstats.time = time;
9449 cpstats.score = curscore;
9450 cpstats.got_only_move = 0;
9451 cpstats.movelist[0] = '\0';
9453 if (buf1[0] != NULLCHAR) {
9454 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9457 cpstats.ok_to_send = 0;
9458 cpstats.line_is_book = 0;
9459 cpstats.nr_moves = 0;
9460 cpstats.moves_left = 0;
9462 SendProgramStatsToFrontend( cps, &cpstats );
9469 /* Parse a game score from the character string "game", and
9470 record it as the history of the current game. The game
9471 score is NOT assumed to start from the standard position.
9472 The display is not updated in any way.
9475 ParseGameHistory (char *game)
9478 int fromX, fromY, toX, toY, boardIndex;
9483 if (appData.debugMode)
9484 fprintf(debugFP, "Parsing game history: %s\n", game);
9486 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9487 gameInfo.site = StrSave(appData.icsHost);
9488 gameInfo.date = PGNDate();
9489 gameInfo.round = StrSave("-");
9491 /* Parse out names of players */
9492 while (*game == ' ') game++;
9494 while (*game != ' ') *p++ = *game++;
9496 gameInfo.white = StrSave(buf);
9497 while (*game == ' ') game++;
9499 while (*game != ' ' && *game != '\n') *p++ = *game++;
9501 gameInfo.black = StrSave(buf);
9504 boardIndex = blackPlaysFirst ? 1 : 0;
9507 yyboardindex = boardIndex;
9508 moveType = (ChessMove) Myylex();
9510 case IllegalMove: /* maybe suicide chess, etc. */
9511 if (appData.debugMode) {
9512 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9513 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9514 setbuf(debugFP, NULL);
9516 case WhitePromotion:
9517 case BlackPromotion:
9518 case WhiteNonPromotion:
9519 case BlackNonPromotion:
9522 case WhiteCapturesEnPassant:
9523 case BlackCapturesEnPassant:
9524 case WhiteKingSideCastle:
9525 case WhiteQueenSideCastle:
9526 case BlackKingSideCastle:
9527 case BlackQueenSideCastle:
9528 case WhiteKingSideCastleWild:
9529 case WhiteQueenSideCastleWild:
9530 case BlackKingSideCastleWild:
9531 case BlackQueenSideCastleWild:
9533 case WhiteHSideCastleFR:
9534 case WhiteASideCastleFR:
9535 case BlackHSideCastleFR:
9536 case BlackASideCastleFR:
9538 fromX = currentMoveString[0] - AAA;
9539 fromY = currentMoveString[1] - ONE;
9540 toX = currentMoveString[2] - AAA;
9541 toY = currentMoveString[3] - ONE;
9542 promoChar = currentMoveString[4];
9546 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9547 fromX = moveType == WhiteDrop ?
9548 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9549 (int) CharToPiece(ToLower(currentMoveString[0]));
9551 toX = currentMoveString[2] - AAA;
9552 toY = currentMoveString[3] - ONE;
9553 promoChar = NULLCHAR;
9557 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9558 if (appData.debugMode) {
9559 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9560 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9561 setbuf(debugFP, NULL);
9563 DisplayError(buf, 0);
9565 case ImpossibleMove:
9567 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9568 if (appData.debugMode) {
9569 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9570 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9571 setbuf(debugFP, NULL);
9573 DisplayError(buf, 0);
9576 if (boardIndex < backwardMostMove) {
9577 /* Oops, gap. How did that happen? */
9578 DisplayError(_("Gap in move list"), 0);
9581 backwardMostMove = blackPlaysFirst ? 1 : 0;
9582 if (boardIndex > forwardMostMove) {
9583 forwardMostMove = boardIndex;
9587 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9588 strcat(parseList[boardIndex-1], " ");
9589 strcat(parseList[boardIndex-1], yy_text);
9601 case GameUnfinished:
9602 if (gameMode == IcsExamining) {
9603 if (boardIndex < backwardMostMove) {
9604 /* Oops, gap. How did that happen? */
9607 backwardMostMove = blackPlaysFirst ? 1 : 0;
9610 gameInfo.result = moveType;
9611 p = strchr(yy_text, '{');
9612 if (p == NULL) p = strchr(yy_text, '(');
9615 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9617 q = strchr(p, *p == '{' ? '}' : ')');
9618 if (q != NULL) *q = NULLCHAR;
9621 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9622 gameInfo.resultDetails = StrSave(p);
9625 if (boardIndex >= forwardMostMove &&
9626 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9627 backwardMostMove = blackPlaysFirst ? 1 : 0;
9630 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9631 fromY, fromX, toY, toX, promoChar,
9632 parseList[boardIndex]);
9633 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9634 /* currentMoveString is set as a side-effect of yylex */
9635 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9636 strcat(moveList[boardIndex], "\n");
9638 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9639 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9645 if(gameInfo.variant != VariantShogi)
9646 strcat(parseList[boardIndex - 1], "+");
9650 strcat(parseList[boardIndex - 1], "#");
9657 /* Apply a move to the given board */
9659 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9661 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9662 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9664 /* [HGM] compute & store e.p. status and castling rights for new position */
9665 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9667 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9668 oldEP = (signed char)board[EP_STATUS];
9669 board[EP_STATUS] = EP_NONE;
9671 if (fromY == DROP_RANK) {
9673 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9674 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9677 piece = board[toY][toX] = (ChessSquare) fromX;
9682 if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9683 victim = board[killY][killX],
9684 board[killY][killX] = EmptySquare,
9685 board[EP_STATUS] = EP_CAPTURE;
9687 if( board[toY][toX] != EmptySquare ) {
9688 board[EP_STATUS] = EP_CAPTURE;
9689 if( (fromX != toX || fromY != toY) && // not igui!
9690 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9691 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
9692 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9696 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9697 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9698 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9700 if( board[fromY][fromX] == WhitePawn ) {
9701 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9702 board[EP_STATUS] = EP_PAWN_MOVE;
9704 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9705 gameInfo.variant != VariantBerolina || toX < fromX)
9706 board[EP_STATUS] = toX | berolina;
9707 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9708 gameInfo.variant != VariantBerolina || toX > fromX)
9709 board[EP_STATUS] = toX;
9712 if( board[fromY][fromX] == BlackPawn ) {
9713 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9714 board[EP_STATUS] = EP_PAWN_MOVE;
9715 if( toY-fromY== -2) {
9716 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9717 gameInfo.variant != VariantBerolina || toX < fromX)
9718 board[EP_STATUS] = toX | berolina;
9719 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9720 gameInfo.variant != VariantBerolina || toX > fromX)
9721 board[EP_STATUS] = toX;
9725 for(i=0; i<nrCastlingRights; i++) {
9726 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9727 board[CASTLING][i] == toX && castlingRank[i] == toY
9728 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9731 if(gameInfo.variant == VariantSChess) { // update virginity
9732 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9733 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9734 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9735 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9738 if (fromX == toX && fromY == toY) return;
9740 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9741 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9742 if(gameInfo.variant == VariantKnightmate)
9743 king += (int) WhiteUnicorn - (int) WhiteKing;
9745 /* Code added by Tord: */
9746 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9747 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9748 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9749 board[fromY][fromX] = EmptySquare;
9750 board[toY][toX] = EmptySquare;
9751 if((toX > fromX) != (piece == WhiteRook)) {
9752 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9754 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9756 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9757 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9758 board[fromY][fromX] = EmptySquare;
9759 board[toY][toX] = EmptySquare;
9760 if((toX > fromX) != (piece == BlackRook)) {
9761 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9763 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9765 /* End of code added by Tord */
9767 } else if (board[fromY][fromX] == king
9768 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9769 && toY == fromY && toX > fromX+1) {
9770 board[fromY][fromX] = EmptySquare;
9771 board[toY][toX] = king;
9772 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9773 board[fromY][BOARD_RGHT-1] = EmptySquare;
9774 } else if (board[fromY][fromX] == king
9775 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9776 && toY == fromY && toX < fromX-1) {
9777 board[fromY][fromX] = EmptySquare;
9778 board[toY][toX] = king;
9779 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9780 board[fromY][BOARD_LEFT] = EmptySquare;
9781 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9782 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9783 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9785 /* white pawn promotion */
9786 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9787 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9788 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9789 board[fromY][fromX] = EmptySquare;
9790 } else if ((fromY >= BOARD_HEIGHT>>1)
9791 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9793 && gameInfo.variant != VariantXiangqi
9794 && gameInfo.variant != VariantBerolina
9795 && (board[fromY][fromX] == WhitePawn)
9796 && (board[toY][toX] == EmptySquare)) {
9797 board[fromY][fromX] = EmptySquare;
9798 board[toY][toX] = WhitePawn;
9799 captured = board[toY - 1][toX];
9800 board[toY - 1][toX] = EmptySquare;
9801 } else if ((fromY == BOARD_HEIGHT-4)
9803 && gameInfo.variant == VariantBerolina
9804 && (board[fromY][fromX] == WhitePawn)
9805 && (board[toY][toX] == EmptySquare)) {
9806 board[fromY][fromX] = EmptySquare;
9807 board[toY][toX] = WhitePawn;
9808 if(oldEP & EP_BEROLIN_A) {
9809 captured = board[fromY][fromX-1];
9810 board[fromY][fromX-1] = EmptySquare;
9811 }else{ captured = board[fromY][fromX+1];
9812 board[fromY][fromX+1] = EmptySquare;
9814 } else if (board[fromY][fromX] == king
9815 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9816 && toY == fromY && toX > fromX+1) {
9817 board[fromY][fromX] = EmptySquare;
9818 board[toY][toX] = king;
9819 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9820 board[fromY][BOARD_RGHT-1] = EmptySquare;
9821 } else if (board[fromY][fromX] == king
9822 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9823 && toY == fromY && toX < fromX-1) {
9824 board[fromY][fromX] = EmptySquare;
9825 board[toY][toX] = king;
9826 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9827 board[fromY][BOARD_LEFT] = EmptySquare;
9828 } else if (fromY == 7 && fromX == 3
9829 && board[fromY][fromX] == BlackKing
9830 && toY == 7 && toX == 5) {
9831 board[fromY][fromX] = EmptySquare;
9832 board[toY][toX] = BlackKing;
9833 board[fromY][7] = EmptySquare;
9834 board[toY][4] = BlackRook;
9835 } else if (fromY == 7 && fromX == 3
9836 && board[fromY][fromX] == BlackKing
9837 && toY == 7 && toX == 1) {
9838 board[fromY][fromX] = EmptySquare;
9839 board[toY][toX] = BlackKing;
9840 board[fromY][0] = EmptySquare;
9841 board[toY][2] = BlackRook;
9842 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9843 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9844 && toY < promoRank && promoChar
9846 /* black pawn promotion */
9847 board[toY][toX] = CharToPiece(ToLower(promoChar));
9848 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9849 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9850 board[fromY][fromX] = EmptySquare;
9851 } else if ((fromY < BOARD_HEIGHT>>1)
9852 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9854 && gameInfo.variant != VariantXiangqi
9855 && gameInfo.variant != VariantBerolina
9856 && (board[fromY][fromX] == BlackPawn)
9857 && (board[toY][toX] == EmptySquare)) {
9858 board[fromY][fromX] = EmptySquare;
9859 board[toY][toX] = BlackPawn;
9860 captured = board[toY + 1][toX];
9861 board[toY + 1][toX] = EmptySquare;
9862 } else if ((fromY == 3)
9864 && gameInfo.variant == VariantBerolina
9865 && (board[fromY][fromX] == BlackPawn)
9866 && (board[toY][toX] == EmptySquare)) {
9867 board[fromY][fromX] = EmptySquare;
9868 board[toY][toX] = BlackPawn;
9869 if(oldEP & EP_BEROLIN_A) {
9870 captured = board[fromY][fromX-1];
9871 board[fromY][fromX-1] = EmptySquare;
9872 }else{ captured = board[fromY][fromX+1];
9873 board[fromY][fromX+1] = EmptySquare;
9876 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9877 board[fromY][fromX] = EmptySquare;
9878 board[toY][toX] = piece;
9882 if (gameInfo.holdingsWidth != 0) {
9884 /* !!A lot more code needs to be written to support holdings */
9885 /* [HGM] OK, so I have written it. Holdings are stored in the */
9886 /* penultimate board files, so they are automaticlly stored */
9887 /* in the game history. */
9888 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9889 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9890 /* Delete from holdings, by decreasing count */
9891 /* and erasing image if necessary */
9892 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9893 if(p < (int) BlackPawn) { /* white drop */
9894 p -= (int)WhitePawn;
9895 p = PieceToNumber((ChessSquare)p);
9896 if(p >= gameInfo.holdingsSize) p = 0;
9897 if(--board[p][BOARD_WIDTH-2] <= 0)
9898 board[p][BOARD_WIDTH-1] = EmptySquare;
9899 if((int)board[p][BOARD_WIDTH-2] < 0)
9900 board[p][BOARD_WIDTH-2] = 0;
9901 } else { /* black drop */
9902 p -= (int)BlackPawn;
9903 p = PieceToNumber((ChessSquare)p);
9904 if(p >= gameInfo.holdingsSize) p = 0;
9905 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9906 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9907 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9908 board[BOARD_HEIGHT-1-p][1] = 0;
9911 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9912 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9913 /* [HGM] holdings: Add to holdings, if holdings exist */
9914 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9915 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9916 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9919 if (p >= (int) BlackPawn) {
9920 p -= (int)BlackPawn;
9921 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9922 /* in Shogi restore piece to its original first */
9923 captured = (ChessSquare) (DEMOTED captured);
9926 p = PieceToNumber((ChessSquare)p);
9927 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9928 board[p][BOARD_WIDTH-2]++;
9929 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9931 p -= (int)WhitePawn;
9932 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9933 captured = (ChessSquare) (DEMOTED captured);
9936 p = PieceToNumber((ChessSquare)p);
9937 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9938 board[BOARD_HEIGHT-1-p][1]++;
9939 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9942 } else if (gameInfo.variant == VariantAtomic) {
9943 if (captured != EmptySquare) {
9945 for (y = toY-1; y <= toY+1; y++) {
9946 for (x = toX-1; x <= toX+1; x++) {
9947 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9948 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9949 board[y][x] = EmptySquare;
9953 board[toY][toX] = EmptySquare;
9956 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9957 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9959 if(promoChar == '+') {
9960 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9961 board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9962 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9963 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9964 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9965 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9966 board[toY][toX] = newPiece;
9968 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9969 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9970 // [HGM] superchess: take promotion piece out of holdings
9971 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9972 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9973 if(!--board[k][BOARD_WIDTH-2])
9974 board[k][BOARD_WIDTH-1] = EmptySquare;
9976 if(!--board[BOARD_HEIGHT-1-k][1])
9977 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9982 /* Updates forwardMostMove */
9984 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9986 int x = toX, y = toY;
9987 char *s = parseList[forwardMostMove];
9988 ChessSquare p = boards[forwardMostMove][toY][toX];
9989 // forwardMostMove++; // [HGM] bare: moved downstream
9991 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
9992 (void) CoordsToAlgebraic(boards[forwardMostMove],
9993 PosFlags(forwardMostMove),
9994 fromY, fromX, y, x, promoChar,
9996 if(killX >= 0 && killY >= 0)
9997 sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
9999 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10000 int timeLeft; static int lastLoadFlag=0; int king, piece;
10001 piece = boards[forwardMostMove][fromY][fromX];
10002 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10003 if(gameInfo.variant == VariantKnightmate)
10004 king += (int) WhiteUnicorn - (int) WhiteKing;
10005 if(forwardMostMove == 0) {
10006 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10007 fprintf(serverMoves, "%s;", UserName());
10008 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10009 fprintf(serverMoves, "%s;", second.tidy);
10010 fprintf(serverMoves, "%s;", first.tidy);
10011 if(gameMode == MachinePlaysWhite)
10012 fprintf(serverMoves, "%s;", UserName());
10013 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10014 fprintf(serverMoves, "%s;", second.tidy);
10015 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10016 lastLoadFlag = loadFlag;
10018 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10019 // print castling suffix
10020 if( toY == fromY && piece == king ) {
10022 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10024 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10027 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10028 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10029 boards[forwardMostMove][toY][toX] == EmptySquare
10030 && fromX != toX && fromY != toY)
10031 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10032 // promotion suffix
10033 if(promoChar != NULLCHAR) {
10034 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10035 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10036 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10037 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10040 char buf[MOVE_LEN*2], *p; int len;
10041 fprintf(serverMoves, "/%d/%d",
10042 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10043 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10044 else timeLeft = blackTimeRemaining/1000;
10045 fprintf(serverMoves, "/%d", timeLeft);
10046 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10047 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10048 if(p = strchr(buf, '=')) *p = NULLCHAR;
10049 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10050 fprintf(serverMoves, "/%s", buf);
10052 fflush(serverMoves);
10055 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10056 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10059 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10060 if (commentList[forwardMostMove+1] != NULL) {
10061 free(commentList[forwardMostMove+1]);
10062 commentList[forwardMostMove+1] = NULL;
10064 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10065 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10066 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10067 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10068 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10069 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10070 adjustedClock = FALSE;
10071 gameInfo.result = GameUnfinished;
10072 if (gameInfo.resultDetails != NULL) {
10073 free(gameInfo.resultDetails);
10074 gameInfo.resultDetails = NULL;
10076 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10077 moveList[forwardMostMove - 1]);
10078 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10084 if(gameInfo.variant != VariantShogi)
10085 strcat(parseList[forwardMostMove - 1], "+");
10089 strcat(parseList[forwardMostMove - 1], "#");
10094 /* Updates currentMove if not pausing */
10096 ShowMove (int fromX, int fromY, int toX, int toY)
10098 int instant = (gameMode == PlayFromGameFile) ?
10099 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10100 if(appData.noGUI) return;
10101 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10103 if (forwardMostMove == currentMove + 1) {
10104 AnimateMove(boards[forwardMostMove - 1],
10105 fromX, fromY, toX, toY);
10108 currentMove = forwardMostMove;
10111 killX = killY = -1; // [HGM] lion: used up
10113 if (instant) return;
10115 DisplayMove(currentMove - 1);
10116 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10117 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10118 SetHighlights(fromX, fromY, toX, toY);
10121 DrawPosition(FALSE, boards[currentMove]);
10122 DisplayBothClocks();
10123 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10127 SendEgtPath (ChessProgramState *cps)
10128 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10129 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10131 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10134 char c, *q = name+1, *r, *s;
10136 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10137 while(*p && *p != ',') *q++ = *p++;
10138 *q++ = ':'; *q = 0;
10139 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10140 strcmp(name, ",nalimov:") == 0 ) {
10141 // take nalimov path from the menu-changeable option first, if it is defined
10142 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10143 SendToProgram(buf,cps); // send egtbpath command for nalimov
10145 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10146 (s = StrStr(appData.egtFormats, name)) != NULL) {
10147 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10148 s = r = StrStr(s, ":") + 1; // beginning of path info
10149 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10150 c = *r; *r = 0; // temporarily null-terminate path info
10151 *--q = 0; // strip of trailig ':' from name
10152 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10154 SendToProgram(buf,cps); // send egtbpath command for this format
10156 if(*p == ',') p++; // read away comma to position for next format name
10161 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10163 int width = 8, height = 8, holdings = 0; // most common sizes
10164 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10165 // correct the deviations default for each variant
10166 if( v == VariantXiangqi ) width = 9, height = 10;
10167 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10168 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10169 if( v == VariantCapablanca || v == VariantCapaRandom ||
10170 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10172 if( v == VariantCourier ) width = 12;
10173 if( v == VariantSuper ) holdings = 8;
10174 if( v == VariantGreat ) width = 10, holdings = 8;
10175 if( v == VariantSChess ) holdings = 7;
10176 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10177 if( v == VariantChu ) width = 12, height = 12;
10178 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10179 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10180 holdingsSize >= 0 && holdingsSize != holdings;
10183 char variantError[MSG_SIZ];
10186 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10187 { // returns error message (recognizable by upper-case) if engine does not support the variant
10188 char *p, *variant = VariantName(v);
10189 static char b[MSG_SIZ];
10190 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10191 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10192 holdingsSize, variant); // cook up sized variant name
10193 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10194 if(StrStr(list, b) == NULL) {
10195 // specific sized variant not known, check if general sizing allowed
10196 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10197 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10198 boardWidth, boardHeight, holdingsSize, engine);
10201 /* [HGM] here we really should compare with the maximum supported board size */
10203 } else snprintf(b, MSG_SIZ,"%s", variant);
10204 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10205 p = StrStr(list, b);
10206 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10208 // occurs not at all in list, or only as sub-string
10209 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10210 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10211 int l = strlen(variantError);
10213 while(p != list && p[-1] != ',') p--;
10214 q = strchr(p, ',');
10215 if(q) *q = NULLCHAR;
10216 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10225 InitChessProgram (ChessProgramState *cps, int setup)
10226 /* setup needed to setup FRC opening position */
10228 char buf[MSG_SIZ], *b;
10229 if (appData.noChessProgram) return;
10230 hintRequested = FALSE;
10231 bookRequested = FALSE;
10233 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10234 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10235 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10236 if(cps->memSize) { /* [HGM] memory */
10237 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10238 SendToProgram(buf, cps);
10240 SendEgtPath(cps); /* [HGM] EGT */
10241 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10242 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10243 SendToProgram(buf, cps);
10246 SendToProgram(cps->initString, cps);
10247 if (gameInfo.variant != VariantNormal &&
10248 gameInfo.variant != VariantLoadable
10249 /* [HGM] also send variant if board size non-standard */
10250 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10252 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10253 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10255 DisplayFatalError(variantError, 0, 1);
10259 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10260 SendToProgram(buf, cps);
10262 currentlyInitializedVariant = gameInfo.variant;
10264 /* [HGM] send opening position in FRC to first engine */
10266 SendToProgram("force\n", cps);
10268 /* engine is now in force mode! Set flag to wake it up after first move. */
10269 setboardSpoiledMachineBlack = 1;
10272 if (cps->sendICS) {
10273 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10274 SendToProgram(buf, cps);
10276 cps->maybeThinking = FALSE;
10277 cps->offeredDraw = 0;
10278 if (!appData.icsActive) {
10279 SendTimeControl(cps, movesPerSession, timeControl,
10280 timeIncrement, appData.searchDepth,
10283 if (appData.showThinking
10284 // [HGM] thinking: four options require thinking output to be sent
10285 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10287 SendToProgram("post\n", cps);
10289 SendToProgram("hard\n", cps);
10290 if (!appData.ponderNextMove) {
10291 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10292 it without being sure what state we are in first. "hard"
10293 is not a toggle, so that one is OK.
10295 SendToProgram("easy\n", cps);
10297 if (cps->usePing) {
10298 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10299 SendToProgram(buf, cps);
10301 cps->initDone = TRUE;
10302 ClearEngineOutputPane(cps == &second);
10307 ResendOptions (ChessProgramState *cps)
10308 { // send the stored value of the options
10311 Option *opt = cps->option;
10312 for(i=0; i<cps->nrOptions; i++, opt++) {
10313 switch(opt->type) {
10317 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10320 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10323 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10329 SendToProgram(buf, cps);
10334 StartChessProgram (ChessProgramState *cps)
10339 if (appData.noChessProgram) return;
10340 cps->initDone = FALSE;
10342 if (strcmp(cps->host, "localhost") == 0) {
10343 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10344 } else if (*appData.remoteShell == NULLCHAR) {
10345 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10347 if (*appData.remoteUser == NULLCHAR) {
10348 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10351 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10352 cps->host, appData.remoteUser, cps->program);
10354 err = StartChildProcess(buf, "", &cps->pr);
10358 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10359 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10360 if(cps != &first) return;
10361 appData.noChessProgram = TRUE;
10364 // DisplayFatalError(buf, err, 1);
10365 // cps->pr = NoProc;
10366 // cps->isr = NULL;
10370 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10371 if (cps->protocolVersion > 1) {
10372 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10373 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10374 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10375 cps->comboCnt = 0; // and values of combo boxes
10377 SendToProgram(buf, cps);
10378 if(cps->reload) ResendOptions(cps);
10380 SendToProgram("xboard\n", cps);
10385 TwoMachinesEventIfReady P((void))
10387 static int curMess = 0;
10388 if (first.lastPing != first.lastPong) {
10389 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10390 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10393 if (second.lastPing != second.lastPong) {
10394 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10395 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10398 DisplayMessage("", ""); curMess = 0;
10399 TwoMachinesEvent();
10403 MakeName (char *template)
10407 static char buf[MSG_SIZ];
10411 clock = time((time_t *)NULL);
10412 tm = localtime(&clock);
10414 while(*p++ = *template++) if(p[-1] == '%') {
10415 switch(*template++) {
10416 case 0: *p = 0; return buf;
10417 case 'Y': i = tm->tm_year+1900; break;
10418 case 'y': i = tm->tm_year-100; break;
10419 case 'M': i = tm->tm_mon+1; break;
10420 case 'd': i = tm->tm_mday; break;
10421 case 'h': i = tm->tm_hour; break;
10422 case 'm': i = tm->tm_min; break;
10423 case 's': i = tm->tm_sec; break;
10426 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10432 CountPlayers (char *p)
10435 while(p = strchr(p, '\n')) p++, n++; // count participants
10440 WriteTourneyFile (char *results, FILE *f)
10441 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10442 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10443 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10444 // create a file with tournament description
10445 fprintf(f, "-participants {%s}\n", appData.participants);
10446 fprintf(f, "-seedBase %d\n", appData.seedBase);
10447 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10448 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10449 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10450 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10451 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10452 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10453 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10454 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10455 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10456 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10457 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10458 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10459 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10460 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10461 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10462 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10463 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10464 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10465 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10466 fprintf(f, "-smpCores %d\n", appData.smpCores);
10468 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10470 fprintf(f, "-mps %d\n", appData.movesPerSession);
10471 fprintf(f, "-tc %s\n", appData.timeControl);
10472 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10474 fprintf(f, "-results \"%s\"\n", results);
10479 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10482 Substitute (char *participants, int expunge)
10484 int i, changed, changes=0, nPlayers=0;
10485 char *p, *q, *r, buf[MSG_SIZ];
10486 if(participants == NULL) return;
10487 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10488 r = p = participants; q = appData.participants;
10489 while(*p && *p == *q) {
10490 if(*p == '\n') r = p+1, nPlayers++;
10493 if(*p) { // difference
10494 while(*p && *p++ != '\n');
10495 while(*q && *q++ != '\n');
10496 changed = nPlayers;
10497 changes = 1 + (strcmp(p, q) != 0);
10499 if(changes == 1) { // a single engine mnemonic was changed
10500 q = r; while(*q) nPlayers += (*q++ == '\n');
10501 p = buf; while(*r && (*p = *r++) != '\n') p++;
10503 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10504 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10505 if(mnemonic[i]) { // The substitute is valid
10507 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10508 flock(fileno(f), LOCK_EX);
10509 ParseArgsFromFile(f);
10510 fseek(f, 0, SEEK_SET);
10511 FREE(appData.participants); appData.participants = participants;
10512 if(expunge) { // erase results of replaced engine
10513 int len = strlen(appData.results), w, b, dummy;
10514 for(i=0; i<len; i++) {
10515 Pairing(i, nPlayers, &w, &b, &dummy);
10516 if((w == changed || b == changed) && appData.results[i] == '*') {
10517 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10522 for(i=0; i<len; i++) {
10523 Pairing(i, nPlayers, &w, &b, &dummy);
10524 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10527 WriteTourneyFile(appData.results, f);
10528 fclose(f); // release lock
10531 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10533 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10534 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10535 free(participants);
10540 CheckPlayers (char *participants)
10543 char buf[MSG_SIZ], *p;
10544 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10545 while(p = strchr(participants, '\n')) {
10547 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10549 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10551 DisplayError(buf, 0);
10555 participants = p + 1;
10561 CreateTourney (char *name)
10564 if(matchMode && strcmp(name, appData.tourneyFile)) {
10565 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10567 if(name[0] == NULLCHAR) {
10568 if(appData.participants[0])
10569 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10572 f = fopen(name, "r");
10573 if(f) { // file exists
10574 ASSIGN(appData.tourneyFile, name);
10575 ParseArgsFromFile(f); // parse it
10577 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10578 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10579 DisplayError(_("Not enough participants"), 0);
10582 if(CheckPlayers(appData.participants)) return 0;
10583 ASSIGN(appData.tourneyFile, name);
10584 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10585 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10588 appData.noChessProgram = FALSE;
10589 appData.clockMode = TRUE;
10595 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10597 char buf[MSG_SIZ], *p, *q;
10598 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10599 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10600 skip = !all && group[0]; // if group requested, we start in skip mode
10601 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10602 p = names; q = buf; header = 0;
10603 while(*p && *p != '\n') *q++ = *p++;
10605 if(*p == '\n') p++;
10606 if(buf[0] == '#') {
10607 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10608 depth++; // we must be entering a new group
10609 if(all) continue; // suppress printing group headers when complete list requested
10611 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10613 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10614 if(engineList[i]) free(engineList[i]);
10615 engineList[i] = strdup(buf);
10616 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10617 if(engineMnemonic[i]) free(engineMnemonic[i]);
10618 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10620 sscanf(q + 8, "%s", buf + strlen(buf));
10623 engineMnemonic[i] = strdup(buf);
10626 engineList[i] = engineMnemonic[i] = NULL;
10630 // following implemented as macro to avoid type limitations
10631 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10634 SwapEngines (int n)
10635 { // swap settings for first engine and other engine (so far only some selected options)
10640 SWAP(chessProgram, p)
10642 SWAP(hasOwnBookUCI, h)
10643 SWAP(protocolVersion, h)
10645 SWAP(scoreIsAbsolute, h)
10650 SWAP(engOptions, p)
10651 SWAP(engInitString, p)
10652 SWAP(computerString, p)
10654 SWAP(fenOverride, p)
10656 SWAP(accumulateTC, h)
10661 GetEngineLine (char *s, int n)
10665 extern char *icsNames;
10666 if(!s || !*s) return 0;
10667 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10668 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10669 if(!mnemonic[i]) return 0;
10670 if(n == 11) return 1; // just testing if there was a match
10671 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10672 if(n == 1) SwapEngines(n);
10673 ParseArgsFromString(buf);
10674 if(n == 1) SwapEngines(n);
10675 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10676 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10677 ParseArgsFromString(buf);
10683 SetPlayer (int player, char *p)
10684 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10686 char buf[MSG_SIZ], *engineName;
10687 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10688 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10689 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10691 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10692 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10693 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10694 ParseArgsFromString(buf);
10695 } else { // no engine with this nickname is installed!
10696 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10697 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10698 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10700 DisplayError(buf, 0);
10707 char *recentEngines;
10710 RecentEngineEvent (int nr)
10713 // SwapEngines(1); // bump first to second
10714 // ReplaceEngine(&second, 1); // and load it there
10715 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10716 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10717 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10718 ReplaceEngine(&first, 0);
10719 FloatToFront(&appData.recentEngineList, command[n]);
10724 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10725 { // determine players from game number
10726 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10728 if(appData.tourneyType == 0) {
10729 roundsPerCycle = (nPlayers - 1) | 1;
10730 pairingsPerRound = nPlayers / 2;
10731 } else if(appData.tourneyType > 0) {
10732 roundsPerCycle = nPlayers - appData.tourneyType;
10733 pairingsPerRound = appData.tourneyType;
10735 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10736 gamesPerCycle = gamesPerRound * roundsPerCycle;
10737 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10738 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10739 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10740 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10741 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10742 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10744 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10745 if(appData.roundSync) *syncInterval = gamesPerRound;
10747 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10749 if(appData.tourneyType == 0) {
10750 if(curPairing == (nPlayers-1)/2 ) {
10751 *whitePlayer = curRound;
10752 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10754 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10755 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10756 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10757 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10759 } else if(appData.tourneyType > 1) {
10760 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10761 *whitePlayer = curRound + appData.tourneyType;
10762 } else if(appData.tourneyType > 0) {
10763 *whitePlayer = curPairing;
10764 *blackPlayer = curRound + appData.tourneyType;
10767 // take care of white/black alternation per round.
10768 // For cycles and games this is already taken care of by default, derived from matchGame!
10769 return curRound & 1;
10773 NextTourneyGame (int nr, int *swapColors)
10774 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10776 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10778 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10779 tf = fopen(appData.tourneyFile, "r");
10780 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10781 ParseArgsFromFile(tf); fclose(tf);
10782 InitTimeControls(); // TC might be altered from tourney file
10784 nPlayers = CountPlayers(appData.participants); // count participants
10785 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10786 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10789 p = q = appData.results;
10790 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10791 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10792 DisplayMessage(_("Waiting for other game(s)"),"");
10793 waitingForGame = TRUE;
10794 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10797 waitingForGame = FALSE;
10800 if(appData.tourneyType < 0) {
10801 if(nr>=0 && !pairingReceived) {
10803 if(pairing.pr == NoProc) {
10804 if(!appData.pairingEngine[0]) {
10805 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10808 StartChessProgram(&pairing); // starts the pairing engine
10810 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10811 SendToProgram(buf, &pairing);
10812 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10813 SendToProgram(buf, &pairing);
10814 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10816 pairingReceived = 0; // ... so we continue here
10818 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10819 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10820 matchGame = 1; roundNr = nr / syncInterval + 1;
10823 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10825 // redefine engines, engine dir, etc.
10826 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10827 if(first.pr == NoProc) {
10828 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10829 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10831 if(second.pr == NoProc) {
10833 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10834 SwapEngines(1); // and make that valid for second engine by swapping
10835 InitEngine(&second, 1);
10837 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10838 UpdateLogos(FALSE); // leave display to ModeHiglight()
10844 { // performs game initialization that does not invoke engines, and then tries to start the game
10845 int res, firstWhite, swapColors = 0;
10846 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10847 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
10849 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10850 if(strcmp(buf, currentDebugFile)) { // name has changed
10851 FILE *f = fopen(buf, "w");
10852 if(f) { // if opening the new file failed, just keep using the old one
10853 ASSIGN(currentDebugFile, buf);
10857 if(appData.serverFileName) {
10858 if(serverFP) fclose(serverFP);
10859 serverFP = fopen(appData.serverFileName, "w");
10860 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10861 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10865 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10866 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10867 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10868 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10869 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10870 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10871 Reset(FALSE, first.pr != NoProc);
10872 res = LoadGameOrPosition(matchGame); // setup game
10873 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10874 if(!res) return; // abort when bad game/pos file
10875 TwoMachinesEvent();
10879 UserAdjudicationEvent (int result)
10881 ChessMove gameResult = GameIsDrawn;
10884 gameResult = WhiteWins;
10886 else if( result < 0 ) {
10887 gameResult = BlackWins;
10890 if( gameMode == TwoMachinesPlay ) {
10891 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10896 // [HGM] save: calculate checksum of game to make games easily identifiable
10898 StringCheckSum (char *s)
10901 if(s==NULL) return 0;
10902 while(*s) i = i*259 + *s++;
10910 for(i=backwardMostMove; i<forwardMostMove; i++) {
10911 sum += pvInfoList[i].depth;
10912 sum += StringCheckSum(parseList[i]);
10913 sum += StringCheckSum(commentList[i]);
10916 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10917 return sum + StringCheckSum(commentList[i]);
10918 } // end of save patch
10921 GameEnds (ChessMove result, char *resultDetails, int whosays)
10923 GameMode nextGameMode;
10925 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10927 if(endingGame) return; /* [HGM] crash: forbid recursion */
10929 if(twoBoards) { // [HGM] dual: switch back to one board
10930 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10931 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10933 if (appData.debugMode) {
10934 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10935 result, resultDetails ? resultDetails : "(null)", whosays);
10938 fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10940 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10942 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10943 /* If we are playing on ICS, the server decides when the
10944 game is over, but the engine can offer to draw, claim
10948 if (appData.zippyPlay && first.initDone) {
10949 if (result == GameIsDrawn) {
10950 /* In case draw still needs to be claimed */
10951 SendToICS(ics_prefix);
10952 SendToICS("draw\n");
10953 } else if (StrCaseStr(resultDetails, "resign")) {
10954 SendToICS(ics_prefix);
10955 SendToICS("resign\n");
10959 endingGame = 0; /* [HGM] crash */
10963 /* If we're loading the game from a file, stop */
10964 if (whosays == GE_FILE) {
10965 (void) StopLoadGameTimer();
10969 /* Cancel draw offers */
10970 first.offeredDraw = second.offeredDraw = 0;
10972 /* If this is an ICS game, only ICS can really say it's done;
10973 if not, anyone can. */
10974 isIcsGame = (gameMode == IcsPlayingWhite ||
10975 gameMode == IcsPlayingBlack ||
10976 gameMode == IcsObserving ||
10977 gameMode == IcsExamining);
10979 if (!isIcsGame || whosays == GE_ICS) {
10980 /* OK -- not an ICS game, or ICS said it was done */
10982 if (!isIcsGame && !appData.noChessProgram)
10983 SetUserThinkingEnables();
10985 /* [HGM] if a machine claims the game end we verify this claim */
10986 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10987 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10989 ChessMove trueResult = (ChessMove) -1;
10991 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10992 first.twoMachinesColor[0] :
10993 second.twoMachinesColor[0] ;
10995 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10996 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10997 /* [HGM] verify: engine mate claims accepted if they were flagged */
10998 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11000 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11001 /* [HGM] verify: engine mate claims accepted if they were flagged */
11002 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11004 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11005 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11008 // now verify win claims, but not in drop games, as we don't understand those yet
11009 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11010 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11011 (result == WhiteWins && claimer == 'w' ||
11012 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11013 if (appData.debugMode) {
11014 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11015 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11017 if(result != trueResult) {
11018 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11019 result = claimer == 'w' ? BlackWins : WhiteWins;
11020 resultDetails = buf;
11023 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11024 && (forwardMostMove <= backwardMostMove ||
11025 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11026 (claimer=='b')==(forwardMostMove&1))
11028 /* [HGM] verify: draws that were not flagged are false claims */
11029 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11030 result = claimer == 'w' ? BlackWins : WhiteWins;
11031 resultDetails = buf;
11033 /* (Claiming a loss is accepted no questions asked!) */
11034 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11035 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11036 result = GameUnfinished;
11037 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11039 /* [HGM] bare: don't allow bare King to win */
11040 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11041 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11042 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11043 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11044 && result != GameIsDrawn)
11045 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11046 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11047 int p = (signed char)boards[forwardMostMove][i][j] - color;
11048 if(p >= 0 && p <= (int)WhiteKing) k++;
11050 if (appData.debugMode) {
11051 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11052 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11055 result = GameIsDrawn;
11056 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11057 resultDetails = buf;
11063 if(serverMoves != NULL && !loadFlag) { char c = '=';
11064 if(result==WhiteWins) c = '+';
11065 if(result==BlackWins) c = '-';
11066 if(resultDetails != NULL)
11067 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11069 if (resultDetails != NULL) {
11070 gameInfo.result = result;
11071 gameInfo.resultDetails = StrSave(resultDetails);
11073 /* display last move only if game was not loaded from file */
11074 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11075 DisplayMove(currentMove - 1);
11077 if (forwardMostMove != 0) {
11078 if (gameMode != PlayFromGameFile && gameMode != EditGame
11079 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11081 if (*appData.saveGameFile != NULLCHAR) {
11082 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11083 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11085 SaveGameToFile(appData.saveGameFile, TRUE);
11086 } else if (appData.autoSaveGames) {
11087 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11089 if (*appData.savePositionFile != NULLCHAR) {
11090 SavePositionToFile(appData.savePositionFile);
11092 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11096 /* Tell program how game ended in case it is learning */
11097 /* [HGM] Moved this to after saving the PGN, just in case */
11098 /* engine died and we got here through time loss. In that */
11099 /* case we will get a fatal error writing the pipe, which */
11100 /* would otherwise lose us the PGN. */
11101 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11102 /* output during GameEnds should never be fatal anymore */
11103 if (gameMode == MachinePlaysWhite ||
11104 gameMode == MachinePlaysBlack ||
11105 gameMode == TwoMachinesPlay ||
11106 gameMode == IcsPlayingWhite ||
11107 gameMode == IcsPlayingBlack ||
11108 gameMode == BeginningOfGame) {
11110 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11112 if (first.pr != NoProc) {
11113 SendToProgram(buf, &first);
11115 if (second.pr != NoProc &&
11116 gameMode == TwoMachinesPlay) {
11117 SendToProgram(buf, &second);
11122 if (appData.icsActive) {
11123 if (appData.quietPlay &&
11124 (gameMode == IcsPlayingWhite ||
11125 gameMode == IcsPlayingBlack)) {
11126 SendToICS(ics_prefix);
11127 SendToICS("set shout 1\n");
11129 nextGameMode = IcsIdle;
11130 ics_user_moved = FALSE;
11131 /* clean up premove. It's ugly when the game has ended and the
11132 * premove highlights are still on the board.
11135 gotPremove = FALSE;
11136 ClearPremoveHighlights();
11137 DrawPosition(FALSE, boards[currentMove]);
11139 if (whosays == GE_ICS) {
11142 if (gameMode == IcsPlayingWhite)
11144 else if(gameMode == IcsPlayingBlack)
11145 PlayIcsLossSound();
11148 if (gameMode == IcsPlayingBlack)
11150 else if(gameMode == IcsPlayingWhite)
11151 PlayIcsLossSound();
11154 PlayIcsDrawSound();
11157 PlayIcsUnfinishedSound();
11160 if(appData.quitNext) { ExitEvent(0); return; }
11161 } else if (gameMode == EditGame ||
11162 gameMode == PlayFromGameFile ||
11163 gameMode == AnalyzeMode ||
11164 gameMode == AnalyzeFile) {
11165 nextGameMode = gameMode;
11167 nextGameMode = EndOfGame;
11172 nextGameMode = gameMode;
11175 if (appData.noChessProgram) {
11176 gameMode = nextGameMode;
11178 endingGame = 0; /* [HGM] crash */
11183 /* Put first chess program into idle state */
11184 if (first.pr != NoProc &&
11185 (gameMode == MachinePlaysWhite ||
11186 gameMode == MachinePlaysBlack ||
11187 gameMode == TwoMachinesPlay ||
11188 gameMode == IcsPlayingWhite ||
11189 gameMode == IcsPlayingBlack ||
11190 gameMode == BeginningOfGame)) {
11191 SendToProgram("force\n", &first);
11192 if (first.usePing) {
11194 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11195 SendToProgram(buf, &first);
11198 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11199 /* Kill off first chess program */
11200 if (first.isr != NULL)
11201 RemoveInputSource(first.isr);
11204 if (first.pr != NoProc) {
11206 DoSleep( appData.delayBeforeQuit );
11207 SendToProgram("quit\n", &first);
11208 DoSleep( appData.delayAfterQuit );
11209 DestroyChildProcess(first.pr, first.useSigterm);
11210 first.reload = TRUE;
11214 if (second.reuse) {
11215 /* Put second chess program into idle state */
11216 if (second.pr != NoProc &&
11217 gameMode == TwoMachinesPlay) {
11218 SendToProgram("force\n", &second);
11219 if (second.usePing) {
11221 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11222 SendToProgram(buf, &second);
11225 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11226 /* Kill off second chess program */
11227 if (second.isr != NULL)
11228 RemoveInputSource(second.isr);
11231 if (second.pr != NoProc) {
11232 DoSleep( appData.delayBeforeQuit );
11233 SendToProgram("quit\n", &second);
11234 DoSleep( appData.delayAfterQuit );
11235 DestroyChildProcess(second.pr, second.useSigterm);
11236 second.reload = TRUE;
11238 second.pr = NoProc;
11241 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11242 char resChar = '=';
11246 if (first.twoMachinesColor[0] == 'w') {
11249 second.matchWins++;
11254 if (first.twoMachinesColor[0] == 'b') {
11257 second.matchWins++;
11260 case GameUnfinished:
11266 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11267 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11268 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11269 ReserveGame(nextGame, resChar); // sets nextGame
11270 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11271 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11272 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11274 if (nextGame <= appData.matchGames && !abortMatch) {
11275 gameMode = nextGameMode;
11276 matchGame = nextGame; // this will be overruled in tourney mode!
11277 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11278 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11279 endingGame = 0; /* [HGM] crash */
11282 gameMode = nextGameMode;
11283 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11284 first.tidy, second.tidy,
11285 first.matchWins, second.matchWins,
11286 appData.matchGames - (first.matchWins + second.matchWins));
11287 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11288 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11289 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11290 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11291 first.twoMachinesColor = "black\n";
11292 second.twoMachinesColor = "white\n";
11294 first.twoMachinesColor = "white\n";
11295 second.twoMachinesColor = "black\n";
11299 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11300 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11302 gameMode = nextGameMode;
11304 endingGame = 0; /* [HGM] crash */
11305 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11306 if(matchMode == TRUE) { // match through command line: exit with or without popup
11308 ToNrEvent(forwardMostMove);
11309 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11311 } else DisplayFatalError(buf, 0, 0);
11312 } else { // match through menu; just stop, with or without popup
11313 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11316 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11317 } else DisplayNote(buf);
11319 if(ranking) free(ranking);
11323 /* Assumes program was just initialized (initString sent).
11324 Leaves program in force mode. */
11326 FeedMovesToProgram (ChessProgramState *cps, int upto)
11330 if (appData.debugMode)
11331 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11332 startedFromSetupPosition ? "position and " : "",
11333 backwardMostMove, upto, cps->which);
11334 if(currentlyInitializedVariant != gameInfo.variant) {
11336 // [HGM] variantswitch: make engine aware of new variant
11337 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11338 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11339 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11340 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11341 SendToProgram(buf, cps);
11342 currentlyInitializedVariant = gameInfo.variant;
11344 SendToProgram("force\n", cps);
11345 if (startedFromSetupPosition) {
11346 SendBoard(cps, backwardMostMove);
11347 if (appData.debugMode) {
11348 fprintf(debugFP, "feedMoves\n");
11351 for (i = backwardMostMove; i < upto; i++) {
11352 SendMoveToProgram(i, cps);
11358 ResurrectChessProgram ()
11360 /* The chess program may have exited.
11361 If so, restart it and feed it all the moves made so far. */
11362 static int doInit = 0;
11364 if (appData.noChessProgram) return 1;
11366 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11367 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11368 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11369 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11371 if (first.pr != NoProc) return 1;
11372 StartChessProgram(&first);
11374 InitChessProgram(&first, FALSE);
11375 FeedMovesToProgram(&first, currentMove);
11377 if (!first.sendTime) {
11378 /* can't tell gnuchess what its clock should read,
11379 so we bow to its notion. */
11381 timeRemaining[0][currentMove] = whiteTimeRemaining;
11382 timeRemaining[1][currentMove] = blackTimeRemaining;
11385 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11386 appData.icsEngineAnalyze) && first.analysisSupport) {
11387 SendToProgram("analyze\n", &first);
11388 first.analyzing = TRUE;
11394 * Button procedures
11397 Reset (int redraw, int init)
11401 if (appData.debugMode) {
11402 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11403 redraw, init, gameMode);
11405 CleanupTail(); // [HGM] vari: delete any stored variations
11406 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11407 pausing = pauseExamInvalid = FALSE;
11408 startedFromSetupPosition = blackPlaysFirst = FALSE;
11410 whiteFlag = blackFlag = FALSE;
11411 userOfferedDraw = FALSE;
11412 hintRequested = bookRequested = FALSE;
11413 first.maybeThinking = FALSE;
11414 second.maybeThinking = FALSE;
11415 first.bookSuspend = FALSE; // [HGM] book
11416 second.bookSuspend = FALSE;
11417 thinkOutput[0] = NULLCHAR;
11418 lastHint[0] = NULLCHAR;
11419 ClearGameInfo(&gameInfo);
11420 gameInfo.variant = StringToVariant(appData.variant);
11421 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11422 ics_user_moved = ics_clock_paused = FALSE;
11423 ics_getting_history = H_FALSE;
11425 white_holding[0] = black_holding[0] = NULLCHAR;
11426 ClearProgramStats();
11427 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11431 flipView = appData.flipView;
11432 ClearPremoveHighlights();
11433 gotPremove = FALSE;
11434 alarmSounded = FALSE;
11435 killX = killY = -1; // [HGM] lion
11437 GameEnds(EndOfFile, NULL, GE_PLAYER);
11438 if(appData.serverMovesName != NULL) {
11439 /* [HGM] prepare to make moves file for broadcasting */
11440 clock_t t = clock();
11441 if(serverMoves != NULL) fclose(serverMoves);
11442 serverMoves = fopen(appData.serverMovesName, "r");
11443 if(serverMoves != NULL) {
11444 fclose(serverMoves);
11445 /* delay 15 sec before overwriting, so all clients can see end */
11446 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11448 serverMoves = fopen(appData.serverMovesName, "w");
11452 gameMode = BeginningOfGame;
11454 if(appData.icsActive) gameInfo.variant = VariantNormal;
11455 currentMove = forwardMostMove = backwardMostMove = 0;
11456 MarkTargetSquares(1);
11457 InitPosition(redraw);
11458 for (i = 0; i < MAX_MOVES; i++) {
11459 if (commentList[i] != NULL) {
11460 free(commentList[i]);
11461 commentList[i] = NULL;
11465 timeRemaining[0][0] = whiteTimeRemaining;
11466 timeRemaining[1][0] = blackTimeRemaining;
11468 if (first.pr == NoProc) {
11469 StartChessProgram(&first);
11472 InitChessProgram(&first, startedFromSetupPosition);
11475 DisplayMessage("", "");
11476 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11477 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11478 ClearMap(); // [HGM] exclude: invalidate map
11482 AutoPlayGameLoop ()
11485 if (!AutoPlayOneMove())
11487 if (matchMode || appData.timeDelay == 0)
11489 if (appData.timeDelay < 0)
11491 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11499 ReloadGame(1); // next game
11505 int fromX, fromY, toX, toY;
11507 if (appData.debugMode) {
11508 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11511 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11514 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11515 pvInfoList[currentMove].depth = programStats.depth;
11516 pvInfoList[currentMove].score = programStats.score;
11517 pvInfoList[currentMove].time = 0;
11518 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11519 else { // append analysis of final position as comment
11521 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11522 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11524 programStats.depth = 0;
11527 if (currentMove >= forwardMostMove) {
11528 if(gameMode == AnalyzeFile) {
11529 if(appData.loadGameIndex == -1) {
11530 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11531 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11533 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11536 // gameMode = EndOfGame;
11537 // ModeHighlight();
11539 /* [AS] Clear current move marker at the end of a game */
11540 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11545 toX = moveList[currentMove][2] - AAA;
11546 toY = moveList[currentMove][3] - ONE;
11548 if (moveList[currentMove][1] == '@') {
11549 if (appData.highlightLastMove) {
11550 SetHighlights(-1, -1, toX, toY);
11553 fromX = moveList[currentMove][0] - AAA;
11554 fromY = moveList[currentMove][1] - ONE;
11556 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11558 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11560 if (appData.highlightLastMove) {
11561 SetHighlights(fromX, fromY, toX, toY);
11564 DisplayMove(currentMove);
11565 SendMoveToProgram(currentMove++, &first);
11566 DisplayBothClocks();
11567 DrawPosition(FALSE, boards[currentMove]);
11568 // [HGM] PV info: always display, routine tests if empty
11569 DisplayComment(currentMove - 1, commentList[currentMove]);
11575 LoadGameOneMove (ChessMove readAhead)
11577 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11578 char promoChar = NULLCHAR;
11579 ChessMove moveType;
11580 char move[MSG_SIZ];
11583 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11584 gameMode != AnalyzeMode && gameMode != Training) {
11589 yyboardindex = forwardMostMove;
11590 if (readAhead != EndOfFile) {
11591 moveType = readAhead;
11593 if (gameFileFP == NULL)
11595 moveType = (ChessMove) Myylex();
11599 switch (moveType) {
11601 if (appData.debugMode)
11602 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11605 /* append the comment but don't display it */
11606 AppendComment(currentMove, p, FALSE);
11609 case WhiteCapturesEnPassant:
11610 case BlackCapturesEnPassant:
11611 case WhitePromotion:
11612 case BlackPromotion:
11613 case WhiteNonPromotion:
11614 case BlackNonPromotion:
11617 case WhiteKingSideCastle:
11618 case WhiteQueenSideCastle:
11619 case BlackKingSideCastle:
11620 case BlackQueenSideCastle:
11621 case WhiteKingSideCastleWild:
11622 case WhiteQueenSideCastleWild:
11623 case BlackKingSideCastleWild:
11624 case BlackQueenSideCastleWild:
11626 case WhiteHSideCastleFR:
11627 case WhiteASideCastleFR:
11628 case BlackHSideCastleFR:
11629 case BlackASideCastleFR:
11631 if (appData.debugMode)
11632 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11633 fromX = currentMoveString[0] - AAA;
11634 fromY = currentMoveString[1] - ONE;
11635 toX = currentMoveString[2] - AAA;
11636 toY = currentMoveString[3] - ONE;
11637 promoChar = currentMoveString[4];
11638 if(promoChar == ';') promoChar = NULLCHAR;
11643 if (appData.debugMode)
11644 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11645 fromX = moveType == WhiteDrop ?
11646 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11647 (int) CharToPiece(ToLower(currentMoveString[0]));
11649 toX = currentMoveString[2] - AAA;
11650 toY = currentMoveString[3] - ONE;
11656 case GameUnfinished:
11657 if (appData.debugMode)
11658 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11659 p = strchr(yy_text, '{');
11660 if (p == NULL) p = strchr(yy_text, '(');
11663 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11665 q = strchr(p, *p == '{' ? '}' : ')');
11666 if (q != NULL) *q = NULLCHAR;
11669 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11670 GameEnds(moveType, p, GE_FILE);
11672 if (cmailMsgLoaded) {
11674 flipView = WhiteOnMove(currentMove);
11675 if (moveType == GameUnfinished) flipView = !flipView;
11676 if (appData.debugMode)
11677 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11682 if (appData.debugMode)
11683 fprintf(debugFP, "Parser hit end of file\n");
11684 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11690 if (WhiteOnMove(currentMove)) {
11691 GameEnds(BlackWins, "Black mates", GE_FILE);
11693 GameEnds(WhiteWins, "White mates", GE_FILE);
11697 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11703 case MoveNumberOne:
11704 if (lastLoadGameStart == GNUChessGame) {
11705 /* GNUChessGames have numbers, but they aren't move numbers */
11706 if (appData.debugMode)
11707 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11708 yy_text, (int) moveType);
11709 return LoadGameOneMove(EndOfFile); /* tail recursion */
11711 /* else fall thru */
11716 /* Reached start of next game in file */
11717 if (appData.debugMode)
11718 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11719 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11725 if (WhiteOnMove(currentMove)) {
11726 GameEnds(BlackWins, "Black mates", GE_FILE);
11728 GameEnds(WhiteWins, "White mates", GE_FILE);
11732 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11738 case PositionDiagram: /* should not happen; ignore */
11739 case ElapsedTime: /* ignore */
11740 case NAG: /* ignore */
11741 if (appData.debugMode)
11742 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11743 yy_text, (int) moveType);
11744 return LoadGameOneMove(EndOfFile); /* tail recursion */
11747 if (appData.testLegality) {
11748 if (appData.debugMode)
11749 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11750 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11751 (forwardMostMove / 2) + 1,
11752 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11753 DisplayError(move, 0);
11756 if (appData.debugMode)
11757 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11758 yy_text, currentMoveString);
11759 fromX = currentMoveString[0] - AAA;
11760 fromY = currentMoveString[1] - ONE;
11761 toX = currentMoveString[2] - AAA;
11762 toY = currentMoveString[3] - ONE;
11763 promoChar = currentMoveString[4];
11767 case AmbiguousMove:
11768 if (appData.debugMode)
11769 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11770 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11771 (forwardMostMove / 2) + 1,
11772 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11773 DisplayError(move, 0);
11778 case ImpossibleMove:
11779 if (appData.debugMode)
11780 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11781 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11782 (forwardMostMove / 2) + 1,
11783 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11784 DisplayError(move, 0);
11790 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11791 DrawPosition(FALSE, boards[currentMove]);
11792 DisplayBothClocks();
11793 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11794 DisplayComment(currentMove - 1, commentList[currentMove]);
11796 (void) StopLoadGameTimer();
11798 cmailOldMove = forwardMostMove;
11801 /* currentMoveString is set as a side-effect of yylex */
11803 thinkOutput[0] = NULLCHAR;
11804 MakeMove(fromX, fromY, toX, toY, promoChar);
11805 killX = killY = -1; // [HGM] lion: used up
11806 currentMove = forwardMostMove;
11811 /* Load the nth game from the given file */
11813 LoadGameFromFile (char *filename, int n, char *title, int useList)
11818 if (strcmp(filename, "-") == 0) {
11822 f = fopen(filename, "rb");
11824 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11825 DisplayError(buf, errno);
11829 if (fseek(f, 0, 0) == -1) {
11830 /* f is not seekable; probably a pipe */
11833 if (useList && n == 0) {
11834 int error = GameListBuild(f);
11836 DisplayError(_("Cannot build game list"), error);
11837 } else if (!ListEmpty(&gameList) &&
11838 ((ListGame *) gameList.tailPred)->number > 1) {
11839 GameListPopUp(f, title);
11846 return LoadGame(f, n, title, FALSE);
11851 MakeRegisteredMove ()
11853 int fromX, fromY, toX, toY;
11855 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11856 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11859 if (appData.debugMode)
11860 fprintf(debugFP, "Restoring %s for game %d\n",
11861 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11863 thinkOutput[0] = NULLCHAR;
11864 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11865 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11866 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11867 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11868 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11869 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11870 MakeMove(fromX, fromY, toX, toY, promoChar);
11871 ShowMove(fromX, fromY, toX, toY);
11873 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11880 if (WhiteOnMove(currentMove)) {
11881 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11883 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11888 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11895 if (WhiteOnMove(currentMove)) {
11896 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11898 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11903 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11914 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11916 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11920 if (gameNumber > nCmailGames) {
11921 DisplayError(_("No more games in this message"), 0);
11924 if (f == lastLoadGameFP) {
11925 int offset = gameNumber - lastLoadGameNumber;
11927 cmailMsg[0] = NULLCHAR;
11928 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11929 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11930 nCmailMovesRegistered--;
11932 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11933 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11934 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11937 if (! RegisterMove()) return FALSE;
11941 retVal = LoadGame(f, gameNumber, title, useList);
11943 /* Make move registered during previous look at this game, if any */
11944 MakeRegisteredMove();
11946 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11947 commentList[currentMove]
11948 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11949 DisplayComment(currentMove - 1, commentList[currentMove]);
11955 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11957 ReloadGame (int offset)
11959 int gameNumber = lastLoadGameNumber + offset;
11960 if (lastLoadGameFP == NULL) {
11961 DisplayError(_("No game has been loaded yet"), 0);
11964 if (gameNumber <= 0) {
11965 DisplayError(_("Can't back up any further"), 0);
11968 if (cmailMsgLoaded) {
11969 return CmailLoadGame(lastLoadGameFP, gameNumber,
11970 lastLoadGameTitle, lastLoadGameUseList);
11972 return LoadGame(lastLoadGameFP, gameNumber,
11973 lastLoadGameTitle, lastLoadGameUseList);
11977 int keys[EmptySquare+1];
11980 PositionMatches (Board b1, Board b2)
11983 switch(appData.searchMode) {
11984 case 1: return CompareWithRights(b1, b2);
11986 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11987 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11991 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11992 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11993 sum += keys[b1[r][f]] - keys[b2[r][f]];
11997 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11998 sum += keys[b1[r][f]] - keys[b2[r][f]];
12010 int pieceList[256], quickBoard[256];
12011 ChessSquare pieceType[256] = { EmptySquare };
12012 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12013 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12014 int soughtTotal, turn;
12015 Boolean epOK, flipSearch;
12018 unsigned char piece, to;
12021 #define DSIZE (250000)
12023 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12024 Move *moveDatabase = initialSpace;
12025 unsigned int movePtr, dataSize = DSIZE;
12028 MakePieceList (Board board, int *counts)
12030 int r, f, n=Q_PROMO, total=0;
12031 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12032 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12033 int sq = f + (r<<4);
12034 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12035 quickBoard[sq] = ++n;
12037 pieceType[n] = board[r][f];
12038 counts[board[r][f]]++;
12039 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12040 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12044 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12049 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12051 int sq = fromX + (fromY<<4);
12052 int piece = quickBoard[sq];
12053 quickBoard[sq] = 0;
12054 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12055 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12056 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12057 moveDatabase[movePtr++].piece = Q_WCASTL;
12058 quickBoard[sq] = piece;
12059 piece = quickBoard[from]; quickBoard[from] = 0;
12060 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12062 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12063 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12064 moveDatabase[movePtr++].piece = Q_BCASTL;
12065 quickBoard[sq] = piece;
12066 piece = quickBoard[from]; quickBoard[from] = 0;
12067 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12069 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12070 quickBoard[(fromY<<4)+toX] = 0;
12071 moveDatabase[movePtr].piece = Q_EP;
12072 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12073 moveDatabase[movePtr].to = sq;
12075 if(promoPiece != pieceType[piece]) {
12076 moveDatabase[movePtr++].piece = Q_PROMO;
12077 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12079 moveDatabase[movePtr].piece = piece;
12080 quickBoard[sq] = piece;
12085 PackGame (Board board)
12087 Move *newSpace = NULL;
12088 moveDatabase[movePtr].piece = 0; // terminate previous game
12089 if(movePtr > dataSize) {
12090 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12091 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12092 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12095 Move *p = moveDatabase, *q = newSpace;
12096 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12097 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12098 moveDatabase = newSpace;
12099 } else { // calloc failed, we must be out of memory. Too bad...
12100 dataSize = 0; // prevent calloc events for all subsequent games
12101 return 0; // and signal this one isn't cached
12105 MakePieceList(board, counts);
12110 QuickCompare (Board board, int *minCounts, int *maxCounts)
12111 { // compare according to search mode
12113 switch(appData.searchMode)
12115 case 1: // exact position match
12116 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12117 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12118 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12121 case 2: // can have extra material on empty squares
12122 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12123 if(board[r][f] == EmptySquare) continue;
12124 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12127 case 3: // material with exact Pawn structure
12128 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12129 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12130 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12131 } // fall through to material comparison
12132 case 4: // exact material
12133 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12135 case 6: // material range with given imbalance
12136 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12137 // fall through to range comparison
12138 case 5: // material range
12139 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12145 QuickScan (Board board, Move *move)
12146 { // reconstruct game,and compare all positions in it
12147 int cnt=0, stretch=0, total = MakePieceList(board, counts);
12149 int piece = move->piece;
12150 int to = move->to, from = pieceList[piece];
12151 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12152 if(!piece) return -1;
12153 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12154 piece = (++move)->piece;
12155 from = pieceList[piece];
12156 counts[pieceType[piece]]--;
12157 pieceType[piece] = (ChessSquare) move->to;
12158 counts[move->to]++;
12159 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12160 counts[pieceType[quickBoard[to]]]--;
12161 quickBoard[to] = 0; total--;
12164 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12165 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12166 from = pieceList[piece]; // so this must be King
12167 quickBoard[from] = 0;
12168 pieceList[piece] = to;
12169 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12170 quickBoard[from] = 0; // rook
12171 quickBoard[to] = piece;
12172 to = move->to; piece = move->piece;
12176 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12177 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12178 quickBoard[from] = 0;
12180 quickBoard[to] = piece;
12181 pieceList[piece] = to;
12183 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12184 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12185 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12186 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12188 static int lastCounts[EmptySquare+1];
12190 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12191 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12192 } else stretch = 0;
12193 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12202 flipSearch = FALSE;
12203 CopyBoard(soughtBoard, boards[currentMove]);
12204 soughtTotal = MakePieceList(soughtBoard, maxSought);
12205 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12206 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12207 CopyBoard(reverseBoard, boards[currentMove]);
12208 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12209 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12210 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12211 reverseBoard[r][f] = piece;
12213 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12214 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12215 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12216 || (boards[currentMove][CASTLING][2] == NoRights ||
12217 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12218 && (boards[currentMove][CASTLING][5] == NoRights ||
12219 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12222 CopyBoard(flipBoard, soughtBoard);
12223 CopyBoard(rotateBoard, reverseBoard);
12224 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12225 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12226 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12229 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12230 if(appData.searchMode >= 5) {
12231 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12232 MakePieceList(soughtBoard, minSought);
12233 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12235 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12236 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12239 GameInfo dummyInfo;
12240 static int creatingBook;
12243 GameContainsPosition (FILE *f, ListGame *lg)
12245 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12246 int fromX, fromY, toX, toY;
12248 static int initDone=FALSE;
12250 // weed out games based on numerical tag comparison
12251 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12252 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12253 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12254 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12256 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12259 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12260 else CopyBoard(boards[scratch], initialPosition); // default start position
12263 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12264 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12267 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12268 fseek(f, lg->offset, 0);
12271 yyboardindex = scratch;
12272 quickFlag = plyNr+1;
12277 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12283 if(plyNr) return -1; // after we have seen moves, this is for new game
12286 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12287 case ImpossibleMove:
12288 case WhiteWins: // game ends here with these four
12291 case GameUnfinished:
12295 if(appData.testLegality) return -1;
12296 case WhiteCapturesEnPassant:
12297 case BlackCapturesEnPassant:
12298 case WhitePromotion:
12299 case BlackPromotion:
12300 case WhiteNonPromotion:
12301 case BlackNonPromotion:
12304 case WhiteKingSideCastle:
12305 case WhiteQueenSideCastle:
12306 case BlackKingSideCastle:
12307 case BlackQueenSideCastle:
12308 case WhiteKingSideCastleWild:
12309 case WhiteQueenSideCastleWild:
12310 case BlackKingSideCastleWild:
12311 case BlackQueenSideCastleWild:
12312 case WhiteHSideCastleFR:
12313 case WhiteASideCastleFR:
12314 case BlackHSideCastleFR:
12315 case BlackASideCastleFR:
12316 fromX = currentMoveString[0] - AAA;
12317 fromY = currentMoveString[1] - ONE;
12318 toX = currentMoveString[2] - AAA;
12319 toY = currentMoveString[3] - ONE;
12320 promoChar = currentMoveString[4];
12324 fromX = next == WhiteDrop ?
12325 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12326 (int) CharToPiece(ToLower(currentMoveString[0]));
12328 toX = currentMoveString[2] - AAA;
12329 toY = currentMoveString[3] - ONE;
12333 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12335 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12336 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12337 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12338 if(appData.findMirror) {
12339 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12340 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12345 /* Load the nth game from open file f */
12347 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12351 int gn = gameNumber;
12352 ListGame *lg = NULL;
12353 int numPGNTags = 0;
12355 GameMode oldGameMode;
12356 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12358 if (appData.debugMode)
12359 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12361 if (gameMode == Training )
12362 SetTrainingModeOff();
12364 oldGameMode = gameMode;
12365 if (gameMode != BeginningOfGame) {
12366 Reset(FALSE, TRUE);
12368 killX = killY = -1; // [HGM] lion: in case we did not Reset
12371 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12372 fclose(lastLoadGameFP);
12376 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12379 fseek(f, lg->offset, 0);
12380 GameListHighlight(gameNumber);
12381 pos = lg->position;
12385 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12386 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12388 DisplayError(_("Game number out of range"), 0);
12393 if (fseek(f, 0, 0) == -1) {
12394 if (f == lastLoadGameFP ?
12395 gameNumber == lastLoadGameNumber + 1 :
12399 DisplayError(_("Can't seek on game file"), 0);
12404 lastLoadGameFP = f;
12405 lastLoadGameNumber = gameNumber;
12406 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12407 lastLoadGameUseList = useList;
12411 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12412 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12413 lg->gameInfo.black);
12415 } else if (*title != NULLCHAR) {
12416 if (gameNumber > 1) {
12417 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12420 DisplayTitle(title);
12424 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12425 gameMode = PlayFromGameFile;
12429 currentMove = forwardMostMove = backwardMostMove = 0;
12430 CopyBoard(boards[0], initialPosition);
12434 * Skip the first gn-1 games in the file.
12435 * Also skip over anything that precedes an identifiable
12436 * start of game marker, to avoid being confused by
12437 * garbage at the start of the file. Currently
12438 * recognized start of game markers are the move number "1",
12439 * the pattern "gnuchess .* game", the pattern
12440 * "^[#;%] [^ ]* game file", and a PGN tag block.
12441 * A game that starts with one of the latter two patterns
12442 * will also have a move number 1, possibly
12443 * following a position diagram.
12444 * 5-4-02: Let's try being more lenient and allowing a game to
12445 * start with an unnumbered move. Does that break anything?
12447 cm = lastLoadGameStart = EndOfFile;
12449 yyboardindex = forwardMostMove;
12450 cm = (ChessMove) Myylex();
12453 if (cmailMsgLoaded) {
12454 nCmailGames = CMAIL_MAX_GAMES - gn;
12457 DisplayError(_("Game not found in file"), 0);
12464 lastLoadGameStart = cm;
12467 case MoveNumberOne:
12468 switch (lastLoadGameStart) {
12473 case MoveNumberOne:
12475 gn--; /* count this game */
12476 lastLoadGameStart = cm;
12485 switch (lastLoadGameStart) {
12488 case MoveNumberOne:
12490 gn--; /* count this game */
12491 lastLoadGameStart = cm;
12494 lastLoadGameStart = cm; /* game counted already */
12502 yyboardindex = forwardMostMove;
12503 cm = (ChessMove) Myylex();
12504 } while (cm == PGNTag || cm == Comment);
12511 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12512 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12513 != CMAIL_OLD_RESULT) {
12515 cmailResult[ CMAIL_MAX_GAMES
12516 - gn - 1] = CMAIL_OLD_RESULT;
12523 /* Only a NormalMove can be at the start of a game
12524 * without a position diagram. */
12525 if (lastLoadGameStart == EndOfFile ) {
12527 lastLoadGameStart = MoveNumberOne;
12536 if (appData.debugMode)
12537 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12539 if (cm == XBoardGame) {
12540 /* Skip any header junk before position diagram and/or move 1 */
12542 yyboardindex = forwardMostMove;
12543 cm = (ChessMove) Myylex();
12545 if (cm == EndOfFile ||
12546 cm == GNUChessGame || cm == XBoardGame) {
12547 /* Empty game; pretend end-of-file and handle later */
12552 if (cm == MoveNumberOne || cm == PositionDiagram ||
12553 cm == PGNTag || cm == Comment)
12556 } else if (cm == GNUChessGame) {
12557 if (gameInfo.event != NULL) {
12558 free(gameInfo.event);
12560 gameInfo.event = StrSave(yy_text);
12563 startedFromSetupPosition = FALSE;
12564 while (cm == PGNTag) {
12565 if (appData.debugMode)
12566 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12567 err = ParsePGNTag(yy_text, &gameInfo);
12568 if (!err) numPGNTags++;
12570 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12571 if(gameInfo.variant != oldVariant) {
12572 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12573 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12574 InitPosition(TRUE);
12575 oldVariant = gameInfo.variant;
12576 if (appData.debugMode)
12577 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12581 if (gameInfo.fen != NULL) {
12582 Board initial_position;
12583 startedFromSetupPosition = TRUE;
12584 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12586 DisplayError(_("Bad FEN position in file"), 0);
12589 CopyBoard(boards[0], initial_position);
12590 if (blackPlaysFirst) {
12591 currentMove = forwardMostMove = backwardMostMove = 1;
12592 CopyBoard(boards[1], initial_position);
12593 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12594 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12595 timeRemaining[0][1] = whiteTimeRemaining;
12596 timeRemaining[1][1] = blackTimeRemaining;
12597 if (commentList[0] != NULL) {
12598 commentList[1] = commentList[0];
12599 commentList[0] = NULL;
12602 currentMove = forwardMostMove = backwardMostMove = 0;
12604 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12606 initialRulePlies = FENrulePlies;
12607 for( i=0; i< nrCastlingRights; i++ )
12608 initialRights[i] = initial_position[CASTLING][i];
12610 yyboardindex = forwardMostMove;
12611 free(gameInfo.fen);
12612 gameInfo.fen = NULL;
12615 yyboardindex = forwardMostMove;
12616 cm = (ChessMove) Myylex();
12618 /* Handle comments interspersed among the tags */
12619 while (cm == Comment) {
12621 if (appData.debugMode)
12622 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12624 AppendComment(currentMove, p, FALSE);
12625 yyboardindex = forwardMostMove;
12626 cm = (ChessMove) Myylex();
12630 /* don't rely on existence of Event tag since if game was
12631 * pasted from clipboard the Event tag may not exist
12633 if (numPGNTags > 0){
12635 if (gameInfo.variant == VariantNormal) {
12636 VariantClass v = StringToVariant(gameInfo.event);
12637 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12638 if(v < VariantShogi) gameInfo.variant = v;
12641 if( appData.autoDisplayTags ) {
12642 tags = PGNTags(&gameInfo);
12643 TagsPopUp(tags, CmailMsg());
12648 /* Make something up, but don't display it now */
12653 if (cm == PositionDiagram) {
12656 Board initial_position;
12658 if (appData.debugMode)
12659 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12661 if (!startedFromSetupPosition) {
12663 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12664 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12675 initial_position[i][j++] = CharToPiece(*p);
12678 while (*p == ' ' || *p == '\t' ||
12679 *p == '\n' || *p == '\r') p++;
12681 if (strncmp(p, "black", strlen("black"))==0)
12682 blackPlaysFirst = TRUE;
12684 blackPlaysFirst = FALSE;
12685 startedFromSetupPosition = TRUE;
12687 CopyBoard(boards[0], initial_position);
12688 if (blackPlaysFirst) {
12689 currentMove = forwardMostMove = backwardMostMove = 1;
12690 CopyBoard(boards[1], initial_position);
12691 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12692 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12693 timeRemaining[0][1] = whiteTimeRemaining;
12694 timeRemaining[1][1] = blackTimeRemaining;
12695 if (commentList[0] != NULL) {
12696 commentList[1] = commentList[0];
12697 commentList[0] = NULL;
12700 currentMove = forwardMostMove = backwardMostMove = 0;
12703 yyboardindex = forwardMostMove;
12704 cm = (ChessMove) Myylex();
12707 if(!creatingBook) {
12708 if (first.pr == NoProc) {
12709 StartChessProgram(&first);
12711 InitChessProgram(&first, FALSE);
12712 SendToProgram("force\n", &first);
12713 if (startedFromSetupPosition) {
12714 SendBoard(&first, forwardMostMove);
12715 if (appData.debugMode) {
12716 fprintf(debugFP, "Load Game\n");
12718 DisplayBothClocks();
12722 /* [HGM] server: flag to write setup moves in broadcast file as one */
12723 loadFlag = appData.suppressLoadMoves;
12725 while (cm == Comment) {
12727 if (appData.debugMode)
12728 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12730 AppendComment(currentMove, p, FALSE);
12731 yyboardindex = forwardMostMove;
12732 cm = (ChessMove) Myylex();
12735 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12736 cm == WhiteWins || cm == BlackWins ||
12737 cm == GameIsDrawn || cm == GameUnfinished) {
12738 DisplayMessage("", _("No moves in game"));
12739 if (cmailMsgLoaded) {
12740 if (appData.debugMode)
12741 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12745 DrawPosition(FALSE, boards[currentMove]);
12746 DisplayBothClocks();
12747 gameMode = EditGame;
12754 // [HGM] PV info: routine tests if comment empty
12755 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12756 DisplayComment(currentMove - 1, commentList[currentMove]);
12758 if (!matchMode && appData.timeDelay != 0)
12759 DrawPosition(FALSE, boards[currentMove]);
12761 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12762 programStats.ok_to_send = 1;
12765 /* if the first token after the PGN tags is a move
12766 * and not move number 1, retrieve it from the parser
12768 if (cm != MoveNumberOne)
12769 LoadGameOneMove(cm);
12771 /* load the remaining moves from the file */
12772 while (LoadGameOneMove(EndOfFile)) {
12773 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12774 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12777 /* rewind to the start of the game */
12778 currentMove = backwardMostMove;
12780 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12782 if (oldGameMode == AnalyzeFile) {
12783 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12784 AnalyzeFileEvent();
12786 if (oldGameMode == AnalyzeMode) {
12787 AnalyzeFileEvent();
12790 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12791 long int w, b; // [HGM] adjourn: restore saved clock times
12792 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12793 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12794 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12795 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12799 if(creatingBook) return TRUE;
12800 if (!matchMode && pos > 0) {
12801 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12803 if (matchMode || appData.timeDelay == 0) {
12805 } else if (appData.timeDelay > 0) {
12806 AutoPlayGameLoop();
12809 if (appData.debugMode)
12810 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12812 loadFlag = 0; /* [HGM] true game starts */
12816 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12818 ReloadPosition (int offset)
12820 int positionNumber = lastLoadPositionNumber + offset;
12821 if (lastLoadPositionFP == NULL) {
12822 DisplayError(_("No position has been loaded yet"), 0);
12825 if (positionNumber <= 0) {
12826 DisplayError(_("Can't back up any further"), 0);
12829 return LoadPosition(lastLoadPositionFP, positionNumber,
12830 lastLoadPositionTitle);
12833 /* Load the nth position from the given file */
12835 LoadPositionFromFile (char *filename, int n, char *title)
12840 if (strcmp(filename, "-") == 0) {
12841 return LoadPosition(stdin, n, "stdin");
12843 f = fopen(filename, "rb");
12845 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12846 DisplayError(buf, errno);
12849 return LoadPosition(f, n, title);
12854 /* Load the nth position from the given open file, and close it */
12856 LoadPosition (FILE *f, int positionNumber, char *title)
12858 char *p, line[MSG_SIZ];
12859 Board initial_position;
12860 int i, j, fenMode, pn;
12862 if (gameMode == Training )
12863 SetTrainingModeOff();
12865 if (gameMode != BeginningOfGame) {
12866 Reset(FALSE, TRUE);
12868 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12869 fclose(lastLoadPositionFP);
12871 if (positionNumber == 0) positionNumber = 1;
12872 lastLoadPositionFP = f;
12873 lastLoadPositionNumber = positionNumber;
12874 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12875 if (first.pr == NoProc && !appData.noChessProgram) {
12876 StartChessProgram(&first);
12877 InitChessProgram(&first, FALSE);
12879 pn = positionNumber;
12880 if (positionNumber < 0) {
12881 /* Negative position number means to seek to that byte offset */
12882 if (fseek(f, -positionNumber, 0) == -1) {
12883 DisplayError(_("Can't seek on position file"), 0);
12888 if (fseek(f, 0, 0) == -1) {
12889 if (f == lastLoadPositionFP ?
12890 positionNumber == lastLoadPositionNumber + 1 :
12891 positionNumber == 1) {
12894 DisplayError(_("Can't seek on position file"), 0);
12899 /* See if this file is FEN or old-style xboard */
12900 if (fgets(line, MSG_SIZ, f) == NULL) {
12901 DisplayError(_("Position not found in file"), 0);
12904 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12905 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12908 if (fenMode || line[0] == '#') pn--;
12910 /* skip positions before number pn */
12911 if (fgets(line, MSG_SIZ, f) == NULL) {
12913 DisplayError(_("Position not found in file"), 0);
12916 if (fenMode || line[0] == '#') pn--;
12921 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12922 DisplayError(_("Bad FEN position in file"), 0);
12926 (void) fgets(line, MSG_SIZ, f);
12927 (void) fgets(line, MSG_SIZ, f);
12929 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12930 (void) fgets(line, MSG_SIZ, f);
12931 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12934 initial_position[i][j++] = CharToPiece(*p);
12938 blackPlaysFirst = FALSE;
12940 (void) fgets(line, MSG_SIZ, f);
12941 if (strncmp(line, "black", strlen("black"))==0)
12942 blackPlaysFirst = TRUE;
12945 startedFromSetupPosition = TRUE;
12947 CopyBoard(boards[0], initial_position);
12948 if (blackPlaysFirst) {
12949 currentMove = forwardMostMove = backwardMostMove = 1;
12950 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12951 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12952 CopyBoard(boards[1], initial_position);
12953 DisplayMessage("", _("Black to play"));
12955 currentMove = forwardMostMove = backwardMostMove = 0;
12956 DisplayMessage("", _("White to play"));
12958 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12959 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12960 SendToProgram("force\n", &first);
12961 SendBoard(&first, forwardMostMove);
12963 if (appData.debugMode) {
12965 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12966 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12967 fprintf(debugFP, "Load Position\n");
12970 if (positionNumber > 1) {
12971 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12972 DisplayTitle(line);
12974 DisplayTitle(title);
12976 gameMode = EditGame;
12979 timeRemaining[0][1] = whiteTimeRemaining;
12980 timeRemaining[1][1] = blackTimeRemaining;
12981 DrawPosition(FALSE, boards[currentMove]);
12988 CopyPlayerNameIntoFileName (char **dest, char *src)
12990 while (*src != NULLCHAR && *src != ',') {
12995 *(*dest)++ = *src++;
13001 DefaultFileName (char *ext)
13003 static char def[MSG_SIZ];
13006 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13008 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13010 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13012 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13019 /* Save the current game to the given file */
13021 SaveGameToFile (char *filename, int append)
13025 int result, i, t,tot=0;
13027 if (strcmp(filename, "-") == 0) {
13028 return SaveGame(stdout, 0, NULL);
13030 for(i=0; i<10; i++) { // upto 10 tries
13031 f = fopen(filename, append ? "a" : "w");
13032 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13033 if(f || errno != 13) break;
13034 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13038 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13039 DisplayError(buf, errno);
13042 safeStrCpy(buf, lastMsg, MSG_SIZ);
13043 DisplayMessage(_("Waiting for access to save file"), "");
13044 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13045 DisplayMessage(_("Saving game"), "");
13046 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13047 result = SaveGame(f, 0, NULL);
13048 DisplayMessage(buf, "");
13055 SavePart (char *str)
13057 static char buf[MSG_SIZ];
13060 p = strchr(str, ' ');
13061 if (p == NULL) return str;
13062 strncpy(buf, str, p - str);
13063 buf[p - str] = NULLCHAR;
13067 #define PGN_MAX_LINE 75
13069 #define PGN_SIDE_WHITE 0
13070 #define PGN_SIDE_BLACK 1
13073 FindFirstMoveOutOfBook (int side)
13077 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13078 int index = backwardMostMove;
13079 int has_book_hit = 0;
13081 if( (index % 2) != side ) {
13085 while( index < forwardMostMove ) {
13086 /* Check to see if engine is in book */
13087 int depth = pvInfoList[index].depth;
13088 int score = pvInfoList[index].score;
13094 else if( score == 0 && depth == 63 ) {
13095 in_book = 1; /* Zappa */
13097 else if( score == 2 && depth == 99 ) {
13098 in_book = 1; /* Abrok */
13101 has_book_hit += in_book;
13117 GetOutOfBookInfo (char * buf)
13121 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13123 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13124 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13128 if( oob[0] >= 0 || oob[1] >= 0 ) {
13129 for( i=0; i<2; i++ ) {
13133 if( i > 0 && oob[0] >= 0 ) {
13134 strcat( buf, " " );
13137 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13138 sprintf( buf+strlen(buf), "%s%.2f",
13139 pvInfoList[idx].score >= 0 ? "+" : "",
13140 pvInfoList[idx].score / 100.0 );
13146 /* Save game in PGN style and close the file */
13148 SaveGamePGN (FILE *f)
13150 int i, offset, linelen, newblock;
13153 int movelen, numlen, blank;
13154 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13156 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13158 PrintPGNTags(f, &gameInfo);
13160 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13162 if (backwardMostMove > 0 || startedFromSetupPosition) {
13163 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13164 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13165 fprintf(f, "\n{--------------\n");
13166 PrintPosition(f, backwardMostMove);
13167 fprintf(f, "--------------}\n");
13171 /* [AS] Out of book annotation */
13172 if( appData.saveOutOfBookInfo ) {
13175 GetOutOfBookInfo( buf );
13177 if( buf[0] != '\0' ) {
13178 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13185 i = backwardMostMove;
13189 while (i < forwardMostMove) {
13190 /* Print comments preceding this move */
13191 if (commentList[i] != NULL) {
13192 if (linelen > 0) fprintf(f, "\n");
13193 fprintf(f, "%s", commentList[i]);
13198 /* Format move number */
13200 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13203 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13205 numtext[0] = NULLCHAR;
13207 numlen = strlen(numtext);
13210 /* Print move number */
13211 blank = linelen > 0 && numlen > 0;
13212 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13221 fprintf(f, "%s", numtext);
13225 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13226 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13229 blank = linelen > 0 && movelen > 0;
13230 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13239 fprintf(f, "%s", move_buffer);
13240 linelen += movelen;
13242 /* [AS] Add PV info if present */
13243 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13244 /* [HGM] add time */
13245 char buf[MSG_SIZ]; int seconds;
13247 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13253 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13256 seconds = (seconds + 4)/10; // round to full seconds
13258 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13260 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13263 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13264 pvInfoList[i].score >= 0 ? "+" : "",
13265 pvInfoList[i].score / 100.0,
13266 pvInfoList[i].depth,
13269 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13271 /* Print score/depth */
13272 blank = linelen > 0 && movelen > 0;
13273 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13282 fprintf(f, "%s", move_buffer);
13283 linelen += movelen;
13289 /* Start a new line */
13290 if (linelen > 0) fprintf(f, "\n");
13292 /* Print comments after last move */
13293 if (commentList[i] != NULL) {
13294 fprintf(f, "%s\n", commentList[i]);
13298 if (gameInfo.resultDetails != NULL &&
13299 gameInfo.resultDetails[0] != NULLCHAR) {
13300 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13301 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13302 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13303 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13304 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13306 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13310 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13314 /* Save game in old style and close the file */
13316 SaveGameOldStyle (FILE *f)
13321 tm = time((time_t *) NULL);
13323 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13326 if (backwardMostMove > 0 || startedFromSetupPosition) {
13327 fprintf(f, "\n[--------------\n");
13328 PrintPosition(f, backwardMostMove);
13329 fprintf(f, "--------------]\n");
13334 i = backwardMostMove;
13335 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13337 while (i < forwardMostMove) {
13338 if (commentList[i] != NULL) {
13339 fprintf(f, "[%s]\n", commentList[i]);
13342 if ((i % 2) == 1) {
13343 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13346 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13348 if (commentList[i] != NULL) {
13352 if (i >= forwardMostMove) {
13356 fprintf(f, "%s\n", parseList[i]);
13361 if (commentList[i] != NULL) {
13362 fprintf(f, "[%s]\n", commentList[i]);
13365 /* This isn't really the old style, but it's close enough */
13366 if (gameInfo.resultDetails != NULL &&
13367 gameInfo.resultDetails[0] != NULLCHAR) {
13368 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13369 gameInfo.resultDetails);
13371 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13378 /* Save the current game to open file f and close the file */
13380 SaveGame (FILE *f, int dummy, char *dummy2)
13382 if (gameMode == EditPosition) EditPositionDone(TRUE);
13383 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13384 if (appData.oldSaveStyle)
13385 return SaveGameOldStyle(f);
13387 return SaveGamePGN(f);
13390 /* Save the current position to the given file */
13392 SavePositionToFile (char *filename)
13397 if (strcmp(filename, "-") == 0) {
13398 return SavePosition(stdout, 0, NULL);
13400 f = fopen(filename, "a");
13402 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13403 DisplayError(buf, errno);
13406 safeStrCpy(buf, lastMsg, MSG_SIZ);
13407 DisplayMessage(_("Waiting for access to save file"), "");
13408 flock(fileno(f), LOCK_EX); // [HGM] lock
13409 DisplayMessage(_("Saving position"), "");
13410 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13411 SavePosition(f, 0, NULL);
13412 DisplayMessage(buf, "");
13418 /* Save the current position to the given open file and close the file */
13420 SavePosition (FILE *f, int dummy, char *dummy2)
13425 if (gameMode == EditPosition) EditPositionDone(TRUE);
13426 if (appData.oldSaveStyle) {
13427 tm = time((time_t *) NULL);
13429 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13431 fprintf(f, "[--------------\n");
13432 PrintPosition(f, currentMove);
13433 fprintf(f, "--------------]\n");
13435 fen = PositionToFEN(currentMove, NULL, 1);
13436 fprintf(f, "%s\n", fen);
13444 ReloadCmailMsgEvent (int unregister)
13447 static char *inFilename = NULL;
13448 static char *outFilename;
13450 struct stat inbuf, outbuf;
13453 /* Any registered moves are unregistered if unregister is set, */
13454 /* i.e. invoked by the signal handler */
13456 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13457 cmailMoveRegistered[i] = FALSE;
13458 if (cmailCommentList[i] != NULL) {
13459 free(cmailCommentList[i]);
13460 cmailCommentList[i] = NULL;
13463 nCmailMovesRegistered = 0;
13466 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13467 cmailResult[i] = CMAIL_NOT_RESULT;
13471 if (inFilename == NULL) {
13472 /* Because the filenames are static they only get malloced once */
13473 /* and they never get freed */
13474 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13475 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13477 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13478 sprintf(outFilename, "%s.out", appData.cmailGameName);
13481 status = stat(outFilename, &outbuf);
13483 cmailMailedMove = FALSE;
13485 status = stat(inFilename, &inbuf);
13486 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13489 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13490 counts the games, notes how each one terminated, etc.
13492 It would be nice to remove this kludge and instead gather all
13493 the information while building the game list. (And to keep it
13494 in the game list nodes instead of having a bunch of fixed-size
13495 parallel arrays.) Note this will require getting each game's
13496 termination from the PGN tags, as the game list builder does
13497 not process the game moves. --mann
13499 cmailMsgLoaded = TRUE;
13500 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13502 /* Load first game in the file or popup game menu */
13503 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13505 #endif /* !WIN32 */
13513 char string[MSG_SIZ];
13515 if ( cmailMailedMove
13516 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13517 return TRUE; /* Allow free viewing */
13520 /* Unregister move to ensure that we don't leave RegisterMove */
13521 /* with the move registered when the conditions for registering no */
13523 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13524 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13525 nCmailMovesRegistered --;
13527 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13529 free(cmailCommentList[lastLoadGameNumber - 1]);
13530 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13534 if (cmailOldMove == -1) {
13535 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13539 if (currentMove > cmailOldMove + 1) {
13540 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13544 if (currentMove < cmailOldMove) {
13545 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13549 if (forwardMostMove > currentMove) {
13550 /* Silently truncate extra moves */
13554 if ( (currentMove == cmailOldMove + 1)
13555 || ( (currentMove == cmailOldMove)
13556 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13557 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13558 if (gameInfo.result != GameUnfinished) {
13559 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13562 if (commentList[currentMove] != NULL) {
13563 cmailCommentList[lastLoadGameNumber - 1]
13564 = StrSave(commentList[currentMove]);
13566 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13568 if (appData.debugMode)
13569 fprintf(debugFP, "Saving %s for game %d\n",
13570 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13572 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13574 f = fopen(string, "w");
13575 if (appData.oldSaveStyle) {
13576 SaveGameOldStyle(f); /* also closes the file */
13578 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13579 f = fopen(string, "w");
13580 SavePosition(f, 0, NULL); /* also closes the file */
13582 fprintf(f, "{--------------\n");
13583 PrintPosition(f, currentMove);
13584 fprintf(f, "--------------}\n\n");
13586 SaveGame(f, 0, NULL); /* also closes the file*/
13589 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13590 nCmailMovesRegistered ++;
13591 } else if (nCmailGames == 1) {
13592 DisplayError(_("You have not made a move yet"), 0);
13603 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13604 FILE *commandOutput;
13605 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13606 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13612 if (! cmailMsgLoaded) {
13613 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13617 if (nCmailGames == nCmailResults) {
13618 DisplayError(_("No unfinished games"), 0);
13622 #if CMAIL_PROHIBIT_REMAIL
13623 if (cmailMailedMove) {
13624 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);
13625 DisplayError(msg, 0);
13630 if (! (cmailMailedMove || RegisterMove())) return;
13632 if ( cmailMailedMove
13633 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13634 snprintf(string, MSG_SIZ, partCommandString,
13635 appData.debugMode ? " -v" : "", appData.cmailGameName);
13636 commandOutput = popen(string, "r");
13638 if (commandOutput == NULL) {
13639 DisplayError(_("Failed to invoke cmail"), 0);
13641 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13642 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13644 if (nBuffers > 1) {
13645 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13646 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13647 nBytes = MSG_SIZ - 1;
13649 (void) memcpy(msg, buffer, nBytes);
13651 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13653 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13654 cmailMailedMove = TRUE; /* Prevent >1 moves */
13657 for (i = 0; i < nCmailGames; i ++) {
13658 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13663 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13665 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13667 appData.cmailGameName,
13669 LoadGameFromFile(buffer, 1, buffer, FALSE);
13670 cmailMsgLoaded = FALSE;
13674 DisplayInformation(msg);
13675 pclose(commandOutput);
13678 if ((*cmailMsg) != '\0') {
13679 DisplayInformation(cmailMsg);
13684 #endif /* !WIN32 */
13693 int prependComma = 0;
13695 char string[MSG_SIZ]; /* Space for game-list */
13698 if (!cmailMsgLoaded) return "";
13700 if (cmailMailedMove) {
13701 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13703 /* Create a list of games left */
13704 snprintf(string, MSG_SIZ, "[");
13705 for (i = 0; i < nCmailGames; i ++) {
13706 if (! ( cmailMoveRegistered[i]
13707 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13708 if (prependComma) {
13709 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13711 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13715 strcat(string, number);
13718 strcat(string, "]");
13720 if (nCmailMovesRegistered + nCmailResults == 0) {
13721 switch (nCmailGames) {
13723 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13727 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13731 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13736 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13738 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13743 if (nCmailResults == nCmailGames) {
13744 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13746 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13751 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13763 if (gameMode == Training)
13764 SetTrainingModeOff();
13767 cmailMsgLoaded = FALSE;
13768 if (appData.icsActive) {
13769 SendToICS(ics_prefix);
13770 SendToICS("refresh\n");
13775 ExitEvent (int status)
13779 /* Give up on clean exit */
13783 /* Keep trying for clean exit */
13787 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13789 if (telnetISR != NULL) {
13790 RemoveInputSource(telnetISR);
13792 if (icsPR != NoProc) {
13793 DestroyChildProcess(icsPR, TRUE);
13796 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13797 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13799 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13800 /* make sure this other one finishes before killing it! */
13801 if(endingGame) { int count = 0;
13802 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13803 while(endingGame && count++ < 10) DoSleep(1);
13804 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13807 /* Kill off chess programs */
13808 if (first.pr != NoProc) {
13811 DoSleep( appData.delayBeforeQuit );
13812 SendToProgram("quit\n", &first);
13813 DoSleep( appData.delayAfterQuit );
13814 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13816 if (second.pr != NoProc) {
13817 DoSleep( appData.delayBeforeQuit );
13818 SendToProgram("quit\n", &second);
13819 DoSleep( appData.delayAfterQuit );
13820 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13822 if (first.isr != NULL) {
13823 RemoveInputSource(first.isr);
13825 if (second.isr != NULL) {
13826 RemoveInputSource(second.isr);
13829 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13830 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13832 ShutDownFrontEnd();
13837 PauseEngine (ChessProgramState *cps)
13839 SendToProgram("pause\n", cps);
13844 UnPauseEngine (ChessProgramState *cps)
13846 SendToProgram("resume\n", cps);
13853 if (appData.debugMode)
13854 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13858 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13860 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13861 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13862 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13864 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13865 HandleMachineMove(stashedInputMove, stalledEngine);
13866 stalledEngine = NULL;
13869 if (gameMode == MachinePlaysWhite ||
13870 gameMode == TwoMachinesPlay ||
13871 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13872 if(first.pause) UnPauseEngine(&first);
13873 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13874 if(second.pause) UnPauseEngine(&second);
13875 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13878 DisplayBothClocks();
13880 if (gameMode == PlayFromGameFile) {
13881 if (appData.timeDelay >= 0)
13882 AutoPlayGameLoop();
13883 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13884 Reset(FALSE, TRUE);
13885 SendToICS(ics_prefix);
13886 SendToICS("refresh\n");
13887 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13888 ForwardInner(forwardMostMove);
13890 pauseExamInvalid = FALSE;
13892 switch (gameMode) {
13896 pauseExamForwardMostMove = forwardMostMove;
13897 pauseExamInvalid = FALSE;
13900 case IcsPlayingWhite:
13901 case IcsPlayingBlack:
13905 case PlayFromGameFile:
13906 (void) StopLoadGameTimer();
13910 case BeginningOfGame:
13911 if (appData.icsActive) return;
13912 /* else fall through */
13913 case MachinePlaysWhite:
13914 case MachinePlaysBlack:
13915 case TwoMachinesPlay:
13916 if (forwardMostMove == 0)
13917 return; /* don't pause if no one has moved */
13918 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13919 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13920 if(onMove->pause) { // thinking engine can be paused
13921 PauseEngine(onMove); // do it
13922 if(onMove->other->pause) // pondering opponent can always be paused immediately
13923 PauseEngine(onMove->other);
13925 SendToProgram("easy\n", onMove->other);
13927 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13928 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13930 PauseEngine(&first);
13932 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13933 } else { // human on move, pause pondering by either method
13935 PauseEngine(&first);
13936 else if(appData.ponderNextMove)
13937 SendToProgram("easy\n", &first);
13940 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13950 EditCommentEvent ()
13952 char title[MSG_SIZ];
13954 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13955 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13957 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13958 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13959 parseList[currentMove - 1]);
13962 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13969 char *tags = PGNTags(&gameInfo);
13971 EditTagsPopUp(tags, NULL);
13978 if(second.analyzing) {
13979 SendToProgram("exit\n", &second);
13980 second.analyzing = FALSE;
13982 if (second.pr == NoProc) StartChessProgram(&second);
13983 InitChessProgram(&second, FALSE);
13984 FeedMovesToProgram(&second, currentMove);
13986 SendToProgram("analyze\n", &second);
13987 second.analyzing = TRUE;
13991 /* Toggle ShowThinking */
13993 ToggleShowThinking()
13995 appData.showThinking = !appData.showThinking;
13996 ShowThinkingEvent();
14000 AnalyzeModeEvent ()
14004 if (!first.analysisSupport) {
14005 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14006 DisplayError(buf, 0);
14009 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14010 if (appData.icsActive) {
14011 if (gameMode != IcsObserving) {
14012 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14013 DisplayError(buf, 0);
14015 if (appData.icsEngineAnalyze) {
14016 if (appData.debugMode)
14017 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14023 /* if enable, user wants to disable icsEngineAnalyze */
14024 if (appData.icsEngineAnalyze) {
14029 appData.icsEngineAnalyze = TRUE;
14030 if (appData.debugMode)
14031 fprintf(debugFP, "ICS engine analyze starting... \n");
14034 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14035 if (appData.noChessProgram || gameMode == AnalyzeMode)
14038 if (gameMode != AnalyzeFile) {
14039 if (!appData.icsEngineAnalyze) {
14041 if (gameMode != EditGame) return 0;
14043 if (!appData.showThinking) ToggleShowThinking();
14044 ResurrectChessProgram();
14045 SendToProgram("analyze\n", &first);
14046 first.analyzing = TRUE;
14047 /*first.maybeThinking = TRUE;*/
14048 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14049 EngineOutputPopUp();
14051 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14056 StartAnalysisClock();
14057 GetTimeMark(&lastNodeCountTime);
14063 AnalyzeFileEvent ()
14065 if (appData.noChessProgram || gameMode == AnalyzeFile)
14068 if (!first.analysisSupport) {
14070 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14071 DisplayError(buf, 0);
14075 if (gameMode != AnalyzeMode) {
14076 keepInfo = 1; // mere annotating should not alter PGN tags
14079 if (gameMode != EditGame) return;
14080 if (!appData.showThinking) ToggleShowThinking();
14081 ResurrectChessProgram();
14082 SendToProgram("analyze\n", &first);
14083 first.analyzing = TRUE;
14084 /*first.maybeThinking = TRUE;*/
14085 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14086 EngineOutputPopUp();
14088 gameMode = AnalyzeFile;
14092 StartAnalysisClock();
14093 GetTimeMark(&lastNodeCountTime);
14095 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14096 AnalysisPeriodicEvent(1);
14100 MachineWhiteEvent ()
14103 char *bookHit = NULL;
14105 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14109 if (gameMode == PlayFromGameFile ||
14110 gameMode == TwoMachinesPlay ||
14111 gameMode == Training ||
14112 gameMode == AnalyzeMode ||
14113 gameMode == EndOfGame)
14116 if (gameMode == EditPosition)
14117 EditPositionDone(TRUE);
14119 if (!WhiteOnMove(currentMove)) {
14120 DisplayError(_("It is not White's turn"), 0);
14124 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14127 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14128 gameMode == AnalyzeFile)
14131 ResurrectChessProgram(); /* in case it isn't running */
14132 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14133 gameMode = MachinePlaysWhite;
14136 gameMode = MachinePlaysWhite;
14140 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14142 if (first.sendName) {
14143 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14144 SendToProgram(buf, &first);
14146 if (first.sendTime) {
14147 if (first.useColors) {
14148 SendToProgram("black\n", &first); /*gnu kludge*/
14150 SendTimeRemaining(&first, TRUE);
14152 if (first.useColors) {
14153 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14155 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14156 SetMachineThinkingEnables();
14157 first.maybeThinking = TRUE;
14161 if (appData.autoFlipView && !flipView) {
14162 flipView = !flipView;
14163 DrawPosition(FALSE, NULL);
14164 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14167 if(bookHit) { // [HGM] book: simulate book reply
14168 static char bookMove[MSG_SIZ]; // a bit generous?
14170 programStats.nodes = programStats.depth = programStats.time =
14171 programStats.score = programStats.got_only_move = 0;
14172 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14174 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14175 strcat(bookMove, bookHit);
14176 HandleMachineMove(bookMove, &first);
14181 MachineBlackEvent ()
14184 char *bookHit = NULL;
14186 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14190 if (gameMode == PlayFromGameFile ||
14191 gameMode == TwoMachinesPlay ||
14192 gameMode == Training ||
14193 gameMode == AnalyzeMode ||
14194 gameMode == EndOfGame)
14197 if (gameMode == EditPosition)
14198 EditPositionDone(TRUE);
14200 if (WhiteOnMove(currentMove)) {
14201 DisplayError(_("It is not Black's turn"), 0);
14205 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14208 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14209 gameMode == AnalyzeFile)
14212 ResurrectChessProgram(); /* in case it isn't running */
14213 gameMode = MachinePlaysBlack;
14217 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14219 if (first.sendName) {
14220 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14221 SendToProgram(buf, &first);
14223 if (first.sendTime) {
14224 if (first.useColors) {
14225 SendToProgram("white\n", &first); /*gnu kludge*/
14227 SendTimeRemaining(&first, FALSE);
14229 if (first.useColors) {
14230 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14232 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14233 SetMachineThinkingEnables();
14234 first.maybeThinking = TRUE;
14237 if (appData.autoFlipView && flipView) {
14238 flipView = !flipView;
14239 DrawPosition(FALSE, NULL);
14240 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14242 if(bookHit) { // [HGM] book: simulate book reply
14243 static char bookMove[MSG_SIZ]; // a bit generous?
14245 programStats.nodes = programStats.depth = programStats.time =
14246 programStats.score = programStats.got_only_move = 0;
14247 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14249 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14250 strcat(bookMove, bookHit);
14251 HandleMachineMove(bookMove, &first);
14257 DisplayTwoMachinesTitle ()
14260 if (appData.matchGames > 0) {
14261 if(appData.tourneyFile[0]) {
14262 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14263 gameInfo.white, _("vs."), gameInfo.black,
14264 nextGame+1, appData.matchGames+1,
14265 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14267 if (first.twoMachinesColor[0] == 'w') {
14268 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14269 gameInfo.white, _("vs."), gameInfo.black,
14270 first.matchWins, second.matchWins,
14271 matchGame - 1 - (first.matchWins + second.matchWins));
14273 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14274 gameInfo.white, _("vs."), gameInfo.black,
14275 second.matchWins, first.matchWins,
14276 matchGame - 1 - (first.matchWins + second.matchWins));
14279 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14285 SettingsMenuIfReady ()
14287 if (second.lastPing != second.lastPong) {
14288 DisplayMessage("", _("Waiting for second chess program"));
14289 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14293 DisplayMessage("", "");
14294 SettingsPopUp(&second);
14298 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14301 if (cps->pr == NoProc) {
14302 StartChessProgram(cps);
14303 if (cps->protocolVersion == 1) {
14305 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14307 /* kludge: allow timeout for initial "feature" command */
14308 if(retry != TwoMachinesEventIfReady) FreezeUI();
14309 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14310 DisplayMessage("", buf);
14311 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14319 TwoMachinesEvent P((void))
14323 ChessProgramState *onmove;
14324 char *bookHit = NULL;
14325 static int stalling = 0;
14329 if (appData.noChessProgram) return;
14331 switch (gameMode) {
14332 case TwoMachinesPlay:
14334 case MachinePlaysWhite:
14335 case MachinePlaysBlack:
14336 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14337 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14341 case BeginningOfGame:
14342 case PlayFromGameFile:
14345 if (gameMode != EditGame) return;
14348 EditPositionDone(TRUE);
14359 // forwardMostMove = currentMove;
14360 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14361 startingEngine = TRUE;
14363 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14365 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14366 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14367 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14370 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14372 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14373 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14374 startingEngine = FALSE;
14375 DisplayError("second engine does not play this", 0);
14380 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14381 SendToProgram("force\n", &second);
14383 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14386 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14387 if(appData.matchPause>10000 || appData.matchPause<10)
14388 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14389 wait = SubtractTimeMarks(&now, &pauseStart);
14390 if(wait < appData.matchPause) {
14391 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14394 // we are now committed to starting the game
14396 DisplayMessage("", "");
14397 if (startedFromSetupPosition) {
14398 SendBoard(&second, backwardMostMove);
14399 if (appData.debugMode) {
14400 fprintf(debugFP, "Two Machines\n");
14403 for (i = backwardMostMove; i < forwardMostMove; i++) {
14404 SendMoveToProgram(i, &second);
14407 gameMode = TwoMachinesPlay;
14408 pausing = startingEngine = FALSE;
14409 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14411 DisplayTwoMachinesTitle();
14413 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14418 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14419 SendToProgram(first.computerString, &first);
14420 if (first.sendName) {
14421 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14422 SendToProgram(buf, &first);
14424 SendToProgram(second.computerString, &second);
14425 if (second.sendName) {
14426 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14427 SendToProgram(buf, &second);
14431 if (!first.sendTime || !second.sendTime) {
14432 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14433 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14435 if (onmove->sendTime) {
14436 if (onmove->useColors) {
14437 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14439 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14441 if (onmove->useColors) {
14442 SendToProgram(onmove->twoMachinesColor, onmove);
14444 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14445 // SendToProgram("go\n", onmove);
14446 onmove->maybeThinking = TRUE;
14447 SetMachineThinkingEnables();
14451 if(bookHit) { // [HGM] book: simulate book reply
14452 static char bookMove[MSG_SIZ]; // a bit generous?
14454 programStats.nodes = programStats.depth = programStats.time =
14455 programStats.score = programStats.got_only_move = 0;
14456 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14458 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14459 strcat(bookMove, bookHit);
14460 savedMessage = bookMove; // args for deferred call
14461 savedState = onmove;
14462 ScheduleDelayedEvent(DeferredBookMove, 1);
14469 if (gameMode == Training) {
14470 SetTrainingModeOff();
14471 gameMode = PlayFromGameFile;
14472 DisplayMessage("", _("Training mode off"));
14474 gameMode = Training;
14475 animateTraining = appData.animate;
14477 /* make sure we are not already at the end of the game */
14478 if (currentMove < forwardMostMove) {
14479 SetTrainingModeOn();
14480 DisplayMessage("", _("Training mode on"));
14482 gameMode = PlayFromGameFile;
14483 DisplayError(_("Already at end of game"), 0);
14492 if (!appData.icsActive) return;
14493 switch (gameMode) {
14494 case IcsPlayingWhite:
14495 case IcsPlayingBlack:
14498 case BeginningOfGame:
14506 EditPositionDone(TRUE);
14519 gameMode = IcsIdle;
14529 switch (gameMode) {
14531 SetTrainingModeOff();
14533 case MachinePlaysWhite:
14534 case MachinePlaysBlack:
14535 case BeginningOfGame:
14536 SendToProgram("force\n", &first);
14537 SetUserThinkingEnables();
14539 case PlayFromGameFile:
14540 (void) StopLoadGameTimer();
14541 if (gameFileFP != NULL) {
14546 EditPositionDone(TRUE);
14551 SendToProgram("force\n", &first);
14553 case TwoMachinesPlay:
14554 GameEnds(EndOfFile, NULL, GE_PLAYER);
14555 ResurrectChessProgram();
14556 SetUserThinkingEnables();
14559 ResurrectChessProgram();
14561 case IcsPlayingBlack:
14562 case IcsPlayingWhite:
14563 DisplayError(_("Warning: You are still playing a game"), 0);
14566 DisplayError(_("Warning: You are still observing a game"), 0);
14569 DisplayError(_("Warning: You are still examining a game"), 0);
14580 first.offeredDraw = second.offeredDraw = 0;
14582 if (gameMode == PlayFromGameFile) {
14583 whiteTimeRemaining = timeRemaining[0][currentMove];
14584 blackTimeRemaining = timeRemaining[1][currentMove];
14588 if (gameMode == MachinePlaysWhite ||
14589 gameMode == MachinePlaysBlack ||
14590 gameMode == TwoMachinesPlay ||
14591 gameMode == EndOfGame) {
14592 i = forwardMostMove;
14593 while (i > currentMove) {
14594 SendToProgram("undo\n", &first);
14597 if(!adjustedClock) {
14598 whiteTimeRemaining = timeRemaining[0][currentMove];
14599 blackTimeRemaining = timeRemaining[1][currentMove];
14600 DisplayBothClocks();
14602 if (whiteFlag || blackFlag) {
14603 whiteFlag = blackFlag = 0;
14608 gameMode = EditGame;
14615 EditPositionEvent ()
14617 if (gameMode == EditPosition) {
14623 if (gameMode != EditGame) return;
14625 gameMode = EditPosition;
14628 if (currentMove > 0)
14629 CopyBoard(boards[0], boards[currentMove]);
14631 blackPlaysFirst = !WhiteOnMove(currentMove);
14633 currentMove = forwardMostMove = backwardMostMove = 0;
14634 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14636 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14642 /* [DM] icsEngineAnalyze - possible call from other functions */
14643 if (appData.icsEngineAnalyze) {
14644 appData.icsEngineAnalyze = FALSE;
14646 DisplayMessage("",_("Close ICS engine analyze..."));
14648 if (first.analysisSupport && first.analyzing) {
14649 SendToBoth("exit\n");
14650 first.analyzing = second.analyzing = FALSE;
14652 thinkOutput[0] = NULLCHAR;
14656 EditPositionDone (Boolean fakeRights)
14658 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14660 startedFromSetupPosition = TRUE;
14661 InitChessProgram(&first, FALSE);
14662 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14663 boards[0][EP_STATUS] = EP_NONE;
14664 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14665 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14666 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14667 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14668 } else boards[0][CASTLING][2] = NoRights;
14669 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14670 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14671 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14672 } else boards[0][CASTLING][5] = NoRights;
14673 if(gameInfo.variant == VariantSChess) {
14675 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14676 boards[0][VIRGIN][i] = 0;
14677 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14678 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14682 SendToProgram("force\n", &first);
14683 if (blackPlaysFirst) {
14684 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14685 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14686 currentMove = forwardMostMove = backwardMostMove = 1;
14687 CopyBoard(boards[1], boards[0]);
14689 currentMove = forwardMostMove = backwardMostMove = 0;
14691 SendBoard(&first, forwardMostMove);
14692 if (appData.debugMode) {
14693 fprintf(debugFP, "EditPosDone\n");
14696 DisplayMessage("", "");
14697 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14698 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14699 gameMode = EditGame;
14701 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14702 ClearHighlights(); /* [AS] */
14705 /* Pause for `ms' milliseconds */
14706 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14708 TimeDelay (long ms)
14715 } while (SubtractTimeMarks(&m2, &m1) < ms);
14718 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14720 SendMultiLineToICS (char *buf)
14722 char temp[MSG_SIZ+1], *p;
14729 strncpy(temp, buf, len);
14734 if (*p == '\n' || *p == '\r')
14739 strcat(temp, "\n");
14741 SendToPlayer(temp, strlen(temp));
14745 SetWhiteToPlayEvent ()
14747 if (gameMode == EditPosition) {
14748 blackPlaysFirst = FALSE;
14749 DisplayBothClocks(); /* works because currentMove is 0 */
14750 } else if (gameMode == IcsExamining) {
14751 SendToICS(ics_prefix);
14752 SendToICS("tomove white\n");
14757 SetBlackToPlayEvent ()
14759 if (gameMode == EditPosition) {
14760 blackPlaysFirst = TRUE;
14761 currentMove = 1; /* kludge */
14762 DisplayBothClocks();
14764 } else if (gameMode == IcsExamining) {
14765 SendToICS(ics_prefix);
14766 SendToICS("tomove black\n");
14771 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14774 ChessSquare piece = boards[0][y][x];
14775 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14776 static int lastVariant;
14778 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14780 switch (selection) {
14782 CopyBoard(currentBoard, boards[0]);
14783 CopyBoard(menuBoard, initialPosition);
14784 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14785 SendToICS(ics_prefix);
14786 SendToICS("bsetup clear\n");
14787 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14788 SendToICS(ics_prefix);
14789 SendToICS("clearboard\n");
14792 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14793 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14794 for (y = 0; y < BOARD_HEIGHT; y++) {
14795 if (gameMode == IcsExamining) {
14796 if (boards[currentMove][y][x] != EmptySquare) {
14797 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14802 if(boards[0][y][x] != p) nonEmpty++;
14803 boards[0][y][x] = p;
14806 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14808 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14809 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
14810 ChessSquare p = menuBoard[0][x];
14811 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14812 p = menuBoard[BOARD_HEIGHT-1][x];
14813 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14815 DisplayMessage("Clicking clock again restores position", "");
14816 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14817 if(!nonEmpty) { // asked to clear an empty board
14818 CopyBoard(boards[0], menuBoard);
14820 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14821 CopyBoard(boards[0], initialPosition);
14823 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14824 && !CompareBoards(nullBoard, erasedBoard)) {
14825 CopyBoard(boards[0], erasedBoard);
14827 CopyBoard(erasedBoard, currentBoard);
14831 if (gameMode == EditPosition) {
14832 DrawPosition(FALSE, boards[0]);
14837 SetWhiteToPlayEvent();
14841 SetBlackToPlayEvent();
14845 if (gameMode == IcsExamining) {
14846 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14847 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14850 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14851 if(x == BOARD_LEFT-2) {
14852 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14853 boards[0][y][1] = 0;
14855 if(x == BOARD_RGHT+1) {
14856 if(y >= gameInfo.holdingsSize) break;
14857 boards[0][y][BOARD_WIDTH-2] = 0;
14860 boards[0][y][x] = EmptySquare;
14861 DrawPosition(FALSE, boards[0]);
14866 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14867 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14868 selection = (ChessSquare) (PROMOTED piece);
14869 } else if(piece == EmptySquare) selection = WhiteSilver;
14870 else selection = (ChessSquare)((int)piece - 1);
14874 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14875 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14876 selection = (ChessSquare) (DEMOTED piece);
14877 } else if(piece == EmptySquare) selection = BlackSilver;
14878 else selection = (ChessSquare)((int)piece + 1);
14883 if(gameInfo.variant == VariantShatranj ||
14884 gameInfo.variant == VariantXiangqi ||
14885 gameInfo.variant == VariantCourier ||
14886 gameInfo.variant == VariantASEAN ||
14887 gameInfo.variant == VariantMakruk )
14888 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14893 if(gameInfo.variant == VariantXiangqi)
14894 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14895 if(gameInfo.variant == VariantKnightmate)
14896 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14899 if (gameMode == IcsExamining) {
14900 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14901 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14902 PieceToChar(selection), AAA + x, ONE + y);
14905 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14907 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14908 n = PieceToNumber(selection - BlackPawn);
14909 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14910 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14911 boards[0][BOARD_HEIGHT-1-n][1]++;
14913 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14914 n = PieceToNumber(selection);
14915 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14916 boards[0][n][BOARD_WIDTH-1] = selection;
14917 boards[0][n][BOARD_WIDTH-2]++;
14920 boards[0][y][x] = selection;
14921 DrawPosition(TRUE, boards[0]);
14923 fromX = fromY = -1;
14931 DropMenuEvent (ChessSquare selection, int x, int y)
14933 ChessMove moveType;
14935 switch (gameMode) {
14936 case IcsPlayingWhite:
14937 case MachinePlaysBlack:
14938 if (!WhiteOnMove(currentMove)) {
14939 DisplayMoveError(_("It is Black's turn"));
14942 moveType = WhiteDrop;
14944 case IcsPlayingBlack:
14945 case MachinePlaysWhite:
14946 if (WhiteOnMove(currentMove)) {
14947 DisplayMoveError(_("It is White's turn"));
14950 moveType = BlackDrop;
14953 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14959 if (moveType == BlackDrop && selection < BlackPawn) {
14960 selection = (ChessSquare) ((int) selection
14961 + (int) BlackPawn - (int) WhitePawn);
14963 if (boards[currentMove][y][x] != EmptySquare) {
14964 DisplayMoveError(_("That square is occupied"));
14968 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14974 /* Accept a pending offer of any kind from opponent */
14976 if (appData.icsActive) {
14977 SendToICS(ics_prefix);
14978 SendToICS("accept\n");
14979 } else if (cmailMsgLoaded) {
14980 if (currentMove == cmailOldMove &&
14981 commentList[cmailOldMove] != NULL &&
14982 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14983 "Black offers a draw" : "White offers a draw")) {
14985 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14986 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14988 DisplayError(_("There is no pending offer on this move"), 0);
14989 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14992 /* Not used for offers from chess program */
14999 /* Decline a pending offer of any kind from opponent */
15001 if (appData.icsActive) {
15002 SendToICS(ics_prefix);
15003 SendToICS("decline\n");
15004 } else if (cmailMsgLoaded) {
15005 if (currentMove == cmailOldMove &&
15006 commentList[cmailOldMove] != NULL &&
15007 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15008 "Black offers a draw" : "White offers a draw")) {
15010 AppendComment(cmailOldMove, "Draw declined", TRUE);
15011 DisplayComment(cmailOldMove - 1, "Draw declined");
15014 DisplayError(_("There is no pending offer on this move"), 0);
15017 /* Not used for offers from chess program */
15024 /* Issue ICS rematch command */
15025 if (appData.icsActive) {
15026 SendToICS(ics_prefix);
15027 SendToICS("rematch\n");
15034 /* Call your opponent's flag (claim a win on time) */
15035 if (appData.icsActive) {
15036 SendToICS(ics_prefix);
15037 SendToICS("flag\n");
15039 switch (gameMode) {
15042 case MachinePlaysWhite:
15045 GameEnds(GameIsDrawn, "Both players ran out of time",
15048 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15050 DisplayError(_("Your opponent is not out of time"), 0);
15053 case MachinePlaysBlack:
15056 GameEnds(GameIsDrawn, "Both players ran out of time",
15059 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15061 DisplayError(_("Your opponent is not out of time"), 0);
15069 ClockClick (int which)
15070 { // [HGM] code moved to back-end from winboard.c
15071 if(which) { // black clock
15072 if (gameMode == EditPosition || gameMode == IcsExamining) {
15073 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15074 SetBlackToPlayEvent();
15075 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15076 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15077 } else if (shiftKey) {
15078 AdjustClock(which, -1);
15079 } else if (gameMode == IcsPlayingWhite ||
15080 gameMode == MachinePlaysBlack) {
15083 } else { // white clock
15084 if (gameMode == EditPosition || gameMode == IcsExamining) {
15085 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15086 SetWhiteToPlayEvent();
15087 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15088 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15089 } else if (shiftKey) {
15090 AdjustClock(which, -1);
15091 } else if (gameMode == IcsPlayingBlack ||
15092 gameMode == MachinePlaysWhite) {
15101 /* Offer draw or accept pending draw offer from opponent */
15103 if (appData.icsActive) {
15104 /* Note: tournament rules require draw offers to be
15105 made after you make your move but before you punch
15106 your clock. Currently ICS doesn't let you do that;
15107 instead, you immediately punch your clock after making
15108 a move, but you can offer a draw at any time. */
15110 SendToICS(ics_prefix);
15111 SendToICS("draw\n");
15112 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15113 } else if (cmailMsgLoaded) {
15114 if (currentMove == cmailOldMove &&
15115 commentList[cmailOldMove] != NULL &&
15116 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15117 "Black offers a draw" : "White offers a draw")) {
15118 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15119 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15120 } else if (currentMove == cmailOldMove + 1) {
15121 char *offer = WhiteOnMove(cmailOldMove) ?
15122 "White offers a draw" : "Black offers a draw";
15123 AppendComment(currentMove, offer, TRUE);
15124 DisplayComment(currentMove - 1, offer);
15125 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15127 DisplayError(_("You must make your move before offering a draw"), 0);
15128 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15130 } else if (first.offeredDraw) {
15131 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15133 if (first.sendDrawOffers) {
15134 SendToProgram("draw\n", &first);
15135 userOfferedDraw = TRUE;
15143 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15145 if (appData.icsActive) {
15146 SendToICS(ics_prefix);
15147 SendToICS("adjourn\n");
15149 /* Currently GNU Chess doesn't offer or accept Adjourns */
15157 /* Offer Abort or accept pending Abort offer from opponent */
15159 if (appData.icsActive) {
15160 SendToICS(ics_prefix);
15161 SendToICS("abort\n");
15163 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15170 /* Resign. You can do this even if it's not your turn. */
15172 if (appData.icsActive) {
15173 SendToICS(ics_prefix);
15174 SendToICS("resign\n");
15176 switch (gameMode) {
15177 case MachinePlaysWhite:
15178 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15180 case MachinePlaysBlack:
15181 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15184 if (cmailMsgLoaded) {
15186 if (WhiteOnMove(cmailOldMove)) {
15187 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15189 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15191 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15202 StopObservingEvent ()
15204 /* Stop observing current games */
15205 SendToICS(ics_prefix);
15206 SendToICS("unobserve\n");
15210 StopExaminingEvent ()
15212 /* Stop observing current game */
15213 SendToICS(ics_prefix);
15214 SendToICS("unexamine\n");
15218 ForwardInner (int target)
15220 int limit; int oldSeekGraphUp = seekGraphUp;
15222 if (appData.debugMode)
15223 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15224 target, currentMove, forwardMostMove);
15226 if (gameMode == EditPosition)
15229 seekGraphUp = FALSE;
15230 MarkTargetSquares(1);
15232 if (gameMode == PlayFromGameFile && !pausing)
15235 if (gameMode == IcsExamining && pausing)
15236 limit = pauseExamForwardMostMove;
15238 limit = forwardMostMove;
15240 if (target > limit) target = limit;
15242 if (target > 0 && moveList[target - 1][0]) {
15243 int fromX, fromY, toX, toY;
15244 toX = moveList[target - 1][2] - AAA;
15245 toY = moveList[target - 1][3] - ONE;
15246 if (moveList[target - 1][1] == '@') {
15247 if (appData.highlightLastMove) {
15248 SetHighlights(-1, -1, toX, toY);
15251 fromX = moveList[target - 1][0] - AAA;
15252 fromY = moveList[target - 1][1] - ONE;
15253 if (target == currentMove + 1) {
15254 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15256 if (appData.highlightLastMove) {
15257 SetHighlights(fromX, fromY, toX, toY);
15261 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15262 gameMode == Training || gameMode == PlayFromGameFile ||
15263 gameMode == AnalyzeFile) {
15264 while (currentMove < target) {
15265 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15266 SendMoveToProgram(currentMove++, &first);
15269 currentMove = target;
15272 if (gameMode == EditGame || gameMode == EndOfGame) {
15273 whiteTimeRemaining = timeRemaining[0][currentMove];
15274 blackTimeRemaining = timeRemaining[1][currentMove];
15276 DisplayBothClocks();
15277 DisplayMove(currentMove - 1);
15278 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15279 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15280 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15281 DisplayComment(currentMove - 1, commentList[currentMove]);
15283 ClearMap(); // [HGM] exclude: invalidate map
15290 if (gameMode == IcsExamining && !pausing) {
15291 SendToICS(ics_prefix);
15292 SendToICS("forward\n");
15294 ForwardInner(currentMove + 1);
15301 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15302 /* to optimze, we temporarily turn off analysis mode while we feed
15303 * the remaining moves to the engine. Otherwise we get analysis output
15306 if (first.analysisSupport) {
15307 SendToProgram("exit\nforce\n", &first);
15308 first.analyzing = FALSE;
15312 if (gameMode == IcsExamining && !pausing) {
15313 SendToICS(ics_prefix);
15314 SendToICS("forward 999999\n");
15316 ForwardInner(forwardMostMove);
15319 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15320 /* we have fed all the moves, so reactivate analysis mode */
15321 SendToProgram("analyze\n", &first);
15322 first.analyzing = TRUE;
15323 /*first.maybeThinking = TRUE;*/
15324 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15329 BackwardInner (int target)
15331 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15333 if (appData.debugMode)
15334 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15335 target, currentMove, forwardMostMove);
15337 if (gameMode == EditPosition) return;
15338 seekGraphUp = FALSE;
15339 MarkTargetSquares(1);
15340 if (currentMove <= backwardMostMove) {
15342 DrawPosition(full_redraw, boards[currentMove]);
15345 if (gameMode == PlayFromGameFile && !pausing)
15348 if (moveList[target][0]) {
15349 int fromX, fromY, toX, toY;
15350 toX = moveList[target][2] - AAA;
15351 toY = moveList[target][3] - ONE;
15352 if (moveList[target][1] == '@') {
15353 if (appData.highlightLastMove) {
15354 SetHighlights(-1, -1, toX, toY);
15357 fromX = moveList[target][0] - AAA;
15358 fromY = moveList[target][1] - ONE;
15359 if (target == currentMove - 1) {
15360 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15362 if (appData.highlightLastMove) {
15363 SetHighlights(fromX, fromY, toX, toY);
15367 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15368 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15369 while (currentMove > target) {
15370 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15371 // null move cannot be undone. Reload program with move history before it.
15373 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15374 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15376 SendBoard(&first, i);
15377 if(second.analyzing) SendBoard(&second, i);
15378 for(currentMove=i; currentMove<target; currentMove++) {
15379 SendMoveToProgram(currentMove, &first);
15380 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15384 SendToBoth("undo\n");
15388 currentMove = target;
15391 if (gameMode == EditGame || gameMode == EndOfGame) {
15392 whiteTimeRemaining = timeRemaining[0][currentMove];
15393 blackTimeRemaining = timeRemaining[1][currentMove];
15395 DisplayBothClocks();
15396 DisplayMove(currentMove - 1);
15397 DrawPosition(full_redraw, boards[currentMove]);
15398 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15399 // [HGM] PV info: routine tests if comment empty
15400 DisplayComment(currentMove - 1, commentList[currentMove]);
15401 ClearMap(); // [HGM] exclude: invalidate map
15407 if (gameMode == IcsExamining && !pausing) {
15408 SendToICS(ics_prefix);
15409 SendToICS("backward\n");
15411 BackwardInner(currentMove - 1);
15418 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15419 /* to optimize, we temporarily turn off analysis mode while we undo
15420 * all the moves. Otherwise we get analysis output after each undo.
15422 if (first.analysisSupport) {
15423 SendToProgram("exit\nforce\n", &first);
15424 first.analyzing = FALSE;
15428 if (gameMode == IcsExamining && !pausing) {
15429 SendToICS(ics_prefix);
15430 SendToICS("backward 999999\n");
15432 BackwardInner(backwardMostMove);
15435 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15436 /* we have fed all the moves, so reactivate analysis mode */
15437 SendToProgram("analyze\n", &first);
15438 first.analyzing = TRUE;
15439 /*first.maybeThinking = TRUE;*/
15440 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15447 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15448 if (to >= forwardMostMove) to = forwardMostMove;
15449 if (to <= backwardMostMove) to = backwardMostMove;
15450 if (to < currentMove) {
15458 RevertEvent (Boolean annotate)
15460 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15463 if (gameMode != IcsExamining) {
15464 DisplayError(_("You are not examining a game"), 0);
15468 DisplayError(_("You can't revert while pausing"), 0);
15471 SendToICS(ics_prefix);
15472 SendToICS("revert\n");
15476 RetractMoveEvent ()
15478 switch (gameMode) {
15479 case MachinePlaysWhite:
15480 case MachinePlaysBlack:
15481 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15482 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15485 if (forwardMostMove < 2) return;
15486 currentMove = forwardMostMove = forwardMostMove - 2;
15487 whiteTimeRemaining = timeRemaining[0][currentMove];
15488 blackTimeRemaining = timeRemaining[1][currentMove];
15489 DisplayBothClocks();
15490 DisplayMove(currentMove - 1);
15491 ClearHighlights();/*!! could figure this out*/
15492 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15493 SendToProgram("remove\n", &first);
15494 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15497 case BeginningOfGame:
15501 case IcsPlayingWhite:
15502 case IcsPlayingBlack:
15503 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15504 SendToICS(ics_prefix);
15505 SendToICS("takeback 2\n");
15507 SendToICS(ics_prefix);
15508 SendToICS("takeback 1\n");
15517 ChessProgramState *cps;
15519 switch (gameMode) {
15520 case MachinePlaysWhite:
15521 if (!WhiteOnMove(forwardMostMove)) {
15522 DisplayError(_("It is your turn"), 0);
15527 case MachinePlaysBlack:
15528 if (WhiteOnMove(forwardMostMove)) {
15529 DisplayError(_("It is your turn"), 0);
15534 case TwoMachinesPlay:
15535 if (WhiteOnMove(forwardMostMove) ==
15536 (first.twoMachinesColor[0] == 'w')) {
15542 case BeginningOfGame:
15546 SendToProgram("?\n", cps);
15550 TruncateGameEvent ()
15553 if (gameMode != EditGame) return;
15560 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15561 if (forwardMostMove > currentMove) {
15562 if (gameInfo.resultDetails != NULL) {
15563 free(gameInfo.resultDetails);
15564 gameInfo.resultDetails = NULL;
15565 gameInfo.result = GameUnfinished;
15567 forwardMostMove = currentMove;
15568 HistorySet(parseList, backwardMostMove, forwardMostMove,
15576 if (appData.noChessProgram) return;
15577 switch (gameMode) {
15578 case MachinePlaysWhite:
15579 if (WhiteOnMove(forwardMostMove)) {
15580 DisplayError(_("Wait until your turn."), 0);
15584 case BeginningOfGame:
15585 case MachinePlaysBlack:
15586 if (!WhiteOnMove(forwardMostMove)) {
15587 DisplayError(_("Wait until your turn."), 0);
15592 DisplayError(_("No hint available"), 0);
15595 SendToProgram("hint\n", &first);
15596 hintRequested = TRUE;
15602 ListGame * lg = (ListGame *) gameList.head;
15605 static int secondTime = FALSE;
15607 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15608 DisplayError(_("Game list not loaded or empty"), 0);
15612 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15615 DisplayNote(_("Book file exists! Try again for overwrite."));
15619 creatingBook = TRUE;
15620 secondTime = FALSE;
15622 /* Get list size */
15623 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15624 LoadGame(f, nItem, "", TRUE);
15625 AddGameToBook(TRUE);
15626 lg = (ListGame *) lg->node.succ;
15629 creatingBook = FALSE;
15636 if (appData.noChessProgram) return;
15637 switch (gameMode) {
15638 case MachinePlaysWhite:
15639 if (WhiteOnMove(forwardMostMove)) {
15640 DisplayError(_("Wait until your turn."), 0);
15644 case BeginningOfGame:
15645 case MachinePlaysBlack:
15646 if (!WhiteOnMove(forwardMostMove)) {
15647 DisplayError(_("Wait until your turn."), 0);
15652 EditPositionDone(TRUE);
15654 case TwoMachinesPlay:
15659 SendToProgram("bk\n", &first);
15660 bookOutput[0] = NULLCHAR;
15661 bookRequested = TRUE;
15667 char *tags = PGNTags(&gameInfo);
15668 TagsPopUp(tags, CmailMsg());
15672 /* end button procedures */
15675 PrintPosition (FILE *fp, int move)
15679 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15680 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15681 char c = PieceToChar(boards[move][i][j]);
15682 fputc(c == 'x' ? '.' : c, fp);
15683 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15686 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15687 fprintf(fp, "white to play\n");
15689 fprintf(fp, "black to play\n");
15693 PrintOpponents (FILE *fp)
15695 if (gameInfo.white != NULL) {
15696 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15702 /* Find last component of program's own name, using some heuristics */
15704 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15707 int local = (strcmp(host, "localhost") == 0);
15708 while (!local && (p = strchr(prog, ';')) != NULL) {
15710 while (*p == ' ') p++;
15713 if (*prog == '"' || *prog == '\'') {
15714 q = strchr(prog + 1, *prog);
15716 q = strchr(prog, ' ');
15718 if (q == NULL) q = prog + strlen(prog);
15720 while (p >= prog && *p != '/' && *p != '\\') p--;
15722 if(p == prog && *p == '"') p++;
15724 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15725 memcpy(buf, p, q - p);
15726 buf[q - p] = NULLCHAR;
15734 TimeControlTagValue ()
15737 if (!appData.clockMode) {
15738 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15739 } else if (movesPerSession > 0) {
15740 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15741 } else if (timeIncrement == 0) {
15742 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15744 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15746 return StrSave(buf);
15752 /* This routine is used only for certain modes */
15753 VariantClass v = gameInfo.variant;
15754 ChessMove r = GameUnfinished;
15757 if(keepInfo) return;
15759 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15760 r = gameInfo.result;
15761 p = gameInfo.resultDetails;
15762 gameInfo.resultDetails = NULL;
15764 ClearGameInfo(&gameInfo);
15765 gameInfo.variant = v;
15767 switch (gameMode) {
15768 case MachinePlaysWhite:
15769 gameInfo.event = StrSave( appData.pgnEventHeader );
15770 gameInfo.site = StrSave(HostName());
15771 gameInfo.date = PGNDate();
15772 gameInfo.round = StrSave("-");
15773 gameInfo.white = StrSave(first.tidy);
15774 gameInfo.black = StrSave(UserName());
15775 gameInfo.timeControl = TimeControlTagValue();
15778 case MachinePlaysBlack:
15779 gameInfo.event = StrSave( appData.pgnEventHeader );
15780 gameInfo.site = StrSave(HostName());
15781 gameInfo.date = PGNDate();
15782 gameInfo.round = StrSave("-");
15783 gameInfo.white = StrSave(UserName());
15784 gameInfo.black = StrSave(first.tidy);
15785 gameInfo.timeControl = TimeControlTagValue();
15788 case TwoMachinesPlay:
15789 gameInfo.event = StrSave( appData.pgnEventHeader );
15790 gameInfo.site = StrSave(HostName());
15791 gameInfo.date = PGNDate();
15794 snprintf(buf, MSG_SIZ, "%d", roundNr);
15795 gameInfo.round = StrSave(buf);
15797 gameInfo.round = StrSave("-");
15799 if (first.twoMachinesColor[0] == 'w') {
15800 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15801 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15803 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15804 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15806 gameInfo.timeControl = TimeControlTagValue();
15810 gameInfo.event = StrSave("Edited game");
15811 gameInfo.site = StrSave(HostName());
15812 gameInfo.date = PGNDate();
15813 gameInfo.round = StrSave("-");
15814 gameInfo.white = StrSave("-");
15815 gameInfo.black = StrSave("-");
15816 gameInfo.result = r;
15817 gameInfo.resultDetails = p;
15821 gameInfo.event = StrSave("Edited position");
15822 gameInfo.site = StrSave(HostName());
15823 gameInfo.date = PGNDate();
15824 gameInfo.round = StrSave("-");
15825 gameInfo.white = StrSave("-");
15826 gameInfo.black = StrSave("-");
15829 case IcsPlayingWhite:
15830 case IcsPlayingBlack:
15835 case PlayFromGameFile:
15836 gameInfo.event = StrSave("Game from non-PGN file");
15837 gameInfo.site = StrSave(HostName());
15838 gameInfo.date = PGNDate();
15839 gameInfo.round = StrSave("-");
15840 gameInfo.white = StrSave("?");
15841 gameInfo.black = StrSave("?");
15850 ReplaceComment (int index, char *text)
15856 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15857 pvInfoList[index-1].depth == len &&
15858 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15859 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15860 while (*text == '\n') text++;
15861 len = strlen(text);
15862 while (len > 0 && text[len - 1] == '\n') len--;
15864 if (commentList[index] != NULL)
15865 free(commentList[index]);
15868 commentList[index] = NULL;
15871 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15872 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15873 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15874 commentList[index] = (char *) malloc(len + 2);
15875 strncpy(commentList[index], text, len);
15876 commentList[index][len] = '\n';
15877 commentList[index][len + 1] = NULLCHAR;
15879 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15881 commentList[index] = (char *) malloc(len + 7);
15882 safeStrCpy(commentList[index], "{\n", 3);
15883 safeStrCpy(commentList[index]+2, text, len+1);
15884 commentList[index][len+2] = NULLCHAR;
15885 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15886 strcat(commentList[index], "\n}\n");
15891 CrushCRs (char *text)
15899 if (ch == '\r') continue;
15901 } while (ch != '\0');
15905 AppendComment (int index, char *text, Boolean addBraces)
15906 /* addBraces tells if we should add {} */
15911 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15912 if(addBraces == 3) addBraces = 0; else // force appending literally
15913 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15916 while (*text == '\n') text++;
15917 len = strlen(text);
15918 while (len > 0 && text[len - 1] == '\n') len--;
15919 text[len] = NULLCHAR;
15921 if (len == 0) return;
15923 if (commentList[index] != NULL) {
15924 Boolean addClosingBrace = addBraces;
15925 old = commentList[index];
15926 oldlen = strlen(old);
15927 while(commentList[index][oldlen-1] == '\n')
15928 commentList[index][--oldlen] = NULLCHAR;
15929 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15930 safeStrCpy(commentList[index], old, oldlen + len + 6);
15932 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15933 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15934 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15935 while (*text == '\n') { text++; len--; }
15936 commentList[index][--oldlen] = NULLCHAR;
15938 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15939 else strcat(commentList[index], "\n");
15940 strcat(commentList[index], text);
15941 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15942 else strcat(commentList[index], "\n");
15944 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15946 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15947 else commentList[index][0] = NULLCHAR;
15948 strcat(commentList[index], text);
15949 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15950 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15955 FindStr (char * text, char * sub_text)
15957 char * result = strstr( text, sub_text );
15959 if( result != NULL ) {
15960 result += strlen( sub_text );
15966 /* [AS] Try to extract PV info from PGN comment */
15967 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15969 GetInfoFromComment (int index, char * text)
15971 char * sep = text, *p;
15973 if( text != NULL && index > 0 ) {
15976 int time = -1, sec = 0, deci;
15977 char * s_eval = FindStr( text, "[%eval " );
15978 char * s_emt = FindStr( text, "[%emt " );
15980 if( s_eval != NULL || s_emt != NULL ) {
15982 if(0) { // [HGM] this code is not finished, and could actually be detrimental
15987 if( s_eval != NULL ) {
15988 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15992 if( delim != ']' ) {
15997 if( s_emt != NULL ) {
16002 /* We expect something like: [+|-]nnn.nn/dd */
16005 if(*text != '{') return text; // [HGM] braces: must be normal comment
16007 sep = strchr( text, '/' );
16008 if( sep == NULL || sep < (text+4) ) {
16013 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16014 if(p[1] == '(') { // comment starts with PV
16015 p = strchr(p, ')'); // locate end of PV
16016 if(p == NULL || sep < p+5) return text;
16017 // at this point we have something like "{(.*) +0.23/6 ..."
16018 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16019 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16020 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16022 time = -1; sec = -1; deci = -1;
16023 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16024 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16025 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16026 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16030 if( score_lo < 0 || score_lo >= 100 ) {
16034 if(sec >= 0) time = 600*time + 10*sec; else
16035 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16037 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16039 /* [HGM] PV time: now locate end of PV info */
16040 while( *++sep >= '0' && *sep <= '9'); // strip depth
16042 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16044 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16046 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16047 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16058 pvInfoList[index-1].depth = depth;
16059 pvInfoList[index-1].score = score;
16060 pvInfoList[index-1].time = 10*time; // centi-sec
16061 if(*sep == '}') *sep = 0; else *--sep = '{';
16062 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16068 SendToProgram (char *message, ChessProgramState *cps)
16070 int count, outCount, error;
16073 if (cps->pr == NoProc) return;
16076 if (appData.debugMode) {
16079 fprintf(debugFP, "%ld >%-6s: %s",
16080 SubtractTimeMarks(&now, &programStartTime),
16081 cps->which, message);
16083 fprintf(serverFP, "%ld >%-6s: %s",
16084 SubtractTimeMarks(&now, &programStartTime),
16085 cps->which, message), fflush(serverFP);
16088 count = strlen(message);
16089 outCount = OutputToProcess(cps->pr, message, count, &error);
16090 if (outCount < count && !exiting
16091 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16092 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16093 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16094 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16095 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16096 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16097 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16098 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16100 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16101 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16102 gameInfo.result = res;
16104 gameInfo.resultDetails = StrSave(buf);
16106 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16107 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16112 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16116 ChessProgramState *cps = (ChessProgramState *)closure;
16118 if (isr != cps->isr) return; /* Killed intentionally */
16121 RemoveInputSource(cps->isr);
16122 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16123 _(cps->which), cps->program);
16124 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16125 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16126 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16127 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16128 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16129 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16131 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16132 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16133 gameInfo.result = res;
16135 gameInfo.resultDetails = StrSave(buf);
16137 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16138 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16140 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16141 _(cps->which), cps->program);
16142 RemoveInputSource(cps->isr);
16144 /* [AS] Program is misbehaving badly... kill it */
16145 if( count == -2 ) {
16146 DestroyChildProcess( cps->pr, 9 );
16150 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16155 if ((end_str = strchr(message, '\r')) != NULL)
16156 *end_str = NULLCHAR;
16157 if ((end_str = strchr(message, '\n')) != NULL)
16158 *end_str = NULLCHAR;
16160 if (appData.debugMode) {
16161 TimeMark now; int print = 1;
16162 char *quote = ""; char c; int i;
16164 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16165 char start = message[0];
16166 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16167 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16168 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16169 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16170 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16171 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16172 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16173 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16174 sscanf(message, "hint: %c", &c)!=1 &&
16175 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16176 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16177 print = (appData.engineComments >= 2);
16179 message[0] = start; // restore original message
16183 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16184 SubtractTimeMarks(&now, &programStartTime), cps->which,
16188 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16189 SubtractTimeMarks(&now, &programStartTime), cps->which,
16191 message), fflush(serverFP);
16195 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16196 if (appData.icsEngineAnalyze) {
16197 if (strstr(message, "whisper") != NULL ||
16198 strstr(message, "kibitz") != NULL ||
16199 strstr(message, "tellics") != NULL) return;
16202 HandleMachineMove(message, cps);
16207 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16212 if( timeControl_2 > 0 ) {
16213 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16214 tc = timeControl_2;
16217 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16218 inc /= cps->timeOdds;
16219 st /= cps->timeOdds;
16221 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16224 /* Set exact time per move, normally using st command */
16225 if (cps->stKludge) {
16226 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16228 if (seconds == 0) {
16229 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16231 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16234 snprintf(buf, MSG_SIZ, "st %d\n", st);
16237 /* Set conventional or incremental time control, using level command */
16238 if (seconds == 0) {
16239 /* Note old gnuchess bug -- minutes:seconds used to not work.
16240 Fixed in later versions, but still avoid :seconds
16241 when seconds is 0. */
16242 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16244 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16245 seconds, inc/1000.);
16248 SendToProgram(buf, cps);
16250 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16251 /* Orthogonally, limit search to given depth */
16253 if (cps->sdKludge) {
16254 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16256 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16258 SendToProgram(buf, cps);
16261 if(cps->nps >= 0) { /* [HGM] nps */
16262 if(cps->supportsNPS == FALSE)
16263 cps->nps = -1; // don't use if engine explicitly says not supported!
16265 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16266 SendToProgram(buf, cps);
16271 ChessProgramState *
16273 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16275 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16276 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16282 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16284 char message[MSG_SIZ];
16287 /* Note: this routine must be called when the clocks are stopped
16288 or when they have *just* been set or switched; otherwise
16289 it will be off by the time since the current tick started.
16291 if (machineWhite) {
16292 time = whiteTimeRemaining / 10;
16293 otime = blackTimeRemaining / 10;
16295 time = blackTimeRemaining / 10;
16296 otime = whiteTimeRemaining / 10;
16298 /* [HGM] translate opponent's time by time-odds factor */
16299 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16301 if (time <= 0) time = 1;
16302 if (otime <= 0) otime = 1;
16304 snprintf(message, MSG_SIZ, "time %ld\n", time);
16305 SendToProgram(message, cps);
16307 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16308 SendToProgram(message, cps);
16312 EngineDefinedVariant (ChessProgramState *cps, int n)
16313 { // return name of n-th unknown variant that engine supports
16314 static char buf[MSG_SIZ];
16315 char *p, *s = cps->variants;
16316 if(!s) return NULL;
16317 do { // parse string from variants feature
16319 p = strchr(s, ',');
16320 if(p) *p = NULLCHAR;
16321 v = StringToVariant(s);
16322 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16323 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16324 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16327 if(n < 0) return buf;
16333 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16336 int len = strlen(name);
16339 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16341 sscanf(*p, "%d", &val);
16343 while (**p && **p != ' ')
16345 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16346 SendToProgram(buf, cps);
16353 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16356 int len = strlen(name);
16357 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16359 sscanf(*p, "%d", loc);
16360 while (**p && **p != ' ') (*p)++;
16361 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16362 SendToProgram(buf, cps);
16369 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16372 int len = strlen(name);
16373 if (strncmp((*p), name, len) == 0
16374 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16376 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16377 sscanf(*p, "%[^\"]", *loc);
16378 while (**p && **p != '\"') (*p)++;
16379 if (**p == '\"') (*p)++;
16380 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16381 SendToProgram(buf, cps);
16388 ParseOption (Option *opt, ChessProgramState *cps)
16389 // [HGM] options: process the string that defines an engine option, and determine
16390 // name, type, default value, and allowed value range
16392 char *p, *q, buf[MSG_SIZ];
16393 int n, min = (-1)<<31, max = 1<<31, def;
16395 if(p = strstr(opt->name, " -spin ")) {
16396 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16397 if(max < min) max = min; // enforce consistency
16398 if(def < min) def = min;
16399 if(def > max) def = max;
16404 } else if((p = strstr(opt->name, " -slider "))) {
16405 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16406 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16407 if(max < min) max = min; // enforce consistency
16408 if(def < min) def = min;
16409 if(def > max) def = max;
16413 opt->type = Spin; // Slider;
16414 } else if((p = strstr(opt->name, " -string "))) {
16415 opt->textValue = p+9;
16416 opt->type = TextBox;
16417 } else if((p = strstr(opt->name, " -file "))) {
16418 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16419 opt->textValue = p+7;
16420 opt->type = FileName; // FileName;
16421 } else if((p = strstr(opt->name, " -path "))) {
16422 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16423 opt->textValue = p+7;
16424 opt->type = PathName; // PathName;
16425 } else if(p = strstr(opt->name, " -check ")) {
16426 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16427 opt->value = (def != 0);
16428 opt->type = CheckBox;
16429 } else if(p = strstr(opt->name, " -combo ")) {
16430 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16431 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16432 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16433 opt->value = n = 0;
16434 while(q = StrStr(q, " /// ")) {
16435 n++; *q = 0; // count choices, and null-terminate each of them
16437 if(*q == '*') { // remember default, which is marked with * prefix
16441 cps->comboList[cps->comboCnt++] = q;
16443 cps->comboList[cps->comboCnt++] = NULL;
16445 opt->type = ComboBox;
16446 } else if(p = strstr(opt->name, " -button")) {
16447 opt->type = Button;
16448 } else if(p = strstr(opt->name, " -save")) {
16449 opt->type = SaveButton;
16450 } else return FALSE;
16451 *p = 0; // terminate option name
16452 // now look if the command-line options define a setting for this engine option.
16453 if(cps->optionSettings && cps->optionSettings[0])
16454 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16455 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16456 snprintf(buf, MSG_SIZ, "option %s", p);
16457 if(p = strstr(buf, ",")) *p = 0;
16458 if(q = strchr(buf, '=')) switch(opt->type) {
16460 for(n=0; n<opt->max; n++)
16461 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16464 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16468 opt->value = atoi(q+1);
16473 SendToProgram(buf, cps);
16479 FeatureDone (ChessProgramState *cps, int val)
16481 DelayedEventCallback cb = GetDelayedEvent();
16482 if ((cb == InitBackEnd3 && cps == &first) ||
16483 (cb == SettingsMenuIfReady && cps == &second) ||
16484 (cb == LoadEngine) ||
16485 (cb == TwoMachinesEventIfReady)) {
16486 CancelDelayedEvent();
16487 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16489 cps->initDone = val;
16490 if(val) cps->reload = FALSE;
16493 /* Parse feature command from engine */
16495 ParseFeatures (char *args, ChessProgramState *cps)
16503 while (*p == ' ') p++;
16504 if (*p == NULLCHAR) return;
16506 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16507 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16508 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16509 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16510 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16511 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16512 if (BoolFeature(&p, "reuse", &val, cps)) {
16513 /* Engine can disable reuse, but can't enable it if user said no */
16514 if (!val) cps->reuse = FALSE;
16517 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16518 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16519 if (gameMode == TwoMachinesPlay) {
16520 DisplayTwoMachinesTitle();
16526 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16527 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16528 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16529 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16530 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16531 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16532 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16533 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16534 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16535 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16536 if (IntFeature(&p, "done", &val, cps)) {
16537 FeatureDone(cps, val);
16540 /* Added by Tord: */
16541 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16542 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16543 /* End of additions by Tord */
16545 /* [HGM] added features: */
16546 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16547 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16548 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16549 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16550 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16551 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16552 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16553 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16554 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16555 FREE(cps->option[cps->nrOptions].name);
16556 cps->option[cps->nrOptions].name = q; q = NULL;
16557 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16558 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16559 SendToProgram(buf, cps);
16562 if(cps->nrOptions >= MAX_OPTIONS) {
16564 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16565 DisplayError(buf, 0);
16569 /* End of additions by HGM */
16571 /* unknown feature: complain and skip */
16573 while (*q && *q != '=') q++;
16574 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16575 SendToProgram(buf, cps);
16581 while (*p && *p != '\"') p++;
16582 if (*p == '\"') p++;
16584 while (*p && *p != ' ') p++;
16592 PeriodicUpdatesEvent (int newState)
16594 if (newState == appData.periodicUpdates)
16597 appData.periodicUpdates=newState;
16599 /* Display type changes, so update it now */
16600 // DisplayAnalysis();
16602 /* Get the ball rolling again... */
16604 AnalysisPeriodicEvent(1);
16605 StartAnalysisClock();
16610 PonderNextMoveEvent (int newState)
16612 if (newState == appData.ponderNextMove) return;
16613 if (gameMode == EditPosition) EditPositionDone(TRUE);
16615 SendToProgram("hard\n", &first);
16616 if (gameMode == TwoMachinesPlay) {
16617 SendToProgram("hard\n", &second);
16620 SendToProgram("easy\n", &first);
16621 thinkOutput[0] = NULLCHAR;
16622 if (gameMode == TwoMachinesPlay) {
16623 SendToProgram("easy\n", &second);
16626 appData.ponderNextMove = newState;
16630 NewSettingEvent (int option, int *feature, char *command, int value)
16634 if (gameMode == EditPosition) EditPositionDone(TRUE);
16635 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16636 if(feature == NULL || *feature) SendToProgram(buf, &first);
16637 if (gameMode == TwoMachinesPlay) {
16638 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16643 ShowThinkingEvent ()
16644 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16646 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16647 int newState = appData.showThinking
16648 // [HGM] thinking: other features now need thinking output as well
16649 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16651 if (oldState == newState) return;
16652 oldState = newState;
16653 if (gameMode == EditPosition) EditPositionDone(TRUE);
16655 SendToProgram("post\n", &first);
16656 if (gameMode == TwoMachinesPlay) {
16657 SendToProgram("post\n", &second);
16660 SendToProgram("nopost\n", &first);
16661 thinkOutput[0] = NULLCHAR;
16662 if (gameMode == TwoMachinesPlay) {
16663 SendToProgram("nopost\n", &second);
16666 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16670 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16672 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16673 if (pr == NoProc) return;
16674 AskQuestion(title, question, replyPrefix, pr);
16678 TypeInEvent (char firstChar)
16680 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16681 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16682 gameMode == AnalyzeMode || gameMode == EditGame ||
16683 gameMode == EditPosition || gameMode == IcsExamining ||
16684 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16685 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16686 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16687 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16688 gameMode == Training) PopUpMoveDialog(firstChar);
16692 TypeInDoneEvent (char *move)
16695 int n, fromX, fromY, toX, toY;
16697 ChessMove moveType;
16700 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16701 EditPositionPasteFEN(move);
16704 // [HGM] movenum: allow move number to be typed in any mode
16705 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16709 // undocumented kludge: allow command-line option to be typed in!
16710 // (potentially fatal, and does not implement the effect of the option.)
16711 // should only be used for options that are values on which future decisions will be made,
16712 // and definitely not on options that would be used during initialization.
16713 if(strstr(move, "!!! -") == move) {
16714 ParseArgsFromString(move+4);
16718 if (gameMode != EditGame && currentMove != forwardMostMove &&
16719 gameMode != Training) {
16720 DisplayMoveError(_("Displayed move is not current"));
16722 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16723 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16724 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16725 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16726 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16727 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16729 DisplayMoveError(_("Could not parse move"));
16735 DisplayMove (int moveNumber)
16737 char message[MSG_SIZ];
16739 char cpThinkOutput[MSG_SIZ];
16741 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16743 if (moveNumber == forwardMostMove - 1 ||
16744 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16746 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16748 if (strchr(cpThinkOutput, '\n')) {
16749 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16752 *cpThinkOutput = NULLCHAR;
16755 /* [AS] Hide thinking from human user */
16756 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16757 *cpThinkOutput = NULLCHAR;
16758 if( thinkOutput[0] != NULLCHAR ) {
16761 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16762 cpThinkOutput[i] = '.';
16764 cpThinkOutput[i] = NULLCHAR;
16765 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16769 if (moveNumber == forwardMostMove - 1 &&
16770 gameInfo.resultDetails != NULL) {
16771 if (gameInfo.resultDetails[0] == NULLCHAR) {
16772 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16774 snprintf(res, MSG_SIZ, " {%s} %s",
16775 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16781 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16782 DisplayMessage(res, cpThinkOutput);
16784 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16785 WhiteOnMove(moveNumber) ? " " : ".. ",
16786 parseList[moveNumber], res);
16787 DisplayMessage(message, cpThinkOutput);
16792 DisplayComment (int moveNumber, char *text)
16794 char title[MSG_SIZ];
16796 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16797 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16799 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16800 WhiteOnMove(moveNumber) ? " " : ".. ",
16801 parseList[moveNumber]);
16803 if (text != NULL && (appData.autoDisplayComment || commentUp))
16804 CommentPopUp(title, text);
16807 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16808 * might be busy thinking or pondering. It can be omitted if your
16809 * gnuchess is configured to stop thinking immediately on any user
16810 * input. However, that gnuchess feature depends on the FIONREAD
16811 * ioctl, which does not work properly on some flavors of Unix.
16814 Attention (ChessProgramState *cps)
16817 if (!cps->useSigint) return;
16818 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16819 switch (gameMode) {
16820 case MachinePlaysWhite:
16821 case MachinePlaysBlack:
16822 case TwoMachinesPlay:
16823 case IcsPlayingWhite:
16824 case IcsPlayingBlack:
16827 /* Skip if we know it isn't thinking */
16828 if (!cps->maybeThinking) return;
16829 if (appData.debugMode)
16830 fprintf(debugFP, "Interrupting %s\n", cps->which);
16831 InterruptChildProcess(cps->pr);
16832 cps->maybeThinking = FALSE;
16837 #endif /*ATTENTION*/
16843 if (whiteTimeRemaining <= 0) {
16846 if (appData.icsActive) {
16847 if (appData.autoCallFlag &&
16848 gameMode == IcsPlayingBlack && !blackFlag) {
16849 SendToICS(ics_prefix);
16850 SendToICS("flag\n");
16854 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16856 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16857 if (appData.autoCallFlag) {
16858 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16865 if (blackTimeRemaining <= 0) {
16868 if (appData.icsActive) {
16869 if (appData.autoCallFlag &&
16870 gameMode == IcsPlayingWhite && !whiteFlag) {
16871 SendToICS(ics_prefix);
16872 SendToICS("flag\n");
16876 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16878 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16879 if (appData.autoCallFlag) {
16880 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16891 CheckTimeControl ()
16893 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16894 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16897 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16899 if ( !WhiteOnMove(forwardMostMove) ) {
16900 /* White made time control */
16901 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16902 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16903 /* [HGM] time odds: correct new time quota for time odds! */
16904 / WhitePlayer()->timeOdds;
16905 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16907 lastBlack -= blackTimeRemaining;
16908 /* Black made time control */
16909 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16910 / WhitePlayer()->other->timeOdds;
16911 lastWhite = whiteTimeRemaining;
16916 DisplayBothClocks ()
16918 int wom = gameMode == EditPosition ?
16919 !blackPlaysFirst : WhiteOnMove(currentMove);
16920 DisplayWhiteClock(whiteTimeRemaining, wom);
16921 DisplayBlackClock(blackTimeRemaining, !wom);
16925 /* Timekeeping seems to be a portability nightmare. I think everyone
16926 has ftime(), but I'm really not sure, so I'm including some ifdefs
16927 to use other calls if you don't. Clocks will be less accurate if
16928 you have neither ftime nor gettimeofday.
16931 /* VS 2008 requires the #include outside of the function */
16932 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16933 #include <sys/timeb.h>
16936 /* Get the current time as a TimeMark */
16938 GetTimeMark (TimeMark *tm)
16940 #if HAVE_GETTIMEOFDAY
16942 struct timeval timeVal;
16943 struct timezone timeZone;
16945 gettimeofday(&timeVal, &timeZone);
16946 tm->sec = (long) timeVal.tv_sec;
16947 tm->ms = (int) (timeVal.tv_usec / 1000L);
16949 #else /*!HAVE_GETTIMEOFDAY*/
16952 // include <sys/timeb.h> / moved to just above start of function
16953 struct timeb timeB;
16956 tm->sec = (long) timeB.time;
16957 tm->ms = (int) timeB.millitm;
16959 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16960 tm->sec = (long) time(NULL);
16966 /* Return the difference in milliseconds between two
16967 time marks. We assume the difference will fit in a long!
16970 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16972 return 1000L*(tm2->sec - tm1->sec) +
16973 (long) (tm2->ms - tm1->ms);
16978 * Code to manage the game clocks.
16980 * In tournament play, black starts the clock and then white makes a move.
16981 * We give the human user a slight advantage if he is playing white---the
16982 * clocks don't run until he makes his first move, so it takes zero time.
16983 * Also, we don't account for network lag, so we could get out of sync
16984 * with GNU Chess's clock -- but then, referees are always right.
16987 static TimeMark tickStartTM;
16988 static long intendedTickLength;
16991 NextTickLength (long timeRemaining)
16993 long nominalTickLength, nextTickLength;
16995 if (timeRemaining > 0L && timeRemaining <= 10000L)
16996 nominalTickLength = 100L;
16998 nominalTickLength = 1000L;
16999 nextTickLength = timeRemaining % nominalTickLength;
17000 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17002 return nextTickLength;
17005 /* Adjust clock one minute up or down */
17007 AdjustClock (Boolean which, int dir)
17009 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17010 if(which) blackTimeRemaining += 60000*dir;
17011 else whiteTimeRemaining += 60000*dir;
17012 DisplayBothClocks();
17013 adjustedClock = TRUE;
17016 /* Stop clocks and reset to a fresh time control */
17020 (void) StopClockTimer();
17021 if (appData.icsActive) {
17022 whiteTimeRemaining = blackTimeRemaining = 0;
17023 } else if (searchTime) {
17024 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17025 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17026 } else { /* [HGM] correct new time quote for time odds */
17027 whiteTC = blackTC = fullTimeControlString;
17028 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17029 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17031 if (whiteFlag || blackFlag) {
17033 whiteFlag = blackFlag = FALSE;
17035 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17036 DisplayBothClocks();
17037 adjustedClock = FALSE;
17040 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17042 /* Decrement running clock by amount of time that has passed */
17046 long timeRemaining;
17047 long lastTickLength, fudge;
17050 if (!appData.clockMode) return;
17051 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17055 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17057 /* Fudge if we woke up a little too soon */
17058 fudge = intendedTickLength - lastTickLength;
17059 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17061 if (WhiteOnMove(forwardMostMove)) {
17062 if(whiteNPS >= 0) lastTickLength = 0;
17063 timeRemaining = whiteTimeRemaining -= lastTickLength;
17064 if(timeRemaining < 0 && !appData.icsActive) {
17065 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17066 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17067 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17068 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17071 DisplayWhiteClock(whiteTimeRemaining - fudge,
17072 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17074 if(blackNPS >= 0) lastTickLength = 0;
17075 timeRemaining = blackTimeRemaining -= lastTickLength;
17076 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17077 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17079 blackStartMove = forwardMostMove;
17080 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17083 DisplayBlackClock(blackTimeRemaining - fudge,
17084 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17086 if (CheckFlags()) return;
17088 if(twoBoards) { // count down secondary board's clocks as well
17089 activePartnerTime -= lastTickLength;
17091 if(activePartner == 'W')
17092 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17094 DisplayBlackClock(activePartnerTime, TRUE);
17099 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17100 StartClockTimer(intendedTickLength);
17102 /* if the time remaining has fallen below the alarm threshold, sound the
17103 * alarm. if the alarm has sounded and (due to a takeback or time control
17104 * with increment) the time remaining has increased to a level above the
17105 * threshold, reset the alarm so it can sound again.
17108 if (appData.icsActive && appData.icsAlarm) {
17110 /* make sure we are dealing with the user's clock */
17111 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17112 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17115 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17116 alarmSounded = FALSE;
17117 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17119 alarmSounded = TRUE;
17125 /* A player has just moved, so stop the previously running
17126 clock and (if in clock mode) start the other one.
17127 We redisplay both clocks in case we're in ICS mode, because
17128 ICS gives us an update to both clocks after every move.
17129 Note that this routine is called *after* forwardMostMove
17130 is updated, so the last fractional tick must be subtracted
17131 from the color that is *not* on move now.
17134 SwitchClocks (int newMoveNr)
17136 long lastTickLength;
17138 int flagged = FALSE;
17142 if (StopClockTimer() && appData.clockMode) {
17143 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17144 if (!WhiteOnMove(forwardMostMove)) {
17145 if(blackNPS >= 0) lastTickLength = 0;
17146 blackTimeRemaining -= lastTickLength;
17147 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17148 // if(pvInfoList[forwardMostMove].time == -1)
17149 pvInfoList[forwardMostMove].time = // use GUI time
17150 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17152 if(whiteNPS >= 0) lastTickLength = 0;
17153 whiteTimeRemaining -= lastTickLength;
17154 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17155 // if(pvInfoList[forwardMostMove].time == -1)
17156 pvInfoList[forwardMostMove].time =
17157 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17159 flagged = CheckFlags();
17161 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17162 CheckTimeControl();
17164 if (flagged || !appData.clockMode) return;
17166 switch (gameMode) {
17167 case MachinePlaysBlack:
17168 case MachinePlaysWhite:
17169 case BeginningOfGame:
17170 if (pausing) return;
17174 case PlayFromGameFile:
17182 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17183 if(WhiteOnMove(forwardMostMove))
17184 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17185 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17189 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17190 whiteTimeRemaining : blackTimeRemaining);
17191 StartClockTimer(intendedTickLength);
17195 /* Stop both clocks */
17199 long lastTickLength;
17202 if (!StopClockTimer()) return;
17203 if (!appData.clockMode) return;
17207 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17208 if (WhiteOnMove(forwardMostMove)) {
17209 if(whiteNPS >= 0) lastTickLength = 0;
17210 whiteTimeRemaining -= lastTickLength;
17211 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17213 if(blackNPS >= 0) lastTickLength = 0;
17214 blackTimeRemaining -= lastTickLength;
17215 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17220 /* Start clock of player on move. Time may have been reset, so
17221 if clock is already running, stop and restart it. */
17225 (void) StopClockTimer(); /* in case it was running already */
17226 DisplayBothClocks();
17227 if (CheckFlags()) return;
17229 if (!appData.clockMode) return;
17230 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17232 GetTimeMark(&tickStartTM);
17233 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17234 whiteTimeRemaining : blackTimeRemaining);
17236 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17237 whiteNPS = blackNPS = -1;
17238 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17239 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17240 whiteNPS = first.nps;
17241 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17242 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17243 blackNPS = first.nps;
17244 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17245 whiteNPS = second.nps;
17246 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17247 blackNPS = second.nps;
17248 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17250 StartClockTimer(intendedTickLength);
17254 TimeString (long ms)
17256 long second, minute, hour, day;
17258 static char buf[32];
17260 if (ms > 0 && ms <= 9900) {
17261 /* convert milliseconds to tenths, rounding up */
17262 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17264 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17268 /* convert milliseconds to seconds, rounding up */
17269 /* use floating point to avoid strangeness of integer division
17270 with negative dividends on many machines */
17271 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17278 day = second / (60 * 60 * 24);
17279 second = second % (60 * 60 * 24);
17280 hour = second / (60 * 60);
17281 second = second % (60 * 60);
17282 minute = second / 60;
17283 second = second % 60;
17286 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17287 sign, day, hour, minute, second);
17289 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17291 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17298 * This is necessary because some C libraries aren't ANSI C compliant yet.
17301 StrStr (char *string, char *match)
17305 length = strlen(match);
17307 for (i = strlen(string) - length; i >= 0; i--, string++)
17308 if (!strncmp(match, string, length))
17315 StrCaseStr (char *string, char *match)
17319 length = strlen(match);
17321 for (i = strlen(string) - length; i >= 0; i--, string++) {
17322 for (j = 0; j < length; j++) {
17323 if (ToLower(match[j]) != ToLower(string[j]))
17326 if (j == length) return string;
17334 StrCaseCmp (char *s1, char *s2)
17339 c1 = ToLower(*s1++);
17340 c2 = ToLower(*s2++);
17341 if (c1 > c2) return 1;
17342 if (c1 < c2) return -1;
17343 if (c1 == NULLCHAR) return 0;
17351 return isupper(c) ? tolower(c) : c;
17358 return islower(c) ? toupper(c) : c;
17360 #endif /* !_amigados */
17367 if ((ret = (char *) malloc(strlen(s) + 1)))
17369 safeStrCpy(ret, s, strlen(s)+1);
17375 StrSavePtr (char *s, char **savePtr)
17380 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17381 safeStrCpy(*savePtr, s, strlen(s)+1);
17393 clock = time((time_t *)NULL);
17394 tm = localtime(&clock);
17395 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17396 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17397 return StrSave(buf);
17402 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17404 int i, j, fromX, fromY, toX, toY;
17411 whiteToPlay = (gameMode == EditPosition) ?
17412 !blackPlaysFirst : (move % 2 == 0);
17415 /* Piece placement data */
17416 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17417 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17419 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17420 if (boards[move][i][j] == EmptySquare) {
17422 } else { ChessSquare piece = boards[move][i][j];
17423 if (emptycount > 0) {
17424 if(emptycount<10) /* [HGM] can be >= 10 */
17425 *p++ = '0' + emptycount;
17426 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17429 if(PieceToChar(piece) == '+') {
17430 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17432 piece = (ChessSquare)(DEMOTED piece);
17434 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17436 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17437 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17442 if (emptycount > 0) {
17443 if(emptycount<10) /* [HGM] can be >= 10 */
17444 *p++ = '0' + emptycount;
17445 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17452 /* [HGM] print Crazyhouse or Shogi holdings */
17453 if( gameInfo.holdingsWidth ) {
17454 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17456 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17457 piece = boards[move][i][BOARD_WIDTH-1];
17458 if( piece != EmptySquare )
17459 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17460 *p++ = PieceToChar(piece);
17462 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17463 piece = boards[move][BOARD_HEIGHT-i-1][0];
17464 if( piece != EmptySquare )
17465 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17466 *p++ = PieceToChar(piece);
17469 if( q == p ) *p++ = '-';
17475 *p++ = whiteToPlay ? 'w' : 'b';
17478 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17479 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17481 if(nrCastlingRights) {
17483 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17484 /* [HGM] write directly from rights */
17485 if(boards[move][CASTLING][2] != NoRights &&
17486 boards[move][CASTLING][0] != NoRights )
17487 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17488 if(boards[move][CASTLING][2] != NoRights &&
17489 boards[move][CASTLING][1] != NoRights )
17490 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17491 if(boards[move][CASTLING][5] != NoRights &&
17492 boards[move][CASTLING][3] != NoRights )
17493 *p++ = boards[move][CASTLING][3] + AAA;
17494 if(boards[move][CASTLING][5] != NoRights &&
17495 boards[move][CASTLING][4] != NoRights )
17496 *p++ = boards[move][CASTLING][4] + AAA;
17499 /* [HGM] write true castling rights */
17500 if( nrCastlingRights == 6 ) {
17502 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17503 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17504 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17505 boards[move][CASTLING][2] != NoRights );
17506 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17507 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17508 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17509 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17510 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17514 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17515 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17516 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17517 boards[move][CASTLING][5] != NoRights );
17518 if(gameInfo.variant == VariantSChess) {
17519 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17520 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17521 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17522 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17527 if (q == p) *p++ = '-'; /* No castling rights */
17531 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17532 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17533 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17534 /* En passant target square */
17535 if (move > backwardMostMove) {
17536 fromX = moveList[move - 1][0] - AAA;
17537 fromY = moveList[move - 1][1] - ONE;
17538 toX = moveList[move - 1][2] - AAA;
17539 toY = moveList[move - 1][3] - ONE;
17540 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17541 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17542 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17544 /* 2-square pawn move just happened */
17546 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17550 } else if(move == backwardMostMove) {
17551 // [HGM] perhaps we should always do it like this, and forget the above?
17552 if((signed char)boards[move][EP_STATUS] >= 0) {
17553 *p++ = boards[move][EP_STATUS] + AAA;
17554 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17566 { int i = 0, j=move;
17568 /* [HGM] find reversible plies */
17569 if (appData.debugMode) { int k;
17570 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17571 for(k=backwardMostMove; k<=forwardMostMove; k++)
17572 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17576 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17577 if( j == backwardMostMove ) i += initialRulePlies;
17578 sprintf(p, "%d ", i);
17579 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17581 /* Fullmove number */
17582 sprintf(p, "%d", (move / 2) + 1);
17583 } else *--p = NULLCHAR;
17585 return StrSave(buf);
17589 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17593 int emptycount, virgin[BOARD_FILES];
17598 /* Piece placement data */
17599 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17602 if (*p == '/' || *p == ' ' || *p == '[' ) {
17604 emptycount = gameInfo.boardWidth - j;
17605 while (emptycount--)
17606 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17607 if (*p == '/') p++;
17608 else if(autoSize) { // we stumbled unexpectedly into end of board
17609 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17610 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17612 appData.NrRanks = gameInfo.boardHeight - i; i=0;
17615 #if(BOARD_FILES >= 10)
17616 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17617 p++; emptycount=10;
17618 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17619 while (emptycount--)
17620 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17622 } else if (*p == '*') {
17623 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17624 } else if (isdigit(*p)) {
17625 emptycount = *p++ - '0';
17626 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17627 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17628 while (emptycount--)
17629 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17630 } else if (*p == '+' || isalpha(*p)) {
17631 if (j >= gameInfo.boardWidth) return FALSE;
17633 piece = CharToPiece(*++p);
17634 if(piece == EmptySquare) return FALSE; /* unknown piece */
17635 piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17636 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17637 } else piece = CharToPiece(*p++);
17639 if(piece==EmptySquare) return FALSE; /* unknown piece */
17640 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17641 piece = (ChessSquare) (PROMOTED piece);
17642 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17645 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17651 while (*p == '/' || *p == ' ') p++;
17653 if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17655 /* [HGM] by default clear Crazyhouse holdings, if present */
17656 if(gameInfo.holdingsWidth) {
17657 for(i=0; i<BOARD_HEIGHT; i++) {
17658 board[i][0] = EmptySquare; /* black holdings */
17659 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17660 board[i][1] = (ChessSquare) 0; /* black counts */
17661 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17665 /* [HGM] look for Crazyhouse holdings here */
17666 while(*p==' ') p++;
17667 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17669 if(*p == '-' ) p++; /* empty holdings */ else {
17670 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17671 /* if we would allow FEN reading to set board size, we would */
17672 /* have to add holdings and shift the board read so far here */
17673 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17675 if((int) piece >= (int) BlackPawn ) {
17676 i = (int)piece - (int)BlackPawn;
17677 i = PieceToNumber((ChessSquare)i);
17678 if( i >= gameInfo.holdingsSize ) return FALSE;
17679 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17680 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17682 i = (int)piece - (int)WhitePawn;
17683 i = PieceToNumber((ChessSquare)i);
17684 if( i >= gameInfo.holdingsSize ) return FALSE;
17685 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17686 board[i][BOARD_WIDTH-2]++; /* black holdings */
17693 while(*p == ' ') p++;
17697 if(appData.colorNickNames) {
17698 if( c == appData.colorNickNames[0] ) c = 'w'; else
17699 if( c == appData.colorNickNames[1] ) c = 'b';
17703 *blackPlaysFirst = FALSE;
17706 *blackPlaysFirst = TRUE;
17712 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17713 /* return the extra info in global variiables */
17715 /* set defaults in case FEN is incomplete */
17716 board[EP_STATUS] = EP_UNKNOWN;
17717 for(i=0; i<nrCastlingRights; i++ ) {
17718 board[CASTLING][i] =
17719 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17720 } /* assume possible unless obviously impossible */
17721 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17722 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17723 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17724 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17725 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17726 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17727 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17728 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17731 while(*p==' ') p++;
17732 if(nrCastlingRights) {
17733 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17734 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17735 /* castling indicator present, so default becomes no castlings */
17736 for(i=0; i<nrCastlingRights; i++ ) {
17737 board[CASTLING][i] = NoRights;
17740 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17741 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17742 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17743 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17744 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17746 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17747 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17748 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17750 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17751 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17752 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17753 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17754 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17755 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17758 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17759 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17760 board[CASTLING][2] = whiteKingFile;
17761 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17762 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17765 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17766 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17767 board[CASTLING][2] = whiteKingFile;
17768 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17769 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17772 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17773 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17774 board[CASTLING][5] = blackKingFile;
17775 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17776 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17779 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17780 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17781 board[CASTLING][5] = blackKingFile;
17782 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17783 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17786 default: /* FRC castlings */
17787 if(c >= 'a') { /* black rights */
17788 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17789 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17790 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17791 if(i == BOARD_RGHT) break;
17792 board[CASTLING][5] = i;
17794 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17795 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17797 board[CASTLING][3] = c;
17799 board[CASTLING][4] = c;
17800 } else { /* white rights */
17801 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17802 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17803 if(board[0][i] == WhiteKing) break;
17804 if(i == BOARD_RGHT) break;
17805 board[CASTLING][2] = i;
17806 c -= AAA - 'a' + 'A';
17807 if(board[0][c] >= WhiteKing) break;
17809 board[CASTLING][0] = c;
17811 board[CASTLING][1] = c;
17815 for(i=0; i<nrCastlingRights; i++)
17816 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17817 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17818 if (appData.debugMode) {
17819 fprintf(debugFP, "FEN castling rights:");
17820 for(i=0; i<nrCastlingRights; i++)
17821 fprintf(debugFP, " %d", board[CASTLING][i]);
17822 fprintf(debugFP, "\n");
17825 while(*p==' ') p++;
17828 /* read e.p. field in games that know e.p. capture */
17829 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17830 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17831 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17833 p++; board[EP_STATUS] = EP_NONE;
17835 char c = *p++ - AAA;
17837 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17838 if(*p >= '0' && *p <='9') p++;
17839 board[EP_STATUS] = c;
17844 if(sscanf(p, "%d", &i) == 1) {
17845 FENrulePlies = i; /* 50-move ply counter */
17846 /* (The move number is still ignored) */
17853 EditPositionPasteFEN (char *fen)
17856 Board initial_position;
17858 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17859 DisplayError(_("Bad FEN position in clipboard"), 0);
17862 int savedBlackPlaysFirst = blackPlaysFirst;
17863 EditPositionEvent();
17864 blackPlaysFirst = savedBlackPlaysFirst;
17865 CopyBoard(boards[0], initial_position);
17866 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17867 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17868 DisplayBothClocks();
17869 DrawPosition(FALSE, boards[currentMove]);
17874 static char cseq[12] = "\\ ";
17877 set_cont_sequence (char *new_seq)
17882 // handle bad attempts to set the sequence
17884 return 0; // acceptable error - no debug
17886 len = strlen(new_seq);
17887 ret = (len > 0) && (len < sizeof(cseq));
17889 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17890 else if (appData.debugMode)
17891 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17896 reformat a source message so words don't cross the width boundary. internal
17897 newlines are not removed. returns the wrapped size (no null character unless
17898 included in source message). If dest is NULL, only calculate the size required
17899 for the dest buffer. lp argument indicats line position upon entry, and it's
17900 passed back upon exit.
17903 wrap (char *dest, char *src, int count, int width, int *lp)
17905 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17907 cseq_len = strlen(cseq);
17908 old_line = line = *lp;
17909 ansi = len = clen = 0;
17911 for (i=0; i < count; i++)
17913 if (src[i] == '\033')
17916 // if we hit the width, back up
17917 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17919 // store i & len in case the word is too long
17920 old_i = i, old_len = len;
17922 // find the end of the last word
17923 while (i && src[i] != ' ' && src[i] != '\n')
17929 // word too long? restore i & len before splitting it
17930 if ((old_i-i+clen) >= width)
17937 if (i && src[i-1] == ' ')
17940 if (src[i] != ' ' && src[i] != '\n')
17947 // now append the newline and continuation sequence
17952 strncpy(dest+len, cseq, cseq_len);
17960 dest[len] = src[i];
17964 if (src[i] == '\n')
17969 if (dest && appData.debugMode)
17971 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17972 count, width, line, len, *lp);
17973 show_bytes(debugFP, src, count);
17974 fprintf(debugFP, "\ndest: ");
17975 show_bytes(debugFP, dest, len);
17976 fprintf(debugFP, "\n");
17978 *lp = dest ? line : old_line;
17983 // [HGM] vari: routines for shelving variations
17984 Boolean modeRestore = FALSE;
17987 PushInner (int firstMove, int lastMove)
17989 int i, j, nrMoves = lastMove - firstMove;
17991 // push current tail of game on stack
17992 savedResult[storedGames] = gameInfo.result;
17993 savedDetails[storedGames] = gameInfo.resultDetails;
17994 gameInfo.resultDetails = NULL;
17995 savedFirst[storedGames] = firstMove;
17996 savedLast [storedGames] = lastMove;
17997 savedFramePtr[storedGames] = framePtr;
17998 framePtr -= nrMoves; // reserve space for the boards
17999 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18000 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18001 for(j=0; j<MOVE_LEN; j++)
18002 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18003 for(j=0; j<2*MOVE_LEN; j++)
18004 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18005 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18006 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18007 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18008 pvInfoList[firstMove+i-1].depth = 0;
18009 commentList[framePtr+i] = commentList[firstMove+i];
18010 commentList[firstMove+i] = NULL;
18014 forwardMostMove = firstMove; // truncate game so we can start variation
18018 PushTail (int firstMove, int lastMove)
18020 if(appData.icsActive) { // only in local mode
18021 forwardMostMove = currentMove; // mimic old ICS behavior
18024 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18026 PushInner(firstMove, lastMove);
18027 if(storedGames == 1) GreyRevert(FALSE);
18028 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18032 PopInner (Boolean annotate)
18035 char buf[8000], moveBuf[20];
18037 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18038 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18039 nrMoves = savedLast[storedGames] - currentMove;
18042 if(!WhiteOnMove(currentMove))
18043 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18044 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18045 for(i=currentMove; i<forwardMostMove; i++) {
18047 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18048 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18049 strcat(buf, moveBuf);
18050 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18051 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18055 for(i=1; i<=nrMoves; i++) { // copy last variation back
18056 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18057 for(j=0; j<MOVE_LEN; j++)
18058 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18059 for(j=0; j<2*MOVE_LEN; j++)
18060 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18061 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18062 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18063 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18064 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18065 commentList[currentMove+i] = commentList[framePtr+i];
18066 commentList[framePtr+i] = NULL;
18068 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18069 framePtr = savedFramePtr[storedGames];
18070 gameInfo.result = savedResult[storedGames];
18071 if(gameInfo.resultDetails != NULL) {
18072 free(gameInfo.resultDetails);
18074 gameInfo.resultDetails = savedDetails[storedGames];
18075 forwardMostMove = currentMove + nrMoves;
18079 PopTail (Boolean annotate)
18081 if(appData.icsActive) return FALSE; // only in local mode
18082 if(!storedGames) return FALSE; // sanity
18083 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18085 PopInner(annotate);
18086 if(currentMove < forwardMostMove) ForwardEvent(); else
18087 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18089 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18095 { // remove all shelved variations
18097 for(i=0; i<storedGames; i++) {
18098 if(savedDetails[i])
18099 free(savedDetails[i]);
18100 savedDetails[i] = NULL;
18102 for(i=framePtr; i<MAX_MOVES; i++) {
18103 if(commentList[i]) free(commentList[i]);
18104 commentList[i] = NULL;
18106 framePtr = MAX_MOVES-1;
18111 LoadVariation (int index, char *text)
18112 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18113 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18114 int level = 0, move;
18116 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18117 // first find outermost bracketing variation
18118 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18119 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18120 if(*p == '{') wait = '}'; else
18121 if(*p == '[') wait = ']'; else
18122 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18123 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18125 if(*p == wait) wait = NULLCHAR; // closing ]} found
18128 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18129 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18130 end[1] = NULLCHAR; // clip off comment beyond variation
18131 ToNrEvent(currentMove-1);
18132 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18133 // kludge: use ParsePV() to append variation to game
18134 move = currentMove;
18135 ParsePV(start, TRUE, TRUE);
18136 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18137 ClearPremoveHighlights();
18139 ToNrEvent(currentMove+1);
18145 char *p, *q, buf[MSG_SIZ];
18146 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18147 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18148 ParseArgsFromString(buf);
18149 ActivateTheme(TRUE); // also redo colors
18153 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18156 q = appData.themeNames;
18157 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18158 if(appData.useBitmaps) {
18159 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18160 appData.liteBackTextureFile, appData.darkBackTextureFile,
18161 appData.liteBackTextureMode,
18162 appData.darkBackTextureMode );
18164 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18165 Col2Text(2), // lightSquareColor
18166 Col2Text(3) ); // darkSquareColor
18168 if(appData.useBorder) {
18169 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18172 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18174 if(appData.useFont) {
18175 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18176 appData.renderPiecesWithFont,
18177 appData.fontToPieceTable,
18178 Col2Text(9), // appData.fontBackColorWhite
18179 Col2Text(10) ); // appData.fontForeColorBlack
18181 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18182 appData.pieceDirectory);
18183 if(!appData.pieceDirectory[0])
18184 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18185 Col2Text(0), // whitePieceColor
18186 Col2Text(1) ); // blackPieceColor
18188 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18189 Col2Text(4), // highlightSquareColor
18190 Col2Text(5) ); // premoveHighlightColor
18191 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18192 if(insert != q) insert[-1] = NULLCHAR;
18193 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18196 ActivateTheme(FALSE);