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 }
621 ChessSquare ChuChessArray[2][BOARD_FILES] = {
622 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
623 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
624 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
625 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
629 ChessSquare GothicArray[2][BOARD_FILES] = {
630 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
631 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
632 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
633 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
636 #define GothicArray CapablancaArray
640 ChessSquare FalconArray[2][BOARD_FILES] = {
641 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
642 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
643 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
644 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
647 #define FalconArray CapablancaArray
650 #else // !(BOARD_FILES>=10)
651 #define XiangqiPosition FIDEArray
652 #define CapablancaArray FIDEArray
653 #define GothicArray FIDEArray
654 #define GreatArray FIDEArray
655 #endif // !(BOARD_FILES>=10)
657 #if (BOARD_FILES>=12)
658 ChessSquare CourierArray[2][BOARD_FILES] = {
659 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
660 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
661 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
662 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
664 ChessSquare ChuArray[6][BOARD_FILES] = {
665 { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
666 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
667 { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
668 BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
669 { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
670 WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
671 { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
672 BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
673 { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
674 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
675 { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
676 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
678 #else // !(BOARD_FILES>=12)
679 #define CourierArray CapablancaArray
680 #define ChuArray CapablancaArray
681 #endif // !(BOARD_FILES>=12)
684 Board initialPosition;
687 /* Convert str to a rating. Checks for special cases of "----",
689 "++++", etc. Also strips ()'s */
691 string_to_rating (char *str)
693 while(*str && !isdigit(*str)) ++str;
695 return 0; /* One of the special "no rating" cases */
703 /* Init programStats */
704 programStats.movelist[0] = 0;
705 programStats.depth = 0;
706 programStats.nr_moves = 0;
707 programStats.moves_left = 0;
708 programStats.nodes = 0;
709 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
710 programStats.score = 0;
711 programStats.got_only_move = 0;
712 programStats.got_fail = 0;
713 programStats.line_is_book = 0;
718 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
719 if (appData.firstPlaysBlack) {
720 first.twoMachinesColor = "black\n";
721 second.twoMachinesColor = "white\n";
723 first.twoMachinesColor = "white\n";
724 second.twoMachinesColor = "black\n";
727 first.other = &second;
728 second.other = &first;
731 if(appData.timeOddsMode) {
732 norm = appData.timeOdds[0];
733 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
735 first.timeOdds = appData.timeOdds[0]/norm;
736 second.timeOdds = appData.timeOdds[1]/norm;
739 if(programVersion) free(programVersion);
740 if (appData.noChessProgram) {
741 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
742 sprintf(programVersion, "%s", PACKAGE_STRING);
744 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
745 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
746 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
751 UnloadEngine (ChessProgramState *cps)
753 /* Kill off first chess program */
754 if (cps->isr != NULL)
755 RemoveInputSource(cps->isr);
758 if (cps->pr != NoProc) {
760 DoSleep( appData.delayBeforeQuit );
761 SendToProgram("quit\n", cps);
762 DoSleep( appData.delayAfterQuit );
763 DestroyChildProcess(cps->pr, cps->useSigterm);
766 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
770 ClearOptions (ChessProgramState *cps)
773 cps->nrOptions = cps->comboCnt = 0;
774 for(i=0; i<MAX_OPTIONS; i++) {
775 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
776 cps->option[i].textValue = 0;
780 char *engineNames[] = {
781 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
782 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
784 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
785 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
790 InitEngine (ChessProgramState *cps, int n)
791 { // [HGM] all engine initialiation put in a function that does one engine
795 cps->which = engineNames[n];
796 cps->maybeThinking = FALSE;
800 cps->sendDrawOffers = 1;
802 cps->program = appData.chessProgram[n];
803 cps->host = appData.host[n];
804 cps->dir = appData.directory[n];
805 cps->initString = appData.engInitString[n];
806 cps->computerString = appData.computerString[n];
807 cps->useSigint = TRUE;
808 cps->useSigterm = TRUE;
809 cps->reuse = appData.reuse[n];
810 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
811 cps->useSetboard = FALSE;
813 cps->usePing = FALSE;
816 cps->usePlayother = FALSE;
817 cps->useColors = TRUE;
818 cps->useUsermove = FALSE;
819 cps->sendICS = FALSE;
820 cps->sendName = appData.icsActive;
821 cps->sdKludge = FALSE;
822 cps->stKludge = FALSE;
823 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
824 TidyProgramName(cps->program, cps->host, cps->tidy);
826 ASSIGN(cps->variants, appData.variant);
827 cps->analysisSupport = 2; /* detect */
828 cps->analyzing = FALSE;
829 cps->initDone = FALSE;
832 /* New features added by Tord: */
833 cps->useFEN960 = FALSE;
834 cps->useOOCastle = TRUE;
835 /* End of new features added by Tord. */
836 cps->fenOverride = appData.fenOverride[n];
838 /* [HGM] time odds: set factor for each machine */
839 cps->timeOdds = appData.timeOdds[n];
841 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
842 cps->accumulateTC = appData.accumulateTC[n];
843 cps->maxNrOfSessions = 1;
848 cps->supportsNPS = UNKNOWN;
849 cps->memSize = FALSE;
850 cps->maxCores = FALSE;
851 ASSIGN(cps->egtFormats, "");
854 cps->optionSettings = appData.engOptions[n];
856 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
857 cps->isUCI = appData.isUCI[n]; /* [AS] */
858 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
861 if (appData.protocolVersion[n] > PROTOVER
862 || appData.protocolVersion[n] < 1)
867 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
868 appData.protocolVersion[n]);
869 if( (len >= MSG_SIZ) && appData.debugMode )
870 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
872 DisplayFatalError(buf, 0, 2);
876 cps->protocolVersion = appData.protocolVersion[n];
879 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
880 ParseFeatures(appData.featureDefaults, cps);
883 ChessProgramState *savCps;
891 if(WaitForEngine(savCps, LoadEngine)) return;
892 CommonEngineInit(); // recalculate time odds
893 if(gameInfo.variant != StringToVariant(appData.variant)) {
894 // we changed variant when loading the engine; this forces us to reset
895 Reset(TRUE, savCps != &first);
896 oldMode = BeginningOfGame; // to prevent restoring old mode
898 InitChessProgram(savCps, FALSE);
899 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
900 DisplayMessage("", "");
901 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
902 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
905 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
909 ReplaceEngine (ChessProgramState *cps, int n)
911 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
913 if(oldMode != BeginningOfGame) EditGameEvent();
916 appData.noChessProgram = FALSE;
917 appData.clockMode = TRUE;
920 if(n) return; // only startup first engine immediately; second can wait
921 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
925 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
926 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
928 static char resetOptions[] =
929 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
930 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
931 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
932 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
935 FloatToFront(char **list, char *engineLine)
937 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
939 if(appData.recentEngines <= 0) return;
940 TidyProgramName(engineLine, "localhost", tidy+1);
941 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
942 strncpy(buf+1, *list, MSG_SIZ-50);
943 if(p = strstr(buf, tidy)) { // tidy name appears in list
944 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
945 while(*p++ = *++q); // squeeze out
947 strcat(tidy, buf+1); // put list behind tidy name
948 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
949 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
950 ASSIGN(*list, tidy+1);
953 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
956 Load (ChessProgramState *cps, int i)
958 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
959 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
960 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
961 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
962 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
963 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
964 appData.firstProtocolVersion = PROTOVER;
965 ParseArgsFromString(buf);
967 ReplaceEngine(cps, i);
968 FloatToFront(&appData.recentEngineList, engineLine);
972 while(q = strchr(p, SLASH)) p = q+1;
973 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
974 if(engineDir[0] != NULLCHAR) {
975 ASSIGN(appData.directory[i], engineDir); p = engineName;
976 } else if(p != engineName) { // derive directory from engine path, when not given
978 ASSIGN(appData.directory[i], engineName);
980 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
981 } else { ASSIGN(appData.directory[i], "."); }
982 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
984 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
985 snprintf(command, MSG_SIZ, "%s %s", p, params);
988 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
989 ASSIGN(appData.chessProgram[i], p);
990 appData.isUCI[i] = isUCI;
991 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
992 appData.hasOwnBookUCI[i] = hasBook;
993 if(!nickName[0]) useNick = FALSE;
994 if(useNick) ASSIGN(appData.pgnName[i], nickName);
998 q = firstChessProgramNames;
999 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1000 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1001 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1002 quote, p, quote, appData.directory[i],
1003 useNick ? " -fn \"" : "",
1004 useNick ? nickName : "",
1005 useNick ? "\"" : "",
1006 v1 ? " -firstProtocolVersion 1" : "",
1007 hasBook ? "" : " -fNoOwnBookUCI",
1008 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1009 storeVariant ? " -variant " : "",
1010 storeVariant ? VariantName(gameInfo.variant) : "");
1011 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1012 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1013 if(insert != q) insert[-1] = NULLCHAR;
1014 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1016 FloatToFront(&appData.recentEngineList, buf);
1018 ReplaceEngine(cps, i);
1024 int matched, min, sec;
1026 * Parse timeControl resource
1028 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1029 appData.movesPerSession)) {
1031 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1032 DisplayFatalError(buf, 0, 2);
1036 * Parse searchTime resource
1038 if (*appData.searchTime != NULLCHAR) {
1039 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1041 searchTime = min * 60;
1042 } else if (matched == 2) {
1043 searchTime = min * 60 + sec;
1046 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1047 DisplayFatalError(buf, 0, 2);
1056 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1057 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1059 GetTimeMark(&programStartTime);
1060 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1061 appData.seedBase = random() + (random()<<15);
1062 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1064 ClearProgramStats();
1065 programStats.ok_to_send = 1;
1066 programStats.seen_stat = 0;
1069 * Initialize game list
1075 * Internet chess server status
1077 if (appData.icsActive) {
1078 appData.matchMode = FALSE;
1079 appData.matchGames = 0;
1081 appData.noChessProgram = !appData.zippyPlay;
1083 appData.zippyPlay = FALSE;
1084 appData.zippyTalk = FALSE;
1085 appData.noChessProgram = TRUE;
1087 if (*appData.icsHelper != NULLCHAR) {
1088 appData.useTelnet = TRUE;
1089 appData.telnetProgram = appData.icsHelper;
1092 appData.zippyTalk = appData.zippyPlay = FALSE;
1095 /* [AS] Initialize pv info list [HGM] and game state */
1099 for( i=0; i<=framePtr; i++ ) {
1100 pvInfoList[i].depth = -1;
1101 boards[i][EP_STATUS] = EP_NONE;
1102 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1108 /* [AS] Adjudication threshold */
1109 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1111 InitEngine(&first, 0);
1112 InitEngine(&second, 1);
1115 pairing.which = "pairing"; // pairing engine
1116 pairing.pr = NoProc;
1118 pairing.program = appData.pairingEngine;
1119 pairing.host = "localhost";
1122 if (appData.icsActive) {
1123 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1124 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1125 appData.clockMode = FALSE;
1126 first.sendTime = second.sendTime = 0;
1130 /* Override some settings from environment variables, for backward
1131 compatibility. Unfortunately it's not feasible to have the env
1132 vars just set defaults, at least in xboard. Ugh.
1134 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1139 if (!appData.icsActive) {
1143 /* Check for variants that are supported only in ICS mode,
1144 or not at all. Some that are accepted here nevertheless
1145 have bugs; see comments below.
1147 VariantClass variant = StringToVariant(appData.variant);
1149 case VariantBughouse: /* need four players and two boards */
1150 case VariantKriegspiel: /* need to hide pieces and move details */
1151 /* case VariantFischeRandom: (Fabien: moved below) */
1152 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1153 if( (len >= MSG_SIZ) && appData.debugMode )
1154 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1156 DisplayFatalError(buf, 0, 2);
1159 case VariantUnknown:
1160 case VariantLoadable:
1170 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1171 if( (len >= MSG_SIZ) && appData.debugMode )
1172 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1174 DisplayFatalError(buf, 0, 2);
1177 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1178 case VariantFairy: /* [HGM] TestLegality definitely off! */
1179 case VariantGothic: /* [HGM] should work */
1180 case VariantCapablanca: /* [HGM] should work */
1181 case VariantCourier: /* [HGM] initial forced moves not implemented */
1182 case VariantShogi: /* [HGM] could still mate with pawn drop */
1183 case VariantChu: /* [HGM] experimental */
1184 case VariantKnightmate: /* [HGM] should work */
1185 case VariantCylinder: /* [HGM] untested */
1186 case VariantFalcon: /* [HGM] untested */
1187 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1188 offboard interposition not understood */
1189 case VariantNormal: /* definitely works! */
1190 case VariantWildCastle: /* pieces not automatically shuffled */
1191 case VariantNoCastle: /* pieces not automatically shuffled */
1192 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1193 case VariantLosers: /* should work except for win condition,
1194 and doesn't know captures are mandatory */
1195 case VariantSuicide: /* should work except for win condition,
1196 and doesn't know captures are mandatory */
1197 case VariantGiveaway: /* should work except for win condition,
1198 and doesn't know captures are mandatory */
1199 case VariantTwoKings: /* should work */
1200 case VariantAtomic: /* should work except for win condition */
1201 case Variant3Check: /* should work except for win condition */
1202 case VariantShatranj: /* should work except for all win conditions */
1203 case VariantMakruk: /* should work except for draw countdown */
1204 case VariantASEAN : /* should work except for draw countdown */
1205 case VariantBerolina: /* might work if TestLegality is off */
1206 case VariantCapaRandom: /* should work */
1207 case VariantJanus: /* should work */
1208 case VariantSuper: /* experimental */
1209 case VariantGreat: /* experimental, requires legality testing to be off */
1210 case VariantSChess: /* S-Chess, should work */
1211 case VariantGrand: /* should work */
1212 case VariantSpartan: /* should work */
1213 case VariantLion: /* should work */
1214 case VariantChuChess: /* should work */
1222 NextIntegerFromString (char ** str, long * value)
1227 while( *s == ' ' || *s == '\t' ) {
1233 if( *s >= '0' && *s <= '9' ) {
1234 while( *s >= '0' && *s <= '9' ) {
1235 *value = *value * 10 + (*s - '0');
1248 NextTimeControlFromString (char ** str, long * value)
1251 int result = NextIntegerFromString( str, &temp );
1254 *value = temp * 60; /* Minutes */
1255 if( **str == ':' ) {
1257 result = NextIntegerFromString( str, &temp );
1258 *value += temp; /* Seconds */
1266 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1267 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1268 int result = -1, type = 0; long temp, temp2;
1270 if(**str != ':') return -1; // old params remain in force!
1272 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1273 if( NextIntegerFromString( str, &temp ) ) return -1;
1274 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1277 /* time only: incremental or sudden-death time control */
1278 if(**str == '+') { /* increment follows; read it */
1280 if(**str == '!') type = *(*str)++; // Bronstein TC
1281 if(result = NextIntegerFromString( str, &temp2)) return -1;
1282 *inc = temp2 * 1000;
1283 if(**str == '.') { // read fraction of increment
1284 char *start = ++(*str);
1285 if(result = NextIntegerFromString( str, &temp2)) return -1;
1287 while(start++ < *str) temp2 /= 10;
1291 *moves = 0; *tc = temp * 1000; *incType = type;
1295 (*str)++; /* classical time control */
1296 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1308 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1309 { /* [HGM] get time to add from the multi-session time-control string */
1310 int incType, moves=1; /* kludge to force reading of first session */
1311 long time, increment;
1314 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1316 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1317 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1318 if(movenr == -1) return time; /* last move before new session */
1319 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1320 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1321 if(!moves) return increment; /* current session is incremental */
1322 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1323 } while(movenr >= -1); /* try again for next session */
1325 return 0; // no new time quota on this move
1329 ParseTimeControl (char *tc, float ti, int mps)
1333 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1336 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1337 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1338 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1342 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1344 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1347 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1349 snprintf(buf, MSG_SIZ, ":%s", mytc);
1351 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1353 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1358 /* Parse second time control */
1361 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1369 timeControl_2 = tc2 * 1000;
1379 timeControl = tc1 * 1000;
1382 timeIncrement = ti * 1000; /* convert to ms */
1383 movesPerSession = 0;
1386 movesPerSession = mps;
1394 if (appData.debugMode) {
1395 # ifdef __GIT_VERSION
1396 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1398 fprintf(debugFP, "Version: %s\n", programVersion);
1401 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1403 set_cont_sequence(appData.wrapContSeq);
1404 if (appData.matchGames > 0) {
1405 appData.matchMode = TRUE;
1406 } else if (appData.matchMode) {
1407 appData.matchGames = 1;
1409 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1410 appData.matchGames = appData.sameColorGames;
1411 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1412 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1413 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1416 if (appData.noChessProgram || first.protocolVersion == 1) {
1419 /* kludge: allow timeout for initial "feature" commands */
1421 DisplayMessage("", _("Starting chess program"));
1422 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1427 CalculateIndex (int index, int gameNr)
1428 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1430 if(index > 0) return index; // fixed nmber
1431 if(index == 0) return 1;
1432 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1433 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1438 LoadGameOrPosition (int gameNr)
1439 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1440 if (*appData.loadGameFile != NULLCHAR) {
1441 if (!LoadGameFromFile(appData.loadGameFile,
1442 CalculateIndex(appData.loadGameIndex, gameNr),
1443 appData.loadGameFile, FALSE)) {
1444 DisplayFatalError(_("Bad game file"), 0, 1);
1447 } else if (*appData.loadPositionFile != NULLCHAR) {
1448 if (!LoadPositionFromFile(appData.loadPositionFile,
1449 CalculateIndex(appData.loadPositionIndex, gameNr),
1450 appData.loadPositionFile)) {
1451 DisplayFatalError(_("Bad position file"), 0, 1);
1459 ReserveGame (int gameNr, char resChar)
1461 FILE *tf = fopen(appData.tourneyFile, "r+");
1462 char *p, *q, c, buf[MSG_SIZ];
1463 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1464 safeStrCpy(buf, lastMsg, MSG_SIZ);
1465 DisplayMessage(_("Pick new game"), "");
1466 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1467 ParseArgsFromFile(tf);
1468 p = q = appData.results;
1469 if(appData.debugMode) {
1470 char *r = appData.participants;
1471 fprintf(debugFP, "results = '%s'\n", p);
1472 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1473 fprintf(debugFP, "\n");
1475 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1477 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1478 safeStrCpy(q, p, strlen(p) + 2);
1479 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1480 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1481 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1482 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1485 fseek(tf, -(strlen(p)+4), SEEK_END);
1487 if(c != '"') // depending on DOS or Unix line endings we can be one off
1488 fseek(tf, -(strlen(p)+2), SEEK_END);
1489 else fseek(tf, -(strlen(p)+3), SEEK_END);
1490 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1491 DisplayMessage(buf, "");
1492 free(p); appData.results = q;
1493 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1494 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1495 int round = appData.defaultMatchGames * appData.tourneyType;
1496 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1497 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1498 UnloadEngine(&first); // next game belongs to other pairing;
1499 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1501 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1505 MatchEvent (int mode)
1506 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1508 if(matchMode) { // already in match mode: switch it off
1510 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1513 // if(gameMode != BeginningOfGame) {
1514 // DisplayError(_("You can only start a match from the initial position."), 0);
1518 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1519 /* Set up machine vs. machine match */
1521 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1522 if(appData.tourneyFile[0]) {
1524 if(nextGame > appData.matchGames) {
1526 if(strchr(appData.results, '*') == NULL) {
1528 appData.tourneyCycles++;
1529 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1531 NextTourneyGame(-1, &dummy);
1533 if(nextGame <= appData.matchGames) {
1534 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1536 ScheduleDelayedEvent(NextMatchGame, 10000);
1541 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1542 DisplayError(buf, 0);
1543 appData.tourneyFile[0] = 0;
1547 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1548 DisplayFatalError(_("Can't have a match with no chess programs"),
1553 matchGame = roundNr = 1;
1554 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1558 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1561 InitBackEnd3 P((void))
1563 GameMode initialMode;
1567 InitChessProgram(&first, startedFromSetupPosition);
1569 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1570 free(programVersion);
1571 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1572 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1573 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1576 if (appData.icsActive) {
1578 /* [DM] Make a console window if needed [HGM] merged ifs */
1584 if (*appData.icsCommPort != NULLCHAR)
1585 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1586 appData.icsCommPort);
1588 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1589 appData.icsHost, appData.icsPort);
1591 if( (len >= MSG_SIZ) && appData.debugMode )
1592 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1594 DisplayFatalError(buf, err, 1);
1599 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1601 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1602 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1603 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1604 } else if (appData.noChessProgram) {
1610 if (*appData.cmailGameName != NULLCHAR) {
1612 OpenLoopback(&cmailPR);
1614 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1618 DisplayMessage("", "");
1619 if (StrCaseCmp(appData.initialMode, "") == 0) {
1620 initialMode = BeginningOfGame;
1621 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1622 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1623 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1624 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1627 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1628 initialMode = TwoMachinesPlay;
1629 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1630 initialMode = AnalyzeFile;
1631 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1632 initialMode = AnalyzeMode;
1633 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1634 initialMode = MachinePlaysWhite;
1635 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1636 initialMode = MachinePlaysBlack;
1637 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1638 initialMode = EditGame;
1639 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1640 initialMode = EditPosition;
1641 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1642 initialMode = Training;
1644 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1645 if( (len >= MSG_SIZ) && appData.debugMode )
1646 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1648 DisplayFatalError(buf, 0, 2);
1652 if (appData.matchMode) {
1653 if(appData.tourneyFile[0]) { // start tourney from command line
1655 if(f = fopen(appData.tourneyFile, "r")) {
1656 ParseArgsFromFile(f); // make sure tourney parmeters re known
1658 appData.clockMode = TRUE;
1660 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1663 } else if (*appData.cmailGameName != NULLCHAR) {
1664 /* Set up cmail mode */
1665 ReloadCmailMsgEvent(TRUE);
1667 /* Set up other modes */
1668 if (initialMode == AnalyzeFile) {
1669 if (*appData.loadGameFile == NULLCHAR) {
1670 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1674 if (*appData.loadGameFile != NULLCHAR) {
1675 (void) LoadGameFromFile(appData.loadGameFile,
1676 appData.loadGameIndex,
1677 appData.loadGameFile, TRUE);
1678 } else if (*appData.loadPositionFile != NULLCHAR) {
1679 (void) LoadPositionFromFile(appData.loadPositionFile,
1680 appData.loadPositionIndex,
1681 appData.loadPositionFile);
1682 /* [HGM] try to make self-starting even after FEN load */
1683 /* to allow automatic setup of fairy variants with wtm */
1684 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1685 gameMode = BeginningOfGame;
1686 setboardSpoiledMachineBlack = 1;
1688 /* [HGM] loadPos: make that every new game uses the setup */
1689 /* from file as long as we do not switch variant */
1690 if(!blackPlaysFirst) {
1691 startedFromPositionFile = TRUE;
1692 CopyBoard(filePosition, boards[0]);
1695 if (initialMode == AnalyzeMode) {
1696 if (appData.noChessProgram) {
1697 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1700 if (appData.icsActive) {
1701 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1705 } else if (initialMode == AnalyzeFile) {
1706 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1707 ShowThinkingEvent();
1709 AnalysisPeriodicEvent(1);
1710 } else if (initialMode == MachinePlaysWhite) {
1711 if (appData.noChessProgram) {
1712 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1716 if (appData.icsActive) {
1717 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1721 MachineWhiteEvent();
1722 } else if (initialMode == MachinePlaysBlack) {
1723 if (appData.noChessProgram) {
1724 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1728 if (appData.icsActive) {
1729 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1733 MachineBlackEvent();
1734 } else if (initialMode == TwoMachinesPlay) {
1735 if (appData.noChessProgram) {
1736 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1740 if (appData.icsActive) {
1741 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1746 } else if (initialMode == EditGame) {
1748 } else if (initialMode == EditPosition) {
1749 EditPositionEvent();
1750 } else if (initialMode == Training) {
1751 if (*appData.loadGameFile == NULLCHAR) {
1752 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1761 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1763 DisplayBook(current+1);
1765 MoveHistorySet( movelist, first, last, current, pvInfoList );
1767 EvalGraphSet( first, last, current, pvInfoList );
1769 MakeEngineOutputTitle();
1773 * Establish will establish a contact to a remote host.port.
1774 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1775 * used to talk to the host.
1776 * Returns 0 if okay, error code if not.
1783 if (*appData.icsCommPort != NULLCHAR) {
1784 /* Talk to the host through a serial comm port */
1785 return OpenCommPort(appData.icsCommPort, &icsPR);
1787 } else if (*appData.gateway != NULLCHAR) {
1788 if (*appData.remoteShell == NULLCHAR) {
1789 /* Use the rcmd protocol to run telnet program on a gateway host */
1790 snprintf(buf, sizeof(buf), "%s %s %s",
1791 appData.telnetProgram, appData.icsHost, appData.icsPort);
1792 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1795 /* Use the rsh program to run telnet program on a gateway host */
1796 if (*appData.remoteUser == NULLCHAR) {
1797 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1798 appData.gateway, appData.telnetProgram,
1799 appData.icsHost, appData.icsPort);
1801 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1802 appData.remoteShell, appData.gateway,
1803 appData.remoteUser, appData.telnetProgram,
1804 appData.icsHost, appData.icsPort);
1806 return StartChildProcess(buf, "", &icsPR);
1809 } else if (appData.useTelnet) {
1810 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1813 /* TCP socket interface differs somewhat between
1814 Unix and NT; handle details in the front end.
1816 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1821 EscapeExpand (char *p, char *q)
1822 { // [HGM] initstring: routine to shape up string arguments
1823 while(*p++ = *q++) if(p[-1] == '\\')
1825 case 'n': p[-1] = '\n'; break;
1826 case 'r': p[-1] = '\r'; break;
1827 case 't': p[-1] = '\t'; break;
1828 case '\\': p[-1] = '\\'; break;
1829 case 0: *p = 0; return;
1830 default: p[-1] = q[-1]; break;
1835 show_bytes (FILE *fp, char *buf, int count)
1838 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1839 fprintf(fp, "\\%03o", *buf & 0xff);
1848 /* Returns an errno value */
1850 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1852 char buf[8192], *p, *q, *buflim;
1853 int left, newcount, outcount;
1855 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1856 *appData.gateway != NULLCHAR) {
1857 if (appData.debugMode) {
1858 fprintf(debugFP, ">ICS: ");
1859 show_bytes(debugFP, message, count);
1860 fprintf(debugFP, "\n");
1862 return OutputToProcess(pr, message, count, outError);
1865 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1872 if (appData.debugMode) {
1873 fprintf(debugFP, ">ICS: ");
1874 show_bytes(debugFP, buf, newcount);
1875 fprintf(debugFP, "\n");
1877 outcount = OutputToProcess(pr, buf, newcount, outError);
1878 if (outcount < newcount) return -1; /* to be sure */
1885 } else if (((unsigned char) *p) == TN_IAC) {
1886 *q++ = (char) TN_IAC;
1893 if (appData.debugMode) {
1894 fprintf(debugFP, ">ICS: ");
1895 show_bytes(debugFP, buf, newcount);
1896 fprintf(debugFP, "\n");
1898 outcount = OutputToProcess(pr, buf, newcount, outError);
1899 if (outcount < newcount) return -1; /* to be sure */
1904 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1906 int outError, outCount;
1907 static int gotEof = 0;
1910 /* Pass data read from player on to ICS */
1913 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1914 if (outCount < count) {
1915 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1917 if(have_sent_ICS_logon == 2) {
1918 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1919 fprintf(ini, "%s", message);
1920 have_sent_ICS_logon = 3;
1922 have_sent_ICS_logon = 1;
1923 } else if(have_sent_ICS_logon == 3) {
1924 fprintf(ini, "%s", message);
1926 have_sent_ICS_logon = 1;
1928 } else if (count < 0) {
1929 RemoveInputSource(isr);
1930 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1931 } else if (gotEof++ > 0) {
1932 RemoveInputSource(isr);
1933 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1939 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1940 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1941 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1942 SendToICS("date\n");
1943 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1946 /* added routine for printf style output to ics */
1948 ics_printf (char *format, ...)
1950 char buffer[MSG_SIZ];
1953 va_start(args, format);
1954 vsnprintf(buffer, sizeof(buffer), format, args);
1955 buffer[sizeof(buffer)-1] = '\0';
1963 int count, outCount, outError;
1965 if (icsPR == NoProc) return;
1968 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1969 if (outCount < count) {
1970 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1974 /* This is used for sending logon scripts to the ICS. Sending
1975 without a delay causes problems when using timestamp on ICC
1976 (at least on my machine). */
1978 SendToICSDelayed (char *s, long msdelay)
1980 int count, outCount, outError;
1982 if (icsPR == NoProc) return;
1985 if (appData.debugMode) {
1986 fprintf(debugFP, ">ICS: ");
1987 show_bytes(debugFP, s, count);
1988 fprintf(debugFP, "\n");
1990 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1992 if (outCount < count) {
1993 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1998 /* Remove all highlighting escape sequences in s
1999 Also deletes any suffix starting with '('
2002 StripHighlightAndTitle (char *s)
2004 static char retbuf[MSG_SIZ];
2007 while (*s != NULLCHAR) {
2008 while (*s == '\033') {
2009 while (*s != NULLCHAR && !isalpha(*s)) s++;
2010 if (*s != NULLCHAR) s++;
2012 while (*s != NULLCHAR && *s != '\033') {
2013 if (*s == '(' || *s == '[') {
2024 /* Remove all highlighting escape sequences in s */
2026 StripHighlight (char *s)
2028 static char retbuf[MSG_SIZ];
2031 while (*s != NULLCHAR) {
2032 while (*s == '\033') {
2033 while (*s != NULLCHAR && !isalpha(*s)) s++;
2034 if (*s != NULLCHAR) s++;
2036 while (*s != NULLCHAR && *s != '\033') {
2044 char engineVariant[MSG_SIZ];
2045 char *variantNames[] = VARIANT_NAMES;
2047 VariantName (VariantClass v)
2049 if(v == VariantUnknown || *engineVariant) return engineVariant;
2050 return variantNames[v];
2054 /* Identify a variant from the strings the chess servers use or the
2055 PGN Variant tag names we use. */
2057 StringToVariant (char *e)
2061 VariantClass v = VariantNormal;
2062 int i, found = FALSE;
2068 /* [HGM] skip over optional board-size prefixes */
2069 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2070 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2071 while( *e++ != '_');
2074 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2078 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2079 if (p = StrCaseStr(e, variantNames[i])) {
2080 if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2081 v = (VariantClass) i;
2088 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2089 || StrCaseStr(e, "wild/fr")
2090 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2091 v = VariantFischeRandom;
2092 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2093 (i = 1, p = StrCaseStr(e, "w"))) {
2095 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2102 case 0: /* FICS only, actually */
2104 /* Castling legal even if K starts on d-file */
2105 v = VariantWildCastle;
2110 /* Castling illegal even if K & R happen to start in
2111 normal positions. */
2112 v = VariantNoCastle;
2125 /* Castling legal iff K & R start in normal positions */
2131 /* Special wilds for position setup; unclear what to do here */
2132 v = VariantLoadable;
2135 /* Bizarre ICC game */
2136 v = VariantTwoKings;
2139 v = VariantKriegspiel;
2145 v = VariantFischeRandom;
2148 v = VariantCrazyhouse;
2151 v = VariantBughouse;
2157 /* Not quite the same as FICS suicide! */
2158 v = VariantGiveaway;
2164 v = VariantShatranj;
2167 /* Temporary names for future ICC types. The name *will* change in
2168 the next xboard/WinBoard release after ICC defines it. */
2206 v = VariantCapablanca;
2209 v = VariantKnightmate;
2215 v = VariantCylinder;
2221 v = VariantCapaRandom;
2224 v = VariantBerolina;
2236 /* Found "wild" or "w" in the string but no number;
2237 must assume it's normal chess. */
2241 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2242 if( (len >= MSG_SIZ) && appData.debugMode )
2243 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2245 DisplayError(buf, 0);
2251 if (appData.debugMode) {
2252 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2253 e, wnum, VariantName(v));
2258 static int leftover_start = 0, leftover_len = 0;
2259 char star_match[STAR_MATCH_N][MSG_SIZ];
2261 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2262 advance *index beyond it, and set leftover_start to the new value of
2263 *index; else return FALSE. If pattern contains the character '*', it
2264 matches any sequence of characters not containing '\r', '\n', or the
2265 character following the '*' (if any), and the matched sequence(s) are
2266 copied into star_match.
2269 looking_at ( char *buf, int *index, char *pattern)
2271 char *bufp = &buf[*index], *patternp = pattern;
2273 char *matchp = star_match[0];
2276 if (*patternp == NULLCHAR) {
2277 *index = leftover_start = bufp - buf;
2281 if (*bufp == NULLCHAR) return FALSE;
2282 if (*patternp == '*') {
2283 if (*bufp == *(patternp + 1)) {
2285 matchp = star_match[++star_count];
2289 } else if (*bufp == '\n' || *bufp == '\r') {
2291 if (*patternp == NULLCHAR)
2296 *matchp++ = *bufp++;
2300 if (*patternp != *bufp) return FALSE;
2307 SendToPlayer (char *data, int length)
2309 int error, outCount;
2310 outCount = OutputToProcess(NoProc, data, length, &error);
2311 if (outCount < length) {
2312 DisplayFatalError(_("Error writing to display"), error, 1);
2317 PackHolding (char packed[], char *holding)
2327 switch (runlength) {
2338 sprintf(q, "%d", runlength);
2350 /* Telnet protocol requests from the front end */
2352 TelnetRequest (unsigned char ddww, unsigned char option)
2354 unsigned char msg[3];
2355 int outCount, outError;
2357 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2359 if (appData.debugMode) {
2360 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2376 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2385 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2388 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2393 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2395 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2402 if (!appData.icsActive) return;
2403 TelnetRequest(TN_DO, TN_ECHO);
2409 if (!appData.icsActive) return;
2410 TelnetRequest(TN_DONT, TN_ECHO);
2414 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2416 /* put the holdings sent to us by the server on the board holdings area */
2417 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2421 if(gameInfo.holdingsWidth < 2) return;
2422 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2423 return; // prevent overwriting by pre-board holdings
2425 if( (int)lowestPiece >= BlackPawn ) {
2428 holdingsStartRow = BOARD_HEIGHT-1;
2431 holdingsColumn = BOARD_WIDTH-1;
2432 countsColumn = BOARD_WIDTH-2;
2433 holdingsStartRow = 0;
2437 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2438 board[i][holdingsColumn] = EmptySquare;
2439 board[i][countsColumn] = (ChessSquare) 0;
2441 while( (p=*holdings++) != NULLCHAR ) {
2442 piece = CharToPiece( ToUpper(p) );
2443 if(piece == EmptySquare) continue;
2444 /*j = (int) piece - (int) WhitePawn;*/
2445 j = PieceToNumber(piece);
2446 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2447 if(j < 0) continue; /* should not happen */
2448 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2449 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2450 board[holdingsStartRow+j*direction][countsColumn]++;
2456 VariantSwitch (Board board, VariantClass newVariant)
2458 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2459 static Board oldBoard;
2461 startedFromPositionFile = FALSE;
2462 if(gameInfo.variant == newVariant) return;
2464 /* [HGM] This routine is called each time an assignment is made to
2465 * gameInfo.variant during a game, to make sure the board sizes
2466 * are set to match the new variant. If that means adding or deleting
2467 * holdings, we shift the playing board accordingly
2468 * This kludge is needed because in ICS observe mode, we get boards
2469 * of an ongoing game without knowing the variant, and learn about the
2470 * latter only later. This can be because of the move list we requested,
2471 * in which case the game history is refilled from the beginning anyway,
2472 * but also when receiving holdings of a crazyhouse game. In the latter
2473 * case we want to add those holdings to the already received position.
2477 if (appData.debugMode) {
2478 fprintf(debugFP, "Switch board from %s to %s\n",
2479 VariantName(gameInfo.variant), VariantName(newVariant));
2480 setbuf(debugFP, NULL);
2482 shuffleOpenings = 0; /* [HGM] shuffle */
2483 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2487 newWidth = 9; newHeight = 9;
2488 gameInfo.holdingsSize = 7;
2489 case VariantBughouse:
2490 case VariantCrazyhouse:
2491 newHoldingsWidth = 2; break;
2495 newHoldingsWidth = 2;
2496 gameInfo.holdingsSize = 8;
2499 case VariantCapablanca:
2500 case VariantCapaRandom:
2503 newHoldingsWidth = gameInfo.holdingsSize = 0;
2506 if(newWidth != gameInfo.boardWidth ||
2507 newHeight != gameInfo.boardHeight ||
2508 newHoldingsWidth != gameInfo.holdingsWidth ) {
2510 /* shift position to new playing area, if needed */
2511 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2512 for(i=0; i<BOARD_HEIGHT; i++)
2513 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2514 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2516 for(i=0; i<newHeight; i++) {
2517 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2518 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2520 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2521 for(i=0; i<BOARD_HEIGHT; i++)
2522 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2523 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2526 board[HOLDINGS_SET] = 0;
2527 gameInfo.boardWidth = newWidth;
2528 gameInfo.boardHeight = newHeight;
2529 gameInfo.holdingsWidth = newHoldingsWidth;
2530 gameInfo.variant = newVariant;
2531 InitDrawingSizes(-2, 0);
2532 } else gameInfo.variant = newVariant;
2533 CopyBoard(oldBoard, board); // remember correctly formatted board
2534 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2535 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2538 static int loggedOn = FALSE;
2540 /*-- Game start info cache: --*/
2542 char gs_kind[MSG_SIZ];
2543 static char player1Name[128] = "";
2544 static char player2Name[128] = "";
2545 static char cont_seq[] = "\n\\ ";
2546 static int player1Rating = -1;
2547 static int player2Rating = -1;
2548 /*----------------------------*/
2550 ColorClass curColor = ColorNormal;
2551 int suppressKibitz = 0;
2554 Boolean soughtPending = FALSE;
2555 Boolean seekGraphUp;
2556 #define MAX_SEEK_ADS 200
2558 char *seekAdList[MAX_SEEK_ADS];
2559 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2560 float tcList[MAX_SEEK_ADS];
2561 char colorList[MAX_SEEK_ADS];
2562 int nrOfSeekAds = 0;
2563 int minRating = 1010, maxRating = 2800;
2564 int hMargin = 10, vMargin = 20, h, w;
2565 extern int squareSize, lineGap;
2570 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2571 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2572 if(r < minRating+100 && r >=0 ) r = minRating+100;
2573 if(r > maxRating) r = maxRating;
2574 if(tc < 1.f) tc = 1.f;
2575 if(tc > 95.f) tc = 95.f;
2576 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2577 y = ((double)r - minRating)/(maxRating - minRating)
2578 * (h-vMargin-squareSize/8-1) + vMargin;
2579 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2580 if(strstr(seekAdList[i], " u ")) color = 1;
2581 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2582 !strstr(seekAdList[i], "bullet") &&
2583 !strstr(seekAdList[i], "blitz") &&
2584 !strstr(seekAdList[i], "standard") ) color = 2;
2585 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2586 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2590 PlotSingleSeekAd (int i)
2596 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2598 char buf[MSG_SIZ], *ext = "";
2599 VariantClass v = StringToVariant(type);
2600 if(strstr(type, "wild")) {
2601 ext = type + 4; // append wild number
2602 if(v == VariantFischeRandom) type = "chess960"; else
2603 if(v == VariantLoadable) type = "setup"; else
2604 type = VariantName(v);
2606 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2607 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2608 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2609 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2610 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2611 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2612 seekNrList[nrOfSeekAds] = nr;
2613 zList[nrOfSeekAds] = 0;
2614 seekAdList[nrOfSeekAds++] = StrSave(buf);
2615 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2620 EraseSeekDot (int i)
2622 int x = xList[i], y = yList[i], d=squareSize/4, k;
2623 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2624 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2625 // now replot every dot that overlapped
2626 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2627 int xx = xList[k], yy = yList[k];
2628 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2629 DrawSeekDot(xx, yy, colorList[k]);
2634 RemoveSeekAd (int nr)
2637 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2639 if(seekAdList[i]) free(seekAdList[i]);
2640 seekAdList[i] = seekAdList[--nrOfSeekAds];
2641 seekNrList[i] = seekNrList[nrOfSeekAds];
2642 ratingList[i] = ratingList[nrOfSeekAds];
2643 colorList[i] = colorList[nrOfSeekAds];
2644 tcList[i] = tcList[nrOfSeekAds];
2645 xList[i] = xList[nrOfSeekAds];
2646 yList[i] = yList[nrOfSeekAds];
2647 zList[i] = zList[nrOfSeekAds];
2648 seekAdList[nrOfSeekAds] = NULL;
2654 MatchSoughtLine (char *line)
2656 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2657 int nr, base, inc, u=0; char dummy;
2659 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2660 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2662 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2663 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2664 // match: compact and save the line
2665 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2675 if(!seekGraphUp) return FALSE;
2676 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2677 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2679 DrawSeekBackground(0, 0, w, h);
2680 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2681 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2682 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2683 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2685 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2688 snprintf(buf, MSG_SIZ, "%d", i);
2689 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2692 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2693 for(i=1; i<100; i+=(i<10?1:5)) {
2694 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2695 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2696 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2698 snprintf(buf, MSG_SIZ, "%d", i);
2699 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2702 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2707 SeekGraphClick (ClickType click, int x, int y, int moving)
2709 static int lastDown = 0, displayed = 0, lastSecond;
2710 if(y < 0) return FALSE;
2711 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2712 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2713 if(!seekGraphUp) return FALSE;
2714 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2715 DrawPosition(TRUE, NULL);
2718 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2719 if(click == Release || moving) return FALSE;
2721 soughtPending = TRUE;
2722 SendToICS(ics_prefix);
2723 SendToICS("sought\n"); // should this be "sought all"?
2724 } else { // issue challenge based on clicked ad
2725 int dist = 10000; int i, closest = 0, second = 0;
2726 for(i=0; i<nrOfSeekAds; i++) {
2727 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2728 if(d < dist) { dist = d; closest = i; }
2729 second += (d - zList[i] < 120); // count in-range ads
2730 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2734 second = (second > 1);
2735 if(displayed != closest || second != lastSecond) {
2736 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2737 lastSecond = second; displayed = closest;
2739 if(click == Press) {
2740 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2743 } // on press 'hit', only show info
2744 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2745 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2746 SendToICS(ics_prefix);
2748 return TRUE; // let incoming board of started game pop down the graph
2749 } else if(click == Release) { // release 'miss' is ignored
2750 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2751 if(moving == 2) { // right up-click
2752 nrOfSeekAds = 0; // refresh graph
2753 soughtPending = TRUE;
2754 SendToICS(ics_prefix);
2755 SendToICS("sought\n"); // should this be "sought all"?
2758 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2759 // press miss or release hit 'pop down' seek graph
2760 seekGraphUp = FALSE;
2761 DrawPosition(TRUE, NULL);
2767 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2769 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2770 #define STARTED_NONE 0
2771 #define STARTED_MOVES 1
2772 #define STARTED_BOARD 2
2773 #define STARTED_OBSERVE 3
2774 #define STARTED_HOLDINGS 4
2775 #define STARTED_CHATTER 5
2776 #define STARTED_COMMENT 6
2777 #define STARTED_MOVES_NOHIDE 7
2779 static int started = STARTED_NONE;
2780 static char parse[20000];
2781 static int parse_pos = 0;
2782 static char buf[BUF_SIZE + 1];
2783 static int firstTime = TRUE, intfSet = FALSE;
2784 static ColorClass prevColor = ColorNormal;
2785 static int savingComment = FALSE;
2786 static int cmatch = 0; // continuation sequence match
2793 int backup; /* [DM] For zippy color lines */
2795 char talker[MSG_SIZ]; // [HGM] chat
2798 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2800 if (appData.debugMode) {
2802 fprintf(debugFP, "<ICS: ");
2803 show_bytes(debugFP, data, count);
2804 fprintf(debugFP, "\n");
2808 if (appData.debugMode) { int f = forwardMostMove;
2809 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2810 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2811 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2814 /* If last read ended with a partial line that we couldn't parse,
2815 prepend it to the new read and try again. */
2816 if (leftover_len > 0) {
2817 for (i=0; i<leftover_len; i++)
2818 buf[i] = buf[leftover_start + i];
2821 /* copy new characters into the buffer */
2822 bp = buf + leftover_len;
2823 buf_len=leftover_len;
2824 for (i=0; i<count; i++)
2827 if (data[i] == '\r')
2830 // join lines split by ICS?
2831 if (!appData.noJoin)
2834 Joining just consists of finding matches against the
2835 continuation sequence, and discarding that sequence
2836 if found instead of copying it. So, until a match
2837 fails, there's nothing to do since it might be the
2838 complete sequence, and thus, something we don't want
2841 if (data[i] == cont_seq[cmatch])
2844 if (cmatch == strlen(cont_seq))
2846 cmatch = 0; // complete match. just reset the counter
2849 it's possible for the ICS to not include the space
2850 at the end of the last word, making our [correct]
2851 join operation fuse two separate words. the server
2852 does this when the space occurs at the width setting.
2854 if (!buf_len || buf[buf_len-1] != ' ')
2865 match failed, so we have to copy what matched before
2866 falling through and copying this character. In reality,
2867 this will only ever be just the newline character, but
2868 it doesn't hurt to be precise.
2870 strncpy(bp, cont_seq, cmatch);
2882 buf[buf_len] = NULLCHAR;
2883 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2888 while (i < buf_len) {
2889 /* Deal with part of the TELNET option negotiation
2890 protocol. We refuse to do anything beyond the
2891 defaults, except that we allow the WILL ECHO option,
2892 which ICS uses to turn off password echoing when we are
2893 directly connected to it. We reject this option
2894 if localLineEditing mode is on (always on in xboard)
2895 and we are talking to port 23, which might be a real
2896 telnet server that will try to keep WILL ECHO on permanently.
2898 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2899 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2900 unsigned char option;
2902 switch ((unsigned char) buf[++i]) {
2904 if (appData.debugMode)
2905 fprintf(debugFP, "\n<WILL ");
2906 switch (option = (unsigned char) buf[++i]) {
2908 if (appData.debugMode)
2909 fprintf(debugFP, "ECHO ");
2910 /* Reply only if this is a change, according
2911 to the protocol rules. */
2912 if (remoteEchoOption) break;
2913 if (appData.localLineEditing &&
2914 atoi(appData.icsPort) == TN_PORT) {
2915 TelnetRequest(TN_DONT, TN_ECHO);
2918 TelnetRequest(TN_DO, TN_ECHO);
2919 remoteEchoOption = TRUE;
2923 if (appData.debugMode)
2924 fprintf(debugFP, "%d ", option);
2925 /* Whatever this is, we don't want it. */
2926 TelnetRequest(TN_DONT, option);
2931 if (appData.debugMode)
2932 fprintf(debugFP, "\n<WONT ");
2933 switch (option = (unsigned char) buf[++i]) {
2935 if (appData.debugMode)
2936 fprintf(debugFP, "ECHO ");
2937 /* Reply only if this is a change, according
2938 to the protocol rules. */
2939 if (!remoteEchoOption) break;
2941 TelnetRequest(TN_DONT, TN_ECHO);
2942 remoteEchoOption = FALSE;
2945 if (appData.debugMode)
2946 fprintf(debugFP, "%d ", (unsigned char) option);
2947 /* Whatever this is, it must already be turned
2948 off, because we never agree to turn on
2949 anything non-default, so according to the
2950 protocol rules, we don't reply. */
2955 if (appData.debugMode)
2956 fprintf(debugFP, "\n<DO ");
2957 switch (option = (unsigned char) buf[++i]) {
2959 /* Whatever this is, we refuse to do it. */
2960 if (appData.debugMode)
2961 fprintf(debugFP, "%d ", option);
2962 TelnetRequest(TN_WONT, option);
2967 if (appData.debugMode)
2968 fprintf(debugFP, "\n<DONT ");
2969 switch (option = (unsigned char) buf[++i]) {
2971 if (appData.debugMode)
2972 fprintf(debugFP, "%d ", option);
2973 /* Whatever this is, we are already not doing
2974 it, because we never agree to do anything
2975 non-default, so according to the protocol
2976 rules, we don't reply. */
2981 if (appData.debugMode)
2982 fprintf(debugFP, "\n<IAC ");
2983 /* Doubled IAC; pass it through */
2987 if (appData.debugMode)
2988 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2989 /* Drop all other telnet commands on the floor */
2992 if (oldi > next_out)
2993 SendToPlayer(&buf[next_out], oldi - next_out);
2999 /* OK, this at least will *usually* work */
3000 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3004 if (loggedOn && !intfSet) {
3005 if (ics_type == ICS_ICC) {
3006 snprintf(str, MSG_SIZ,
3007 "/set-quietly interface %s\n/set-quietly style 12\n",
3009 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3010 strcat(str, "/set-2 51 1\n/set seek 1\n");
3011 } else if (ics_type == ICS_CHESSNET) {
3012 snprintf(str, MSG_SIZ, "/style 12\n");
3014 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3015 strcat(str, programVersion);
3016 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3017 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3018 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3020 strcat(str, "$iset nohighlight 1\n");
3022 strcat(str, "$iset lock 1\n$style 12\n");
3025 NotifyFrontendLogin();
3029 if (started == STARTED_COMMENT) {
3030 /* Accumulate characters in comment */
3031 parse[parse_pos++] = buf[i];
3032 if (buf[i] == '\n') {
3033 parse[parse_pos] = NULLCHAR;
3034 if(chattingPartner>=0) {
3036 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3037 OutputChatMessage(chattingPartner, mess);
3038 chattingPartner = -1;
3039 next_out = i+1; // [HGM] suppress printing in ICS window
3041 if(!suppressKibitz) // [HGM] kibitz
3042 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3043 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3044 int nrDigit = 0, nrAlph = 0, j;
3045 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3046 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3047 parse[parse_pos] = NULLCHAR;
3048 // try to be smart: if it does not look like search info, it should go to
3049 // ICS interaction window after all, not to engine-output window.
3050 for(j=0; j<parse_pos; j++) { // count letters and digits
3051 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3052 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3053 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3055 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3056 int depth=0; float score;
3057 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3058 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3059 pvInfoList[forwardMostMove-1].depth = depth;
3060 pvInfoList[forwardMostMove-1].score = 100*score;
3062 OutputKibitz(suppressKibitz, parse);
3065 if(gameMode == IcsObserving) // restore original ICS messages
3066 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3067 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3069 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3070 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3071 SendToPlayer(tmp, strlen(tmp));
3073 next_out = i+1; // [HGM] suppress printing in ICS window
3075 started = STARTED_NONE;
3077 /* Don't match patterns against characters in comment */
3082 if (started == STARTED_CHATTER) {
3083 if (buf[i] != '\n') {
3084 /* Don't match patterns against characters in chatter */
3088 started = STARTED_NONE;
3089 if(suppressKibitz) next_out = i+1;
3092 /* Kludge to deal with rcmd protocol */
3093 if (firstTime && looking_at(buf, &i, "\001*")) {
3094 DisplayFatalError(&buf[1], 0, 1);
3100 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3103 if (appData.debugMode)
3104 fprintf(debugFP, "ics_type %d\n", ics_type);
3107 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3108 ics_type = ICS_FICS;
3110 if (appData.debugMode)
3111 fprintf(debugFP, "ics_type %d\n", ics_type);
3114 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3115 ics_type = ICS_CHESSNET;
3117 if (appData.debugMode)
3118 fprintf(debugFP, "ics_type %d\n", ics_type);
3123 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3124 looking_at(buf, &i, "Logging you in as \"*\"") ||
3125 looking_at(buf, &i, "will be \"*\""))) {
3126 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3130 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3132 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3133 DisplayIcsInteractionTitle(buf);
3134 have_set_title = TRUE;
3137 /* skip finger notes */
3138 if (started == STARTED_NONE &&
3139 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3140 (buf[i] == '1' && buf[i+1] == '0')) &&
3141 buf[i+2] == ':' && buf[i+3] == ' ') {
3142 started = STARTED_CHATTER;
3148 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3149 if(appData.seekGraph) {
3150 if(soughtPending && MatchSoughtLine(buf+i)) {
3151 i = strstr(buf+i, "rated") - buf;
3152 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3153 next_out = leftover_start = i;
3154 started = STARTED_CHATTER;
3155 suppressKibitz = TRUE;
3158 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3159 && looking_at(buf, &i, "* ads displayed")) {
3160 soughtPending = FALSE;
3165 if(appData.autoRefresh) {
3166 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3167 int s = (ics_type == ICS_ICC); // ICC format differs
3169 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3170 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3171 looking_at(buf, &i, "*% "); // eat prompt
3172 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3173 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3174 next_out = i; // suppress
3177 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3178 char *p = star_match[0];
3180 if(seekGraphUp) RemoveSeekAd(atoi(p));
3181 while(*p && *p++ != ' '); // next
3183 looking_at(buf, &i, "*% "); // eat prompt
3184 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3191 /* skip formula vars */
3192 if (started == STARTED_NONE &&
3193 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3194 started = STARTED_CHATTER;
3199 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3200 if (appData.autoKibitz && started == STARTED_NONE &&
3201 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3202 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3203 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3204 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3205 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3206 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3207 suppressKibitz = TRUE;
3208 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3210 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3211 && (gameMode == IcsPlayingWhite)) ||
3212 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3213 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3214 started = STARTED_CHATTER; // own kibitz we simply discard
3216 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3217 parse_pos = 0; parse[0] = NULLCHAR;
3218 savingComment = TRUE;
3219 suppressKibitz = gameMode != IcsObserving ? 2 :
3220 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3224 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3225 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3226 && atoi(star_match[0])) {
3227 // suppress the acknowledgements of our own autoKibitz
3229 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3230 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3231 SendToPlayer(star_match[0], strlen(star_match[0]));
3232 if(looking_at(buf, &i, "*% ")) // eat prompt
3233 suppressKibitz = FALSE;
3237 } // [HGM] kibitz: end of patch
3239 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3241 // [HGM] chat: intercept tells by users for which we have an open chat window
3243 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3244 looking_at(buf, &i, "* whispers:") ||
3245 looking_at(buf, &i, "* kibitzes:") ||
3246 looking_at(buf, &i, "* shouts:") ||
3247 looking_at(buf, &i, "* c-shouts:") ||
3248 looking_at(buf, &i, "--> * ") ||
3249 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3250 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3251 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3252 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3254 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3255 chattingPartner = -1;
3257 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3258 for(p=0; p<MAX_CHAT; p++) {
3259 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3260 talker[0] = '['; strcat(talker, "] ");
3261 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3262 chattingPartner = p; break;
3265 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3266 for(p=0; p<MAX_CHAT; p++) {
3267 if(!strcmp("kibitzes", chatPartner[p])) {
3268 talker[0] = '['; strcat(talker, "] ");
3269 chattingPartner = p; break;
3272 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3273 for(p=0; p<MAX_CHAT; p++) {
3274 if(!strcmp("whispers", chatPartner[p])) {
3275 talker[0] = '['; strcat(talker, "] ");
3276 chattingPartner = p; break;
3279 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3280 if(buf[i-8] == '-' && buf[i-3] == 't')
3281 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3282 if(!strcmp("c-shouts", chatPartner[p])) {
3283 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3284 chattingPartner = p; break;
3287 if(chattingPartner < 0)
3288 for(p=0; p<MAX_CHAT; p++) {
3289 if(!strcmp("shouts", chatPartner[p])) {
3290 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3291 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3292 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3293 chattingPartner = p; break;
3297 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3298 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3299 talker[0] = 0; Colorize(ColorTell, FALSE);
3300 chattingPartner = p; break;
3302 if(chattingPartner<0) i = oldi; else {
3303 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3304 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3305 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3306 started = STARTED_COMMENT;
3307 parse_pos = 0; parse[0] = NULLCHAR;
3308 savingComment = 3 + chattingPartner; // counts as TRUE
3309 suppressKibitz = TRUE;
3312 } // [HGM] chat: end of patch
3315 if (appData.zippyTalk || appData.zippyPlay) {
3316 /* [DM] Backup address for color zippy lines */
3318 if (loggedOn == TRUE)
3319 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3320 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3322 } // [DM] 'else { ' deleted
3324 /* Regular tells and says */
3325 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3326 looking_at(buf, &i, "* (your partner) tells you: ") ||
3327 looking_at(buf, &i, "* says: ") ||
3328 /* Don't color "message" or "messages" output */
3329 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3330 looking_at(buf, &i, "*. * at *:*: ") ||
3331 looking_at(buf, &i, "--* (*:*): ") ||
3332 /* Message notifications (same color as tells) */
3333 looking_at(buf, &i, "* has left a message ") ||
3334 looking_at(buf, &i, "* just sent you a message:\n") ||
3335 /* Whispers and kibitzes */
3336 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3337 looking_at(buf, &i, "* kibitzes: ") ||
3339 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3341 if (tkind == 1 && strchr(star_match[0], ':')) {
3342 /* Avoid "tells you:" spoofs in channels */
3345 if (star_match[0][0] == NULLCHAR ||
3346 strchr(star_match[0], ' ') ||
3347 (tkind == 3 && strchr(star_match[1], ' '))) {
3348 /* Reject bogus matches */
3351 if (appData.colorize) {
3352 if (oldi > next_out) {
3353 SendToPlayer(&buf[next_out], oldi - next_out);
3358 Colorize(ColorTell, FALSE);
3359 curColor = ColorTell;
3362 Colorize(ColorKibitz, FALSE);
3363 curColor = ColorKibitz;
3366 p = strrchr(star_match[1], '(');
3373 Colorize(ColorChannel1, FALSE);
3374 curColor = ColorChannel1;
3376 Colorize(ColorChannel, FALSE);
3377 curColor = ColorChannel;
3381 curColor = ColorNormal;
3385 if (started == STARTED_NONE && appData.autoComment &&
3386 (gameMode == IcsObserving ||
3387 gameMode == IcsPlayingWhite ||
3388 gameMode == IcsPlayingBlack)) {
3389 parse_pos = i - oldi;
3390 memcpy(parse, &buf[oldi], parse_pos);
3391 parse[parse_pos] = NULLCHAR;
3392 started = STARTED_COMMENT;
3393 savingComment = TRUE;
3395 started = STARTED_CHATTER;
3396 savingComment = FALSE;
3403 if (looking_at(buf, &i, "* s-shouts: ") ||
3404 looking_at(buf, &i, "* c-shouts: ")) {
3405 if (appData.colorize) {
3406 if (oldi > next_out) {
3407 SendToPlayer(&buf[next_out], oldi - next_out);
3410 Colorize(ColorSShout, FALSE);
3411 curColor = ColorSShout;
3414 started = STARTED_CHATTER;
3418 if (looking_at(buf, &i, "--->")) {
3423 if (looking_at(buf, &i, "* shouts: ") ||
3424 looking_at(buf, &i, "--> ")) {
3425 if (appData.colorize) {
3426 if (oldi > next_out) {
3427 SendToPlayer(&buf[next_out], oldi - next_out);
3430 Colorize(ColorShout, FALSE);
3431 curColor = ColorShout;
3434 started = STARTED_CHATTER;
3438 if (looking_at( buf, &i, "Challenge:")) {
3439 if (appData.colorize) {
3440 if (oldi > next_out) {
3441 SendToPlayer(&buf[next_out], oldi - next_out);
3444 Colorize(ColorChallenge, FALSE);
3445 curColor = ColorChallenge;
3451 if (looking_at(buf, &i, "* offers you") ||
3452 looking_at(buf, &i, "* offers to be") ||
3453 looking_at(buf, &i, "* would like to") ||
3454 looking_at(buf, &i, "* requests to") ||
3455 looking_at(buf, &i, "Your opponent offers") ||
3456 looking_at(buf, &i, "Your opponent requests")) {
3458 if (appData.colorize) {
3459 if (oldi > next_out) {
3460 SendToPlayer(&buf[next_out], oldi - next_out);
3463 Colorize(ColorRequest, FALSE);
3464 curColor = ColorRequest;
3469 if (looking_at(buf, &i, "* (*) seeking")) {
3470 if (appData.colorize) {
3471 if (oldi > next_out) {
3472 SendToPlayer(&buf[next_out], oldi - next_out);
3475 Colorize(ColorSeek, FALSE);
3476 curColor = ColorSeek;
3481 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3483 if (looking_at(buf, &i, "\\ ")) {
3484 if (prevColor != ColorNormal) {
3485 if (oldi > next_out) {
3486 SendToPlayer(&buf[next_out], oldi - next_out);
3489 Colorize(prevColor, TRUE);
3490 curColor = prevColor;
3492 if (savingComment) {
3493 parse_pos = i - oldi;
3494 memcpy(parse, &buf[oldi], parse_pos);
3495 parse[parse_pos] = NULLCHAR;
3496 started = STARTED_COMMENT;
3497 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3498 chattingPartner = savingComment - 3; // kludge to remember the box
3500 started = STARTED_CHATTER;
3505 if (looking_at(buf, &i, "Black Strength :") ||
3506 looking_at(buf, &i, "<<< style 10 board >>>") ||
3507 looking_at(buf, &i, "<10>") ||
3508 looking_at(buf, &i, "#@#")) {
3509 /* Wrong board style */
3511 SendToICS(ics_prefix);
3512 SendToICS("set style 12\n");
3513 SendToICS(ics_prefix);
3514 SendToICS("refresh\n");
3518 if (looking_at(buf, &i, "login:")) {
3519 if (!have_sent_ICS_logon) {
3521 have_sent_ICS_logon = 1;
3522 else // no init script was found
3523 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3524 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3525 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3530 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3531 (looking_at(buf, &i, "\n<12> ") ||
3532 looking_at(buf, &i, "<12> "))) {
3534 if (oldi > next_out) {
3535 SendToPlayer(&buf[next_out], oldi - next_out);
3538 started = STARTED_BOARD;
3543 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3544 looking_at(buf, &i, "<b1> ")) {
3545 if (oldi > next_out) {
3546 SendToPlayer(&buf[next_out], oldi - next_out);
3549 started = STARTED_HOLDINGS;
3554 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3556 /* Header for a move list -- first line */
3558 switch (ics_getting_history) {
3562 case BeginningOfGame:
3563 /* User typed "moves" or "oldmoves" while we
3564 were idle. Pretend we asked for these
3565 moves and soak them up so user can step
3566 through them and/or save them.
3569 gameMode = IcsObserving;
3572 ics_getting_history = H_GOT_UNREQ_HEADER;
3574 case EditGame: /*?*/
3575 case EditPosition: /*?*/
3576 /* Should above feature work in these modes too? */
3577 /* For now it doesn't */
3578 ics_getting_history = H_GOT_UNWANTED_HEADER;
3581 ics_getting_history = H_GOT_UNWANTED_HEADER;
3586 /* Is this the right one? */
3587 if (gameInfo.white && gameInfo.black &&
3588 strcmp(gameInfo.white, star_match[0]) == 0 &&
3589 strcmp(gameInfo.black, star_match[2]) == 0) {
3591 ics_getting_history = H_GOT_REQ_HEADER;
3594 case H_GOT_REQ_HEADER:
3595 case H_GOT_UNREQ_HEADER:
3596 case H_GOT_UNWANTED_HEADER:
3597 case H_GETTING_MOVES:
3598 /* Should not happen */
3599 DisplayError(_("Error gathering move list: two headers"), 0);
3600 ics_getting_history = H_FALSE;
3604 /* Save player ratings into gameInfo if needed */
3605 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3606 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3607 (gameInfo.whiteRating == -1 ||
3608 gameInfo.blackRating == -1)) {
3610 gameInfo.whiteRating = string_to_rating(star_match[1]);
3611 gameInfo.blackRating = string_to_rating(star_match[3]);
3612 if (appData.debugMode)
3613 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3614 gameInfo.whiteRating, gameInfo.blackRating);
3619 if (looking_at(buf, &i,
3620 "* * match, initial time: * minute*, increment: * second")) {
3621 /* Header for a move list -- second line */
3622 /* Initial board will follow if this is a wild game */
3623 if (gameInfo.event != NULL) free(gameInfo.event);
3624 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3625 gameInfo.event = StrSave(str);
3626 /* [HGM] we switched variant. Translate boards if needed. */
3627 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3631 if (looking_at(buf, &i, "Move ")) {
3632 /* Beginning of a move list */
3633 switch (ics_getting_history) {
3635 /* Normally should not happen */
3636 /* Maybe user hit reset while we were parsing */
3639 /* Happens if we are ignoring a move list that is not
3640 * the one we just requested. Common if the user
3641 * tries to observe two games without turning off
3644 case H_GETTING_MOVES:
3645 /* Should not happen */
3646 DisplayError(_("Error gathering move list: nested"), 0);
3647 ics_getting_history = H_FALSE;
3649 case H_GOT_REQ_HEADER:
3650 ics_getting_history = H_GETTING_MOVES;
3651 started = STARTED_MOVES;
3653 if (oldi > next_out) {
3654 SendToPlayer(&buf[next_out], oldi - next_out);
3657 case H_GOT_UNREQ_HEADER:
3658 ics_getting_history = H_GETTING_MOVES;
3659 started = STARTED_MOVES_NOHIDE;
3662 case H_GOT_UNWANTED_HEADER:
3663 ics_getting_history = H_FALSE;
3669 if (looking_at(buf, &i, "% ") ||
3670 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3671 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3672 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3673 soughtPending = FALSE;
3677 if(suppressKibitz) next_out = i;
3678 savingComment = FALSE;
3682 case STARTED_MOVES_NOHIDE:
3683 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3684 parse[parse_pos + i - oldi] = NULLCHAR;
3685 ParseGameHistory(parse);
3687 if (appData.zippyPlay && first.initDone) {
3688 FeedMovesToProgram(&first, forwardMostMove);
3689 if (gameMode == IcsPlayingWhite) {
3690 if (WhiteOnMove(forwardMostMove)) {
3691 if (first.sendTime) {
3692 if (first.useColors) {
3693 SendToProgram("black\n", &first);
3695 SendTimeRemaining(&first, TRUE);
3697 if (first.useColors) {
3698 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3700 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3701 first.maybeThinking = TRUE;
3703 if (first.usePlayother) {
3704 if (first.sendTime) {
3705 SendTimeRemaining(&first, TRUE);
3707 SendToProgram("playother\n", &first);
3713 } else if (gameMode == IcsPlayingBlack) {
3714 if (!WhiteOnMove(forwardMostMove)) {
3715 if (first.sendTime) {
3716 if (first.useColors) {
3717 SendToProgram("white\n", &first);
3719 SendTimeRemaining(&first, FALSE);
3721 if (first.useColors) {
3722 SendToProgram("black\n", &first);
3724 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3725 first.maybeThinking = TRUE;
3727 if (first.usePlayother) {
3728 if (first.sendTime) {
3729 SendTimeRemaining(&first, FALSE);
3731 SendToProgram("playother\n", &first);
3740 if (gameMode == IcsObserving && ics_gamenum == -1) {
3741 /* Moves came from oldmoves or moves command
3742 while we weren't doing anything else.
3744 currentMove = forwardMostMove;
3745 ClearHighlights();/*!!could figure this out*/
3746 flipView = appData.flipView;
3747 DrawPosition(TRUE, boards[currentMove]);
3748 DisplayBothClocks();
3749 snprintf(str, MSG_SIZ, "%s %s %s",
3750 gameInfo.white, _("vs."), gameInfo.black);
3754 /* Moves were history of an active game */
3755 if (gameInfo.resultDetails != NULL) {
3756 free(gameInfo.resultDetails);
3757 gameInfo.resultDetails = NULL;
3760 HistorySet(parseList, backwardMostMove,
3761 forwardMostMove, currentMove-1);
3762 DisplayMove(currentMove - 1);
3763 if (started == STARTED_MOVES) next_out = i;
3764 started = STARTED_NONE;
3765 ics_getting_history = H_FALSE;
3768 case STARTED_OBSERVE:
3769 started = STARTED_NONE;
3770 SendToICS(ics_prefix);
3771 SendToICS("refresh\n");
3777 if(bookHit) { // [HGM] book: simulate book reply
3778 static char bookMove[MSG_SIZ]; // a bit generous?
3780 programStats.nodes = programStats.depth = programStats.time =
3781 programStats.score = programStats.got_only_move = 0;
3782 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3784 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3785 strcat(bookMove, bookHit);
3786 HandleMachineMove(bookMove, &first);
3791 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3792 started == STARTED_HOLDINGS ||
3793 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3794 /* Accumulate characters in move list or board */
3795 parse[parse_pos++] = buf[i];
3798 /* Start of game messages. Mostly we detect start of game
3799 when the first board image arrives. On some versions
3800 of the ICS, though, we need to do a "refresh" after starting
3801 to observe in order to get the current board right away. */
3802 if (looking_at(buf, &i, "Adding game * to observation list")) {
3803 started = STARTED_OBSERVE;
3807 /* Handle auto-observe */
3808 if (appData.autoObserve &&
3809 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3810 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3812 /* Choose the player that was highlighted, if any. */
3813 if (star_match[0][0] == '\033' ||
3814 star_match[1][0] != '\033') {
3815 player = star_match[0];
3817 player = star_match[2];
3819 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3820 ics_prefix, StripHighlightAndTitle(player));
3823 /* Save ratings from notify string */
3824 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3825 player1Rating = string_to_rating(star_match[1]);
3826 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3827 player2Rating = string_to_rating(star_match[3]);
3829 if (appData.debugMode)
3831 "Ratings from 'Game notification:' %s %d, %s %d\n",
3832 player1Name, player1Rating,
3833 player2Name, player2Rating);
3838 /* Deal with automatic examine mode after a game,
3839 and with IcsObserving -> IcsExamining transition */
3840 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3841 looking_at(buf, &i, "has made you an examiner of game *")) {
3843 int gamenum = atoi(star_match[0]);
3844 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3845 gamenum == ics_gamenum) {
3846 /* We were already playing or observing this game;
3847 no need to refetch history */
3848 gameMode = IcsExamining;
3850 pauseExamForwardMostMove = forwardMostMove;
3851 } else if (currentMove < forwardMostMove) {
3852 ForwardInner(forwardMostMove);
3855 /* I don't think this case really can happen */
3856 SendToICS(ics_prefix);
3857 SendToICS("refresh\n");
3862 /* Error messages */
3863 // if (ics_user_moved) {
3864 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3865 if (looking_at(buf, &i, "Illegal move") ||
3866 looking_at(buf, &i, "Not a legal move") ||
3867 looking_at(buf, &i, "Your king is in check") ||
3868 looking_at(buf, &i, "It isn't your turn") ||
3869 looking_at(buf, &i, "It is not your move")) {
3871 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3872 currentMove = forwardMostMove-1;
3873 DisplayMove(currentMove - 1); /* before DMError */
3874 DrawPosition(FALSE, boards[currentMove]);
3875 SwitchClocks(forwardMostMove-1); // [HGM] race
3876 DisplayBothClocks();
3878 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3884 if (looking_at(buf, &i, "still have time") ||
3885 looking_at(buf, &i, "not out of time") ||
3886 looking_at(buf, &i, "either player is out of time") ||
3887 looking_at(buf, &i, "has timeseal; checking")) {
3888 /* We must have called his flag a little too soon */
3889 whiteFlag = blackFlag = FALSE;
3893 if (looking_at(buf, &i, "added * seconds to") ||
3894 looking_at(buf, &i, "seconds were added to")) {
3895 /* Update the clocks */
3896 SendToICS(ics_prefix);
3897 SendToICS("refresh\n");
3901 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3902 ics_clock_paused = TRUE;
3907 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3908 ics_clock_paused = FALSE;
3913 /* Grab player ratings from the Creating: message.
3914 Note we have to check for the special case when
3915 the ICS inserts things like [white] or [black]. */
3916 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3917 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3919 0 player 1 name (not necessarily white)
3921 2 empty, white, or black (IGNORED)
3922 3 player 2 name (not necessarily black)
3925 The names/ratings are sorted out when the game
3926 actually starts (below).
3928 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3929 player1Rating = string_to_rating(star_match[1]);
3930 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3931 player2Rating = string_to_rating(star_match[4]);
3933 if (appData.debugMode)
3935 "Ratings from 'Creating:' %s %d, %s %d\n",
3936 player1Name, player1Rating,
3937 player2Name, player2Rating);
3942 /* Improved generic start/end-of-game messages */
3943 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3944 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3945 /* If tkind == 0: */
3946 /* star_match[0] is the game number */
3947 /* [1] is the white player's name */
3948 /* [2] is the black player's name */
3949 /* For end-of-game: */
3950 /* [3] is the reason for the game end */
3951 /* [4] is a PGN end game-token, preceded by " " */
3952 /* For start-of-game: */
3953 /* [3] begins with "Creating" or "Continuing" */
3954 /* [4] is " *" or empty (don't care). */
3955 int gamenum = atoi(star_match[0]);
3956 char *whitename, *blackname, *why, *endtoken;
3957 ChessMove endtype = EndOfFile;
3960 whitename = star_match[1];
3961 blackname = star_match[2];
3962 why = star_match[3];
3963 endtoken = star_match[4];
3965 whitename = star_match[1];
3966 blackname = star_match[3];
3967 why = star_match[5];
3968 endtoken = star_match[6];
3971 /* Game start messages */
3972 if (strncmp(why, "Creating ", 9) == 0 ||
3973 strncmp(why, "Continuing ", 11) == 0) {
3974 gs_gamenum = gamenum;
3975 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3976 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3977 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3979 if (appData.zippyPlay) {
3980 ZippyGameStart(whitename, blackname);
3983 partnerBoardValid = FALSE; // [HGM] bughouse
3987 /* Game end messages */
3988 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3989 ics_gamenum != gamenum) {
3992 while (endtoken[0] == ' ') endtoken++;
3993 switch (endtoken[0]) {
3996 endtype = GameUnfinished;
3999 endtype = BlackWins;
4002 if (endtoken[1] == '/')
4003 endtype = GameIsDrawn;
4005 endtype = WhiteWins;
4008 GameEnds(endtype, why, GE_ICS);
4010 if (appData.zippyPlay && first.initDone) {
4011 ZippyGameEnd(endtype, why);
4012 if (first.pr == NoProc) {
4013 /* Start the next process early so that we'll
4014 be ready for the next challenge */
4015 StartChessProgram(&first);
4017 /* Send "new" early, in case this command takes
4018 a long time to finish, so that we'll be ready
4019 for the next challenge. */
4020 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4024 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4028 if (looking_at(buf, &i, "Removing game * from observation") ||
4029 looking_at(buf, &i, "no longer observing game *") ||
4030 looking_at(buf, &i, "Game * (*) has no examiners")) {
4031 if (gameMode == IcsObserving &&
4032 atoi(star_match[0]) == ics_gamenum)
4034 /* icsEngineAnalyze */
4035 if (appData.icsEngineAnalyze) {
4042 ics_user_moved = FALSE;
4047 if (looking_at(buf, &i, "no longer examining game *")) {
4048 if (gameMode == IcsExamining &&
4049 atoi(star_match[0]) == ics_gamenum)
4053 ics_user_moved = FALSE;
4058 /* Advance leftover_start past any newlines we find,
4059 so only partial lines can get reparsed */
4060 if (looking_at(buf, &i, "\n")) {
4061 prevColor = curColor;
4062 if (curColor != ColorNormal) {
4063 if (oldi > next_out) {
4064 SendToPlayer(&buf[next_out], oldi - next_out);
4067 Colorize(ColorNormal, FALSE);
4068 curColor = ColorNormal;
4070 if (started == STARTED_BOARD) {
4071 started = STARTED_NONE;
4072 parse[parse_pos] = NULLCHAR;
4073 ParseBoard12(parse);
4076 /* Send premove here */
4077 if (appData.premove) {
4079 if (currentMove == 0 &&
4080 gameMode == IcsPlayingWhite &&
4081 appData.premoveWhite) {
4082 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4083 if (appData.debugMode)
4084 fprintf(debugFP, "Sending premove:\n");
4086 } else if (currentMove == 1 &&
4087 gameMode == IcsPlayingBlack &&
4088 appData.premoveBlack) {
4089 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4090 if (appData.debugMode)
4091 fprintf(debugFP, "Sending premove:\n");
4093 } else if (gotPremove) {
4095 ClearPremoveHighlights();
4096 if (appData.debugMode)
4097 fprintf(debugFP, "Sending premove:\n");
4098 UserMoveEvent(premoveFromX, premoveFromY,
4099 premoveToX, premoveToY,
4104 /* Usually suppress following prompt */
4105 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4106 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4107 if (looking_at(buf, &i, "*% ")) {
4108 savingComment = FALSE;
4113 } else if (started == STARTED_HOLDINGS) {
4115 char new_piece[MSG_SIZ];
4116 started = STARTED_NONE;
4117 parse[parse_pos] = NULLCHAR;
4118 if (appData.debugMode)
4119 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4120 parse, currentMove);
4121 if (sscanf(parse, " game %d", &gamenum) == 1) {
4122 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4123 if (gameInfo.variant == VariantNormal) {
4124 /* [HGM] We seem to switch variant during a game!
4125 * Presumably no holdings were displayed, so we have
4126 * to move the position two files to the right to
4127 * create room for them!
4129 VariantClass newVariant;
4130 switch(gameInfo.boardWidth) { // base guess on board width
4131 case 9: newVariant = VariantShogi; break;
4132 case 10: newVariant = VariantGreat; break;
4133 default: newVariant = VariantCrazyhouse; break;
4135 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4136 /* Get a move list just to see the header, which
4137 will tell us whether this is really bug or zh */
4138 if (ics_getting_history == H_FALSE) {
4139 ics_getting_history = H_REQUESTED;
4140 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4144 new_piece[0] = NULLCHAR;
4145 sscanf(parse, "game %d white [%s black [%s <- %s",
4146 &gamenum, white_holding, black_holding,
4148 white_holding[strlen(white_holding)-1] = NULLCHAR;
4149 black_holding[strlen(black_holding)-1] = NULLCHAR;
4150 /* [HGM] copy holdings to board holdings area */
4151 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4152 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4153 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4155 if (appData.zippyPlay && first.initDone) {
4156 ZippyHoldings(white_holding, black_holding,
4160 if (tinyLayout || smallLayout) {
4161 char wh[16], bh[16];
4162 PackHolding(wh, white_holding);
4163 PackHolding(bh, black_holding);
4164 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4165 gameInfo.white, gameInfo.black);
4167 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4168 gameInfo.white, white_holding, _("vs."),
4169 gameInfo.black, black_holding);
4171 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4172 DrawPosition(FALSE, boards[currentMove]);
4174 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4175 sscanf(parse, "game %d white [%s black [%s <- %s",
4176 &gamenum, white_holding, black_holding,
4178 white_holding[strlen(white_holding)-1] = NULLCHAR;
4179 black_holding[strlen(black_holding)-1] = NULLCHAR;
4180 /* [HGM] copy holdings to partner-board holdings area */
4181 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4182 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4183 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4184 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4185 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4188 /* Suppress following prompt */
4189 if (looking_at(buf, &i, "*% ")) {
4190 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4191 savingComment = FALSE;
4199 i++; /* skip unparsed character and loop back */
4202 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4203 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4204 // SendToPlayer(&buf[next_out], i - next_out);
4205 started != STARTED_HOLDINGS && leftover_start > next_out) {
4206 SendToPlayer(&buf[next_out], leftover_start - next_out);
4210 leftover_len = buf_len - leftover_start;
4211 /* if buffer ends with something we couldn't parse,
4212 reparse it after appending the next read */
4214 } else if (count == 0) {
4215 RemoveInputSource(isr);
4216 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4218 DisplayFatalError(_("Error reading from ICS"), error, 1);
4223 /* Board style 12 looks like this:
4225 <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
4227 * The "<12> " is stripped before it gets to this routine. The two
4228 * trailing 0's (flip state and clock ticking) are later addition, and
4229 * some chess servers may not have them, or may have only the first.
4230 * Additional trailing fields may be added in the future.
4233 #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"
4235 #define RELATION_OBSERVING_PLAYED 0
4236 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4237 #define RELATION_PLAYING_MYMOVE 1
4238 #define RELATION_PLAYING_NOTMYMOVE -1
4239 #define RELATION_EXAMINING 2
4240 #define RELATION_ISOLATED_BOARD -3
4241 #define RELATION_STARTING_POSITION -4 /* FICS only */
4244 ParseBoard12 (char *string)
4248 char *bookHit = NULL; // [HGM] book
4250 GameMode newGameMode;
4251 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4252 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4253 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4254 char to_play, board_chars[200];
4255 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4256 char black[32], white[32];
4258 int prevMove = currentMove;
4261 int fromX, fromY, toX, toY;
4263 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4264 Boolean weird = FALSE, reqFlag = FALSE;
4266 fromX = fromY = toX = toY = -1;
4270 if (appData.debugMode)
4271 fprintf(debugFP, "Parsing board: %s\n", string);
4273 move_str[0] = NULLCHAR;
4274 elapsed_time[0] = NULLCHAR;
4275 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4277 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4278 if(string[i] == ' ') { ranks++; files = 0; }
4280 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4283 for(j = 0; j <i; j++) board_chars[j] = string[j];
4284 board_chars[i] = '\0';
4287 n = sscanf(string, PATTERN, &to_play, &double_push,
4288 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4289 &gamenum, white, black, &relation, &basetime, &increment,
4290 &white_stren, &black_stren, &white_time, &black_time,
4291 &moveNum, str, elapsed_time, move_str, &ics_flip,
4295 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4296 DisplayError(str, 0);
4300 /* Convert the move number to internal form */
4301 moveNum = (moveNum - 1) * 2;
4302 if (to_play == 'B') moveNum++;
4303 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4304 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4310 case RELATION_OBSERVING_PLAYED:
4311 case RELATION_OBSERVING_STATIC:
4312 if (gamenum == -1) {
4313 /* Old ICC buglet */
4314 relation = RELATION_OBSERVING_STATIC;
4316 newGameMode = IcsObserving;
4318 case RELATION_PLAYING_MYMOVE:
4319 case RELATION_PLAYING_NOTMYMOVE:
4321 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4322 IcsPlayingWhite : IcsPlayingBlack;
4323 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4325 case RELATION_EXAMINING:
4326 newGameMode = IcsExamining;
4328 case RELATION_ISOLATED_BOARD:
4330 /* Just display this board. If user was doing something else,
4331 we will forget about it until the next board comes. */
4332 newGameMode = IcsIdle;
4334 case RELATION_STARTING_POSITION:
4335 newGameMode = gameMode;
4339 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4340 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4341 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4342 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4343 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4344 static int lastBgGame = -1;
4346 for (k = 0; k < ranks; k++) {
4347 for (j = 0; j < files; j++)
4348 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4349 if(gameInfo.holdingsWidth > 1) {
4350 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4351 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4354 CopyBoard(partnerBoard, board);
4355 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4356 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4357 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4358 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4359 if(toSqr = strchr(str, '-')) {
4360 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4361 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4362 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4363 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4364 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4365 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4367 DisplayWhiteClock(white_time*fac, to_play == 'W');
4368 DisplayBlackClock(black_time*fac, to_play != 'W');
4369 activePartner = to_play;
4370 if(gamenum != lastBgGame) {
4372 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4375 lastBgGame = gamenum;
4376 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4377 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4378 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4379 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4380 if(!twoBoards) DisplayMessage(partnerStatus, "");
4381 partnerBoardValid = TRUE;
4385 if(appData.dualBoard && appData.bgObserve) {
4386 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4387 SendToICS(ics_prefix), SendToICS("pobserve\n");
4388 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4390 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4395 /* Modify behavior for initial board display on move listing
4398 switch (ics_getting_history) {
4402 case H_GOT_REQ_HEADER:
4403 case H_GOT_UNREQ_HEADER:
4404 /* This is the initial position of the current game */
4405 gamenum = ics_gamenum;
4406 moveNum = 0; /* old ICS bug workaround */
4407 if (to_play == 'B') {
4408 startedFromSetupPosition = TRUE;
4409 blackPlaysFirst = TRUE;
4411 if (forwardMostMove == 0) forwardMostMove = 1;
4412 if (backwardMostMove == 0) backwardMostMove = 1;
4413 if (currentMove == 0) currentMove = 1;
4415 newGameMode = gameMode;
4416 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4418 case H_GOT_UNWANTED_HEADER:
4419 /* This is an initial board that we don't want */
4421 case H_GETTING_MOVES:
4422 /* Should not happen */
4423 DisplayError(_("Error gathering move list: extra board"), 0);
4424 ics_getting_history = H_FALSE;
4428 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4429 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4430 weird && (int)gameInfo.variant < (int)VariantShogi) {
4431 /* [HGM] We seem to have switched variant unexpectedly
4432 * Try to guess new variant from board size
4434 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4435 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4436 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4437 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4438 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4439 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4440 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4441 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4442 /* Get a move list just to see the header, which
4443 will tell us whether this is really bug or zh */
4444 if (ics_getting_history == H_FALSE) {
4445 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4446 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4451 /* Take action if this is the first board of a new game, or of a
4452 different game than is currently being displayed. */
4453 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4454 relation == RELATION_ISOLATED_BOARD) {
4456 /* Forget the old game and get the history (if any) of the new one */
4457 if (gameMode != BeginningOfGame) {
4461 if (appData.autoRaiseBoard) BoardToTop();
4463 if (gamenum == -1) {
4464 newGameMode = IcsIdle;
4465 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4466 appData.getMoveList && !reqFlag) {
4467 /* Need to get game history */
4468 ics_getting_history = H_REQUESTED;
4469 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4473 /* Initially flip the board to have black on the bottom if playing
4474 black or if the ICS flip flag is set, but let the user change
4475 it with the Flip View button. */
4476 flipView = appData.autoFlipView ?
4477 (newGameMode == IcsPlayingBlack) || ics_flip :
4480 /* Done with values from previous mode; copy in new ones */
4481 gameMode = newGameMode;
4483 ics_gamenum = gamenum;
4484 if (gamenum == gs_gamenum) {
4485 int klen = strlen(gs_kind);
4486 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4487 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4488 gameInfo.event = StrSave(str);
4490 gameInfo.event = StrSave("ICS game");
4492 gameInfo.site = StrSave(appData.icsHost);
4493 gameInfo.date = PGNDate();
4494 gameInfo.round = StrSave("-");
4495 gameInfo.white = StrSave(white);
4496 gameInfo.black = StrSave(black);
4497 timeControl = basetime * 60 * 1000;
4499 timeIncrement = increment * 1000;
4500 movesPerSession = 0;
4501 gameInfo.timeControl = TimeControlTagValue();
4502 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4503 if (appData.debugMode) {
4504 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4505 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4506 setbuf(debugFP, NULL);
4509 gameInfo.outOfBook = NULL;
4511 /* Do we have the ratings? */
4512 if (strcmp(player1Name, white) == 0 &&
4513 strcmp(player2Name, black) == 0) {
4514 if (appData.debugMode)
4515 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4516 player1Rating, player2Rating);
4517 gameInfo.whiteRating = player1Rating;
4518 gameInfo.blackRating = player2Rating;
4519 } else if (strcmp(player2Name, white) == 0 &&
4520 strcmp(player1Name, black) == 0) {
4521 if (appData.debugMode)
4522 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4523 player2Rating, player1Rating);
4524 gameInfo.whiteRating = player2Rating;
4525 gameInfo.blackRating = player1Rating;
4527 player1Name[0] = player2Name[0] = NULLCHAR;
4529 /* Silence shouts if requested */
4530 if (appData.quietPlay &&
4531 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4532 SendToICS(ics_prefix);
4533 SendToICS("set shout 0\n");
4537 /* Deal with midgame name changes */
4539 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4540 if (gameInfo.white) free(gameInfo.white);
4541 gameInfo.white = StrSave(white);
4543 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4544 if (gameInfo.black) free(gameInfo.black);
4545 gameInfo.black = StrSave(black);
4549 /* Throw away game result if anything actually changes in examine mode */
4550 if (gameMode == IcsExamining && !newGame) {
4551 gameInfo.result = GameUnfinished;
4552 if (gameInfo.resultDetails != NULL) {
4553 free(gameInfo.resultDetails);
4554 gameInfo.resultDetails = NULL;
4558 /* In pausing && IcsExamining mode, we ignore boards coming
4559 in if they are in a different variation than we are. */
4560 if (pauseExamInvalid) return;
4561 if (pausing && gameMode == IcsExamining) {
4562 if (moveNum <= pauseExamForwardMostMove) {
4563 pauseExamInvalid = TRUE;
4564 forwardMostMove = pauseExamForwardMostMove;
4569 if (appData.debugMode) {
4570 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4572 /* Parse the board */
4573 for (k = 0; k < ranks; k++) {
4574 for (j = 0; j < files; j++)
4575 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4576 if(gameInfo.holdingsWidth > 1) {
4577 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4578 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4581 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4582 board[5][BOARD_RGHT+1] = WhiteAngel;
4583 board[6][BOARD_RGHT+1] = WhiteMarshall;
4584 board[1][0] = BlackMarshall;
4585 board[2][0] = BlackAngel;
4586 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4588 CopyBoard(boards[moveNum], board);
4589 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4591 startedFromSetupPosition =
4592 !CompareBoards(board, initialPosition);
4593 if(startedFromSetupPosition)
4594 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4597 /* [HGM] Set castling rights. Take the outermost Rooks,
4598 to make it also work for FRC opening positions. Note that board12
4599 is really defective for later FRC positions, as it has no way to
4600 indicate which Rook can castle if they are on the same side of King.
4601 For the initial position we grant rights to the outermost Rooks,
4602 and remember thos rights, and we then copy them on positions
4603 later in an FRC game. This means WB might not recognize castlings with
4604 Rooks that have moved back to their original position as illegal,
4605 but in ICS mode that is not its job anyway.
4607 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4608 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4610 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4611 if(board[0][i] == WhiteRook) j = i;
4612 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4613 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4614 if(board[0][i] == WhiteRook) j = i;
4615 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4616 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4617 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4618 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4619 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4620 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4621 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4623 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4624 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4625 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4626 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4627 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4628 if(board[BOARD_HEIGHT-1][k] == bKing)
4629 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4630 if(gameInfo.variant == VariantTwoKings) {
4631 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4632 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4633 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4636 r = boards[moveNum][CASTLING][0] = initialRights[0];
4637 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4638 r = boards[moveNum][CASTLING][1] = initialRights[1];
4639 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4640 r = boards[moveNum][CASTLING][3] = initialRights[3];
4641 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4642 r = boards[moveNum][CASTLING][4] = initialRights[4];
4643 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4644 /* wildcastle kludge: always assume King has rights */
4645 r = boards[moveNum][CASTLING][2] = initialRights[2];
4646 r = boards[moveNum][CASTLING][5] = initialRights[5];
4648 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4649 boards[moveNum][EP_STATUS] = EP_NONE;
4650 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4651 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4652 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4655 if (ics_getting_history == H_GOT_REQ_HEADER ||
4656 ics_getting_history == H_GOT_UNREQ_HEADER) {
4657 /* This was an initial position from a move list, not
4658 the current position */
4662 /* Update currentMove and known move number limits */
4663 newMove = newGame || moveNum > forwardMostMove;
4666 forwardMostMove = backwardMostMove = currentMove = moveNum;
4667 if (gameMode == IcsExamining && moveNum == 0) {
4668 /* Workaround for ICS limitation: we are not told the wild
4669 type when starting to examine a game. But if we ask for
4670 the move list, the move list header will tell us */
4671 ics_getting_history = H_REQUESTED;
4672 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4675 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4676 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4678 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4679 /* [HGM] applied this also to an engine that is silently watching */
4680 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4681 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4682 gameInfo.variant == currentlyInitializedVariant) {
4683 takeback = forwardMostMove - moveNum;
4684 for (i = 0; i < takeback; i++) {
4685 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4686 SendToProgram("undo\n", &first);
4691 forwardMostMove = moveNum;
4692 if (!pausing || currentMove > forwardMostMove)
4693 currentMove = forwardMostMove;
4695 /* New part of history that is not contiguous with old part */
4696 if (pausing && gameMode == IcsExamining) {
4697 pauseExamInvalid = TRUE;
4698 forwardMostMove = pauseExamForwardMostMove;
4701 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4703 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4704 // [HGM] when we will receive the move list we now request, it will be
4705 // fed to the engine from the first move on. So if the engine is not
4706 // in the initial position now, bring it there.
4707 InitChessProgram(&first, 0);
4710 ics_getting_history = H_REQUESTED;
4711 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4714 forwardMostMove = backwardMostMove = currentMove = moveNum;
4717 /* Update the clocks */
4718 if (strchr(elapsed_time, '.')) {
4720 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4721 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4723 /* Time is in seconds */
4724 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4725 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4730 if (appData.zippyPlay && newGame &&
4731 gameMode != IcsObserving && gameMode != IcsIdle &&
4732 gameMode != IcsExamining)
4733 ZippyFirstBoard(moveNum, basetime, increment);
4736 /* Put the move on the move list, first converting
4737 to canonical algebraic form. */
4739 if (appData.debugMode) {
4740 int f = forwardMostMove;
4741 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4742 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4743 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4744 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4745 fprintf(debugFP, "moveNum = %d\n", moveNum);
4746 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4747 setbuf(debugFP, NULL);
4749 if (moveNum <= backwardMostMove) {
4750 /* We don't know what the board looked like before
4752 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4753 strcat(parseList[moveNum - 1], " ");
4754 strcat(parseList[moveNum - 1], elapsed_time);
4755 moveList[moveNum - 1][0] = NULLCHAR;
4756 } else if (strcmp(move_str, "none") == 0) {
4757 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4758 /* Again, we don't know what the board looked like;
4759 this is really the start of the game. */
4760 parseList[moveNum - 1][0] = NULLCHAR;
4761 moveList[moveNum - 1][0] = NULLCHAR;
4762 backwardMostMove = moveNum;
4763 startedFromSetupPosition = TRUE;
4764 fromX = fromY = toX = toY = -1;
4766 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4767 // So we parse the long-algebraic move string in stead of the SAN move
4768 int valid; char buf[MSG_SIZ], *prom;
4770 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4771 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4772 // str looks something like "Q/a1-a2"; kill the slash
4774 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4775 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4776 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4777 strcat(buf, prom); // long move lacks promo specification!
4778 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4779 if(appData.debugMode)
4780 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4781 safeStrCpy(move_str, buf, MSG_SIZ);
4783 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4784 &fromX, &fromY, &toX, &toY, &promoChar)
4785 || ParseOneMove(buf, moveNum - 1, &moveType,
4786 &fromX, &fromY, &toX, &toY, &promoChar);
4787 // end of long SAN patch
4789 (void) CoordsToAlgebraic(boards[moveNum - 1],
4790 PosFlags(moveNum - 1),
4791 fromY, fromX, toY, toX, promoChar,
4792 parseList[moveNum-1]);
4793 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4799 if(gameInfo.variant != VariantShogi)
4800 strcat(parseList[moveNum - 1], "+");
4803 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4804 strcat(parseList[moveNum - 1], "#");
4807 strcat(parseList[moveNum - 1], " ");
4808 strcat(parseList[moveNum - 1], elapsed_time);
4809 /* currentMoveString is set as a side-effect of ParseOneMove */
4810 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4811 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4812 strcat(moveList[moveNum - 1], "\n");
4814 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4815 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4816 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4817 ChessSquare old, new = boards[moveNum][k][j];
4818 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4819 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4820 if(old == new) continue;
4821 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4822 else if(new == WhiteWazir || new == BlackWazir) {
4823 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4824 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4825 else boards[moveNum][k][j] = old; // preserve type of Gold
4826 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4827 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4830 /* Move from ICS was illegal!? Punt. */
4831 if (appData.debugMode) {
4832 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4833 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4835 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4836 strcat(parseList[moveNum - 1], " ");
4837 strcat(parseList[moveNum - 1], elapsed_time);
4838 moveList[moveNum - 1][0] = NULLCHAR;
4839 fromX = fromY = toX = toY = -1;
4842 if (appData.debugMode) {
4843 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4844 setbuf(debugFP, NULL);
4848 /* Send move to chess program (BEFORE animating it). */
4849 if (appData.zippyPlay && !newGame && newMove &&
4850 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4852 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4853 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4854 if (moveList[moveNum - 1][0] == NULLCHAR) {
4855 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4857 DisplayError(str, 0);
4859 if (first.sendTime) {
4860 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4862 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4863 if (firstMove && !bookHit) {
4865 if (first.useColors) {
4866 SendToProgram(gameMode == IcsPlayingWhite ?
4868 "black\ngo\n", &first);
4870 SendToProgram("go\n", &first);
4872 first.maybeThinking = TRUE;
4875 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4876 if (moveList[moveNum - 1][0] == NULLCHAR) {
4877 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4878 DisplayError(str, 0);
4880 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4881 SendMoveToProgram(moveNum - 1, &first);
4888 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4889 /* If move comes from a remote source, animate it. If it
4890 isn't remote, it will have already been animated. */
4891 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4892 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4894 if (!pausing && appData.highlightLastMove) {
4895 SetHighlights(fromX, fromY, toX, toY);
4899 /* Start the clocks */
4900 whiteFlag = blackFlag = FALSE;
4901 appData.clockMode = !(basetime == 0 && increment == 0);
4903 ics_clock_paused = TRUE;
4905 } else if (ticking == 1) {
4906 ics_clock_paused = FALSE;
4908 if (gameMode == IcsIdle ||
4909 relation == RELATION_OBSERVING_STATIC ||
4910 relation == RELATION_EXAMINING ||
4912 DisplayBothClocks();
4916 /* Display opponents and material strengths */
4917 if (gameInfo.variant != VariantBughouse &&
4918 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4919 if (tinyLayout || smallLayout) {
4920 if(gameInfo.variant == VariantNormal)
4921 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4922 gameInfo.white, white_stren, gameInfo.black, black_stren,
4923 basetime, increment);
4925 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4926 gameInfo.white, white_stren, gameInfo.black, black_stren,
4927 basetime, increment, (int) gameInfo.variant);
4929 if(gameInfo.variant == VariantNormal)
4930 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4931 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4932 basetime, increment);
4934 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4935 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4936 basetime, increment, VariantName(gameInfo.variant));
4939 if (appData.debugMode) {
4940 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4945 /* Display the board */
4946 if (!pausing && !appData.noGUI) {
4948 if (appData.premove)
4950 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4951 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4952 ClearPremoveHighlights();
4954 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4955 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4956 DrawPosition(j, boards[currentMove]);
4958 DisplayMove(moveNum - 1);
4959 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4960 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4961 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4962 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4966 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4968 if(bookHit) { // [HGM] book: simulate book reply
4969 static char bookMove[MSG_SIZ]; // a bit generous?
4971 programStats.nodes = programStats.depth = programStats.time =
4972 programStats.score = programStats.got_only_move = 0;
4973 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4975 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4976 strcat(bookMove, bookHit);
4977 HandleMachineMove(bookMove, &first);
4986 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4987 ics_getting_history = H_REQUESTED;
4988 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4994 SendToBoth (char *msg)
4995 { // to make it easy to keep two engines in step in dual analysis
4996 SendToProgram(msg, &first);
4997 if(second.analyzing) SendToProgram(msg, &second);
5001 AnalysisPeriodicEvent (int force)
5003 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5004 && !force) || !appData.periodicUpdates)
5007 /* Send . command to Crafty to collect stats */
5010 /* Don't send another until we get a response (this makes
5011 us stop sending to old Crafty's which don't understand
5012 the "." command (sending illegal cmds resets node count & time,
5013 which looks bad)) */
5014 programStats.ok_to_send = 0;
5018 ics_update_width (int new_width)
5020 ics_printf("set width %d\n", new_width);
5024 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5028 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5029 if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5030 sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5031 SendToProgram(buf, cps);
5034 // null move in variant where engine does not understand it (for analysis purposes)
5035 SendBoard(cps, moveNum + 1); // send position after move in stead.
5038 if (cps->useUsermove) {
5039 SendToProgram("usermove ", cps);
5043 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5044 int len = space - parseList[moveNum];
5045 memcpy(buf, parseList[moveNum], len);
5047 buf[len] = NULLCHAR;
5049 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5051 SendToProgram(buf, cps);
5053 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5054 AlphaRank(moveList[moveNum], 4);
5055 SendToProgram(moveList[moveNum], cps);
5056 AlphaRank(moveList[moveNum], 4); // and back
5058 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5059 * the engine. It would be nice to have a better way to identify castle
5061 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5062 && cps->useOOCastle) {
5063 int fromX = moveList[moveNum][0] - AAA;
5064 int fromY = moveList[moveNum][1] - ONE;
5065 int toX = moveList[moveNum][2] - AAA;
5066 int toY = moveList[moveNum][3] - ONE;
5067 if((boards[moveNum][fromY][fromX] == WhiteKing
5068 && boards[moveNum][toY][toX] == WhiteRook)
5069 || (boards[moveNum][fromY][fromX] == BlackKing
5070 && boards[moveNum][toY][toX] == BlackRook)) {
5071 if(toX > fromX) SendToProgram("O-O\n", cps);
5072 else SendToProgram("O-O-O\n", cps);
5074 else SendToProgram(moveList[moveNum], cps);
5076 if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5077 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5078 moveList[moveNum][5], moveList[moveNum][6] - '0',
5079 moveList[moveNum][5], moveList[moveNum][6] - '0',
5080 moveList[moveNum][2], moveList[moveNum][3] - '0');
5081 SendToProgram(buf, cps);
5083 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5084 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5085 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5086 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5087 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5089 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5090 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5091 SendToProgram(buf, cps);
5093 else SendToProgram(moveList[moveNum], cps);
5094 /* End of additions by Tord */
5097 /* [HGM] setting up the opening has brought engine in force mode! */
5098 /* Send 'go' if we are in a mode where machine should play. */
5099 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5100 (gameMode == TwoMachinesPlay ||
5102 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5104 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5105 SendToProgram("go\n", cps);
5106 if (appData.debugMode) {
5107 fprintf(debugFP, "(extra)\n");
5110 setboardSpoiledMachineBlack = 0;
5114 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5116 char user_move[MSG_SIZ];
5119 if(gameInfo.variant == VariantSChess && promoChar) {
5120 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5121 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5122 } else suffix[0] = NULLCHAR;
5126 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5127 (int)moveType, fromX, fromY, toX, toY);
5128 DisplayError(user_move + strlen("say "), 0);
5130 case WhiteKingSideCastle:
5131 case BlackKingSideCastle:
5132 case WhiteQueenSideCastleWild:
5133 case BlackQueenSideCastleWild:
5135 case WhiteHSideCastleFR:
5136 case BlackHSideCastleFR:
5138 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5140 case WhiteQueenSideCastle:
5141 case BlackQueenSideCastle:
5142 case WhiteKingSideCastleWild:
5143 case BlackKingSideCastleWild:
5145 case WhiteASideCastleFR:
5146 case BlackASideCastleFR:
5148 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5150 case WhiteNonPromotion:
5151 case BlackNonPromotion:
5152 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5154 case WhitePromotion:
5155 case BlackPromotion:
5156 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5157 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5158 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5159 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5160 PieceToChar(WhiteFerz));
5161 else if(gameInfo.variant == VariantGreat)
5162 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5163 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5164 PieceToChar(WhiteMan));
5166 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5167 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5173 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5174 ToUpper(PieceToChar((ChessSquare) fromX)),
5175 AAA + toX, ONE + toY);
5177 case IllegalMove: /* could be a variant we don't quite understand */
5178 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5180 case WhiteCapturesEnPassant:
5181 case BlackCapturesEnPassant:
5182 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5183 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5186 SendToICS(user_move);
5187 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5188 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5193 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5194 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5195 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5196 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5197 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5200 if(gameMode != IcsExamining) { // is this ever not the case?
5201 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5203 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5204 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5205 } else { // on FICS we must first go to general examine mode
5206 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5208 if(gameInfo.variant != VariantNormal) {
5209 // try figure out wild number, as xboard names are not always valid on ICS
5210 for(i=1; i<=36; i++) {
5211 snprintf(buf, MSG_SIZ, "wild/%d", i);
5212 if(StringToVariant(buf) == gameInfo.variant) break;
5214 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5215 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5216 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5217 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5218 SendToICS(ics_prefix);
5220 if(startedFromSetupPosition || backwardMostMove != 0) {
5221 fen = PositionToFEN(backwardMostMove, NULL, 1);
5222 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5223 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5225 } else { // FICS: everything has to set by separate bsetup commands
5226 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5227 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5229 if(!WhiteOnMove(backwardMostMove)) {
5230 SendToICS("bsetup tomove black\n");
5232 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5233 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5235 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5236 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5238 i = boards[backwardMostMove][EP_STATUS];
5239 if(i >= 0) { // set e.p.
5240 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5246 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5247 SendToICS("bsetup done\n"); // switch to normal examining.
5249 for(i = backwardMostMove; i<last; i++) {
5251 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5252 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5253 int len = strlen(moveList[i]);
5254 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5255 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5259 SendToICS(ics_prefix);
5260 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5263 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5266 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5268 if (rf == DROP_RANK) {
5269 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5270 sprintf(move, "%c@%c%c\n",
5271 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5273 if (promoChar == 'x' || promoChar == NULLCHAR) {
5274 sprintf(move, "%c%c%c%c\n",
5275 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5276 if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5278 sprintf(move, "%c%c%c%c%c\n",
5279 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5285 ProcessICSInitScript (FILE *f)
5289 while (fgets(buf, MSG_SIZ, f)) {
5290 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5297 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5299 static ClickType lastClickType;
5302 Partner (ChessSquare *p)
5303 { // change piece into promotion partner if one shogi-promotes to the other
5304 int stride = gameInfo.variant == VariantChu ? 22 : 11;
5305 ChessSquare partner;
5306 partner = (*p/stride & 1 ? *p - stride : *p + stride);
5307 if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5315 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5316 static int toggleFlag;
5317 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5318 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5319 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5320 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5321 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5322 if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5324 if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5325 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5326 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5327 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5328 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5329 if(!step) step = -1;
5330 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5331 appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion));
5333 int victim = boards[currentMove][toY][toX];
5334 boards[currentMove][toY][toX] = promoSweep;
5335 DrawPosition(FALSE, boards[currentMove]);
5336 boards[currentMove][toY][toX] = victim;
5338 ChangeDragPiece(promoSweep);
5342 PromoScroll (int x, int y)
5346 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5347 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5348 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5349 if(!step) return FALSE;
5350 lastX = x; lastY = y;
5351 if((promoSweep < BlackPawn) == flipView) step = -step;
5352 if(step > 0) selectFlag = 1;
5353 if(!selectFlag) Sweep(step);
5358 NextPiece (int step)
5360 ChessSquare piece = boards[currentMove][toY][toX];
5363 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5364 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5365 if(!step) step = -1;
5366 } while(PieceToChar(pieceSweep) == '.');
5367 boards[currentMove][toY][toX] = pieceSweep;
5368 DrawPosition(FALSE, boards[currentMove]);
5369 boards[currentMove][toY][toX] = piece;
5371 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5373 AlphaRank (char *move, int n)
5375 // char *p = move, c; int x, y;
5377 if (appData.debugMode) {
5378 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5382 move[2]>='0' && move[2]<='9' &&
5383 move[3]>='a' && move[3]<='x' ) {
5385 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5386 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5388 if(move[0]>='0' && move[0]<='9' &&
5389 move[1]>='a' && move[1]<='x' &&
5390 move[2]>='0' && move[2]<='9' &&
5391 move[3]>='a' && move[3]<='x' ) {
5392 /* input move, Shogi -> normal */
5393 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5394 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5395 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5396 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5399 move[3]>='0' && move[3]<='9' &&
5400 move[2]>='a' && move[2]<='x' ) {
5402 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5403 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5406 move[0]>='a' && move[0]<='x' &&
5407 move[3]>='0' && move[3]<='9' &&
5408 move[2]>='a' && move[2]<='x' ) {
5409 /* output move, normal -> Shogi */
5410 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5411 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5412 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5413 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5414 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5416 if (appData.debugMode) {
5417 fprintf(debugFP, " out = '%s'\n", move);
5421 char yy_textstr[8000];
5423 /* Parser for moves from gnuchess, ICS, or user typein box */
5425 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5427 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5429 switch (*moveType) {
5430 case WhitePromotion:
5431 case BlackPromotion:
5432 case WhiteNonPromotion:
5433 case BlackNonPromotion:
5436 case WhiteCapturesEnPassant:
5437 case BlackCapturesEnPassant:
5438 case WhiteKingSideCastle:
5439 case WhiteQueenSideCastle:
5440 case BlackKingSideCastle:
5441 case BlackQueenSideCastle:
5442 case WhiteKingSideCastleWild:
5443 case WhiteQueenSideCastleWild:
5444 case BlackKingSideCastleWild:
5445 case BlackQueenSideCastleWild:
5446 /* Code added by Tord: */
5447 case WhiteHSideCastleFR:
5448 case WhiteASideCastleFR:
5449 case BlackHSideCastleFR:
5450 case BlackASideCastleFR:
5451 /* End of code added by Tord */
5452 case IllegalMove: /* bug or odd chess variant */
5453 *fromX = currentMoveString[0] - AAA;
5454 *fromY = currentMoveString[1] - ONE;
5455 *toX = currentMoveString[2] - AAA;
5456 *toY = currentMoveString[3] - ONE;
5457 *promoChar = currentMoveString[4];
5458 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5459 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5460 if (appData.debugMode) {
5461 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5463 *fromX = *fromY = *toX = *toY = 0;
5466 if (appData.testLegality) {
5467 return (*moveType != IllegalMove);
5469 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5470 // [HGM] lion: if this is a double move we are less critical
5471 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5476 *fromX = *moveType == WhiteDrop ?
5477 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5478 (int) CharToPiece(ToLower(currentMoveString[0]));
5480 *toX = currentMoveString[2] - AAA;
5481 *toY = currentMoveString[3] - ONE;
5482 *promoChar = NULLCHAR;
5486 case ImpossibleMove:
5496 if (appData.debugMode) {
5497 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5500 *fromX = *fromY = *toX = *toY = 0;
5501 *promoChar = NULLCHAR;
5506 Boolean pushed = FALSE;
5507 char *lastParseAttempt;
5510 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5511 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5512 int fromX, fromY, toX, toY; char promoChar;
5517 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5518 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5519 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5522 endPV = forwardMostMove;
5524 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5525 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5526 lastParseAttempt = pv;
5527 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5528 if(!valid && nr == 0 &&
5529 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5530 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5531 // Hande case where played move is different from leading PV move
5532 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5533 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5534 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5535 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5536 endPV += 2; // if position different, keep this
5537 moveList[endPV-1][0] = fromX + AAA;
5538 moveList[endPV-1][1] = fromY + ONE;
5539 moveList[endPV-1][2] = toX + AAA;
5540 moveList[endPV-1][3] = toY + ONE;
5541 parseList[endPV-1][0] = NULLCHAR;
5542 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5545 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5546 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5547 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5548 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5549 valid++; // allow comments in PV
5553 if(endPV+1 > framePtr) break; // no space, truncate
5556 CopyBoard(boards[endPV], boards[endPV-1]);
5557 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5558 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5559 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5560 CoordsToAlgebraic(boards[endPV - 1],
5561 PosFlags(endPV - 1),
5562 fromY, fromX, toY, toX, promoChar,
5563 parseList[endPV - 1]);
5565 if(atEnd == 2) return; // used hidden, for PV conversion
5566 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5567 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5568 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5569 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5570 DrawPosition(TRUE, boards[currentMove]);
5574 MultiPV (ChessProgramState *cps)
5575 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5577 for(i=0; i<cps->nrOptions; i++)
5578 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5583 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5586 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5588 int startPV, multi, lineStart, origIndex = index;
5589 char *p, buf2[MSG_SIZ];
5590 ChessProgramState *cps = (pane ? &second : &first);
5592 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5593 lastX = x; lastY = y;
5594 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5595 lineStart = startPV = index;
5596 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5597 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5599 do{ while(buf[index] && buf[index] != '\n') index++;
5600 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5602 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5603 int n = cps->option[multi].value;
5604 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5605 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5606 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5607 cps->option[multi].value = n;
5610 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5611 ExcludeClick(origIndex - lineStart);
5614 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5615 *start = startPV; *end = index-1;
5616 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5623 static char buf[10*MSG_SIZ];
5624 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5626 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5627 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5628 for(i = forwardMostMove; i<endPV; i++){
5629 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5630 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5633 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5634 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5635 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5641 LoadPV (int x, int y)
5642 { // called on right mouse click to load PV
5643 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5644 lastX = x; lastY = y;
5645 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5653 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5654 if(endPV < 0) return;
5655 if(appData.autoCopyPV) CopyFENToClipboard();
5657 if(extendGame && currentMove > forwardMostMove) {
5658 Boolean saveAnimate = appData.animate;
5660 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5661 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5662 } else storedGames--; // abandon shelved tail of original game
5665 forwardMostMove = currentMove;
5666 currentMove = oldFMM;
5667 appData.animate = FALSE;
5668 ToNrEvent(forwardMostMove);
5669 appData.animate = saveAnimate;
5671 currentMove = forwardMostMove;
5672 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5673 ClearPremoveHighlights();
5674 DrawPosition(TRUE, boards[currentMove]);
5678 MovePV (int x, int y, int h)
5679 { // step through PV based on mouse coordinates (called on mouse move)
5680 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5682 // we must somehow check if right button is still down (might be released off board!)
5683 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5684 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5685 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5687 lastX = x; lastY = y;
5689 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5690 if(endPV < 0) return;
5691 if(y < margin) step = 1; else
5692 if(y > h - margin) step = -1;
5693 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5694 currentMove += step;
5695 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5696 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5697 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5698 DrawPosition(FALSE, boards[currentMove]);
5702 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5703 // All positions will have equal probability, but the current method will not provide a unique
5704 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5710 int piecesLeft[(int)BlackPawn];
5711 int seed, nrOfShuffles;
5714 GetPositionNumber ()
5715 { // sets global variable seed
5718 seed = appData.defaultFrcPosition;
5719 if(seed < 0) { // randomize based on time for negative FRC position numbers
5720 for(i=0; i<50; i++) seed += random();
5721 seed = random() ^ random() >> 8 ^ random() << 8;
5722 if(seed<0) seed = -seed;
5727 put (Board board, int pieceType, int rank, int n, int shade)
5728 // put the piece on the (n-1)-th empty squares of the given shade
5732 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5733 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5734 board[rank][i] = (ChessSquare) pieceType;
5735 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5737 piecesLeft[pieceType]--;
5746 AddOnePiece (Board board, int pieceType, int rank, int shade)
5747 // calculate where the next piece goes, (any empty square), and put it there
5751 i = seed % squaresLeft[shade];
5752 nrOfShuffles *= squaresLeft[shade];
5753 seed /= squaresLeft[shade];
5754 put(board, pieceType, rank, i, shade);
5758 AddTwoPieces (Board board, int pieceType, int rank)
5759 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5761 int i, n=squaresLeft[ANY], j=n-1, k;
5763 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5764 i = seed % k; // pick one
5767 while(i >= j) i -= j--;
5768 j = n - 1 - j; i += j;
5769 put(board, pieceType, rank, j, ANY);
5770 put(board, pieceType, rank, i, ANY);
5774 SetUpShuffle (Board board, int number)
5778 GetPositionNumber(); nrOfShuffles = 1;
5780 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5781 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5782 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5784 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5786 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5787 p = (int) board[0][i];
5788 if(p < (int) BlackPawn) piecesLeft[p] ++;
5789 board[0][i] = EmptySquare;
5792 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5793 // shuffles restricted to allow normal castling put KRR first
5794 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5795 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5796 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5797 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5798 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5799 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5800 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5801 put(board, WhiteRook, 0, 0, ANY);
5802 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5805 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5806 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5807 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5808 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5809 while(piecesLeft[p] >= 2) {
5810 AddOnePiece(board, p, 0, LITE);
5811 AddOnePiece(board, p, 0, DARK);
5813 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5816 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5817 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5818 // but we leave King and Rooks for last, to possibly obey FRC restriction
5819 if(p == (int)WhiteRook) continue;
5820 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5821 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5824 // now everything is placed, except perhaps King (Unicorn) and Rooks
5826 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5827 // Last King gets castling rights
5828 while(piecesLeft[(int)WhiteUnicorn]) {
5829 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5830 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5833 while(piecesLeft[(int)WhiteKing]) {
5834 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5835 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5840 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5841 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5844 // Only Rooks can be left; simply place them all
5845 while(piecesLeft[(int)WhiteRook]) {
5846 i = put(board, WhiteRook, 0, 0, ANY);
5847 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5850 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5852 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5855 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5856 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5859 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5863 SetCharTable (char *table, const char * map)
5864 /* [HGM] moved here from winboard.c because of its general usefulness */
5865 /* Basically a safe strcpy that uses the last character as King */
5867 int result = FALSE; int NrPieces;
5869 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5870 && NrPieces >= 12 && !(NrPieces&1)) {
5871 int i; /* [HGM] Accept even length from 12 to 34 */
5873 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5874 for( i=0; i<NrPieces/2-1; i++ ) {
5876 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5878 table[(int) WhiteKing] = map[NrPieces/2-1];
5879 table[(int) BlackKing] = map[NrPieces-1];
5888 Prelude (Board board)
5889 { // [HGM] superchess: random selection of exo-pieces
5890 int i, j, k; ChessSquare p;
5891 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5893 GetPositionNumber(); // use FRC position number
5895 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5896 SetCharTable(pieceToChar, appData.pieceToCharTable);
5897 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5898 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5901 j = seed%4; seed /= 4;
5902 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5903 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5904 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5905 j = seed%3 + (seed%3 >= j); seed /= 3;
5906 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5907 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5908 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5909 j = seed%3; seed /= 3;
5910 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5911 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5912 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5913 j = seed%2 + (seed%2 >= j); seed /= 2;
5914 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5915 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5916 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5917 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5918 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5919 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5920 put(board, exoPieces[0], 0, 0, ANY);
5921 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5925 InitPosition (int redraw)
5927 ChessSquare (* pieces)[BOARD_FILES];
5928 int i, j, pawnRow=1, pieceRows=1, overrule,
5929 oldx = gameInfo.boardWidth,
5930 oldy = gameInfo.boardHeight,
5931 oldh = gameInfo.holdingsWidth;
5934 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5936 /* [AS] Initialize pv info list [HGM] and game status */
5938 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5939 pvInfoList[i].depth = 0;
5940 boards[i][EP_STATUS] = EP_NONE;
5941 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5944 initialRulePlies = 0; /* 50-move counter start */
5946 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5947 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5951 /* [HGM] logic here is completely changed. In stead of full positions */
5952 /* the initialized data only consist of the two backranks. The switch */
5953 /* selects which one we will use, which is than copied to the Board */
5954 /* initialPosition, which for the rest is initialized by Pawns and */
5955 /* empty squares. This initial position is then copied to boards[0], */
5956 /* possibly after shuffling, so that it remains available. */
5958 gameInfo.holdingsWidth = 0; /* default board sizes */
5959 gameInfo.boardWidth = 8;
5960 gameInfo.boardHeight = 8;
5961 gameInfo.holdingsSize = 0;
5962 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5963 for(i=0; i<BOARD_FILES-2; i++)
5964 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5965 initialPosition[EP_STATUS] = EP_NONE;
5966 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5967 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5968 SetCharTable(pieceNickName, appData.pieceNickNames);
5969 else SetCharTable(pieceNickName, "............");
5972 switch (gameInfo.variant) {
5973 case VariantFischeRandom:
5974 shuffleOpenings = TRUE;
5977 case VariantShatranj:
5978 pieces = ShatranjArray;
5979 nrCastlingRights = 0;
5980 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5983 pieces = makrukArray;
5984 nrCastlingRights = 0;
5985 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5988 pieces = aseanArray;
5989 nrCastlingRights = 0;
5990 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5992 case VariantTwoKings:
5993 pieces = twoKingsArray;
5996 pieces = GrandArray;
5997 nrCastlingRights = 0;
5998 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5999 gameInfo.boardWidth = 10;
6000 gameInfo.boardHeight = 10;
6001 gameInfo.holdingsSize = 7;
6003 case VariantCapaRandom:
6004 shuffleOpenings = TRUE;
6005 case VariantCapablanca:
6006 pieces = CapablancaArray;
6007 gameInfo.boardWidth = 10;
6008 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6011 pieces = GothicArray;
6012 gameInfo.boardWidth = 10;
6013 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6016 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6017 gameInfo.holdingsSize = 7;
6018 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6021 pieces = JanusArray;
6022 gameInfo.boardWidth = 10;
6023 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6024 nrCastlingRights = 6;
6025 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6026 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6027 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6028 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6029 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6030 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6033 pieces = FalconArray;
6034 gameInfo.boardWidth = 10;
6035 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6037 case VariantXiangqi:
6038 pieces = XiangqiArray;
6039 gameInfo.boardWidth = 9;
6040 gameInfo.boardHeight = 10;
6041 nrCastlingRights = 0;
6042 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6045 pieces = ShogiArray;
6046 gameInfo.boardWidth = 9;
6047 gameInfo.boardHeight = 9;
6048 gameInfo.holdingsSize = 7;
6049 nrCastlingRights = 0;
6050 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6053 pieces = ChuArray; pieceRows = 3;
6054 gameInfo.boardWidth = 12;
6055 gameInfo.boardHeight = 12;
6056 nrCastlingRights = 0;
6057 SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6058 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6060 case VariantCourier:
6061 pieces = CourierArray;
6062 gameInfo.boardWidth = 12;
6063 nrCastlingRights = 0;
6064 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6066 case VariantKnightmate:
6067 pieces = KnightmateArray;
6068 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6070 case VariantSpartan:
6071 pieces = SpartanArray;
6072 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6076 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6078 case VariantChuChess:
6079 pieces = ChuChessArray;
6080 gameInfo.boardWidth = 10;
6081 gameInfo.boardHeight = 10;
6082 SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6085 pieces = fairyArray;
6086 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6089 pieces = GreatArray;
6090 gameInfo.boardWidth = 10;
6091 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6092 gameInfo.holdingsSize = 8;
6096 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6097 gameInfo.holdingsSize = 8;
6098 startedFromSetupPosition = TRUE;
6100 case VariantCrazyhouse:
6101 case VariantBughouse:
6103 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6104 gameInfo.holdingsSize = 5;
6106 case VariantWildCastle:
6108 /* !!?shuffle with kings guaranteed to be on d or e file */
6109 shuffleOpenings = 1;
6111 case VariantNoCastle:
6113 nrCastlingRights = 0;
6114 /* !!?unconstrained back-rank shuffle */
6115 shuffleOpenings = 1;
6120 if(appData.NrFiles >= 0) {
6121 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6122 gameInfo.boardWidth = appData.NrFiles;
6124 if(appData.NrRanks >= 0) {
6125 gameInfo.boardHeight = appData.NrRanks;
6127 if(appData.holdingsSize >= 0) {
6128 i = appData.holdingsSize;
6129 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6130 gameInfo.holdingsSize = i;
6132 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6133 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6134 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6136 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6137 if(pawnRow < 1) pawnRow = 1;
6138 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6139 gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6140 if(gameInfo.variant == VariantChu) pawnRow = 3;
6142 /* User pieceToChar list overrules defaults */
6143 if(appData.pieceToCharTable != NULL)
6144 SetCharTable(pieceToChar, appData.pieceToCharTable);
6146 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6148 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6149 s = (ChessSquare) 0; /* account holding counts in guard band */
6150 for( i=0; i<BOARD_HEIGHT; i++ )
6151 initialPosition[i][j] = s;
6153 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6154 initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6155 initialPosition[pawnRow][j] = WhitePawn;
6156 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6157 if(gameInfo.variant == VariantXiangqi) {
6159 initialPosition[pawnRow][j] =
6160 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6161 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6162 initialPosition[2][j] = WhiteCannon;
6163 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6167 if(gameInfo.variant == VariantChu) {
6168 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6169 initialPosition[pawnRow+1][j] = WhiteCobra,
6170 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6171 for(i=1; i<pieceRows; i++) {
6172 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6173 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6176 if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6177 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6178 initialPosition[0][j] = WhiteRook;
6179 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6182 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth];
6184 if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6185 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6188 initialPosition[1][j] = WhiteBishop;
6189 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6191 initialPosition[1][j] = WhiteRook;
6192 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6195 if( nrCastlingRights == -1) {
6196 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6197 /* This sets default castling rights from none to normal corners */
6198 /* Variants with other castling rights must set them themselves above */
6199 nrCastlingRights = 6;
6201 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6202 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6203 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6204 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6205 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6206 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6209 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6210 if(gameInfo.variant == VariantGreat) { // promotion commoners
6211 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6212 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6213 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6214 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6216 if( gameInfo.variant == VariantSChess ) {
6217 initialPosition[1][0] = BlackMarshall;
6218 initialPosition[2][0] = BlackAngel;
6219 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6220 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6221 initialPosition[1][1] = initialPosition[2][1] =
6222 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6224 if (appData.debugMode) {
6225 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6227 if(shuffleOpenings) {
6228 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6229 startedFromSetupPosition = TRUE;
6231 if(startedFromPositionFile) {
6232 /* [HGM] loadPos: use PositionFile for every new game */
6233 CopyBoard(initialPosition, filePosition);
6234 for(i=0; i<nrCastlingRights; i++)
6235 initialRights[i] = filePosition[CASTLING][i];
6236 startedFromSetupPosition = TRUE;
6239 CopyBoard(boards[0], initialPosition);
6241 if(oldx != gameInfo.boardWidth ||
6242 oldy != gameInfo.boardHeight ||
6243 oldv != gameInfo.variant ||
6244 oldh != gameInfo.holdingsWidth
6246 InitDrawingSizes(-2 ,0);
6248 oldv = gameInfo.variant;
6250 DrawPosition(TRUE, boards[currentMove]);
6254 SendBoard (ChessProgramState *cps, int moveNum)
6256 char message[MSG_SIZ];
6258 if (cps->useSetboard) {
6259 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6260 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6261 SendToProgram(message, cps);
6266 int i, j, left=0, right=BOARD_WIDTH;
6267 /* Kludge to set black to move, avoiding the troublesome and now
6268 * deprecated "black" command.
6270 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6271 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6273 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6275 SendToProgram("edit\n", cps);
6276 SendToProgram("#\n", cps);
6277 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6278 bp = &boards[moveNum][i][left];
6279 for (j = left; j < right; j++, bp++) {
6280 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6281 if ((int) *bp < (int) BlackPawn) {
6282 if(j == BOARD_RGHT+1)
6283 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6284 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6285 if(message[0] == '+' || message[0] == '~') {
6286 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6287 PieceToChar((ChessSquare)(DEMOTED *bp)),
6290 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6291 message[1] = BOARD_RGHT - 1 - j + '1';
6292 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6294 SendToProgram(message, cps);
6299 SendToProgram("c\n", cps);
6300 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6301 bp = &boards[moveNum][i][left];
6302 for (j = left; j < right; j++, bp++) {
6303 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6304 if (((int) *bp != (int) EmptySquare)
6305 && ((int) *bp >= (int) BlackPawn)) {
6306 if(j == BOARD_LEFT-2)
6307 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6308 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6310 if(message[0] == '+' || message[0] == '~') {
6311 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6312 PieceToChar((ChessSquare)(DEMOTED *bp)),
6315 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6316 message[1] = BOARD_RGHT - 1 - j + '1';
6317 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6319 SendToProgram(message, cps);
6324 SendToProgram(".\n", cps);
6326 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6329 char exclusionHeader[MSG_SIZ];
6330 int exCnt, excludePtr;
6331 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6332 static Exclusion excluTab[200];
6333 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6339 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6340 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6346 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6347 excludePtr = 24; exCnt = 0;
6352 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6353 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6354 char buf[2*MOVE_LEN], *p;
6355 Exclusion *e = excluTab;
6357 for(i=0; i<exCnt; i++)
6358 if(e[i].ff == fromX && e[i].fr == fromY &&
6359 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6360 if(i == exCnt) { // was not in exclude list; add it
6361 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6362 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6363 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6366 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6367 excludePtr++; e[i].mark = excludePtr++;
6368 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6371 exclusionHeader[e[i].mark] = state;
6375 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6376 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6380 if((signed char)promoChar == -1) { // kludge to indicate best move
6381 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6382 return 1; // if unparsable, abort
6384 // update exclusion map (resolving toggle by consulting existing state)
6385 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6387 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6388 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6389 excludeMap[k] |= 1<<j;
6390 else excludeMap[k] &= ~(1<<j);
6392 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6394 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6395 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6397 return (state == '+');
6401 ExcludeClick (int index)
6404 Exclusion *e = excluTab;
6405 if(index < 25) { // none, best or tail clicked
6406 if(index < 13) { // none: include all
6407 WriteMap(0); // clear map
6408 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6409 SendToBoth("include all\n"); // and inform engine
6410 } else if(index > 18) { // tail
6411 if(exclusionHeader[19] == '-') { // tail was excluded
6412 SendToBoth("include all\n");
6413 WriteMap(0); // clear map completely
6414 // now re-exclude selected moves
6415 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6416 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6417 } else { // tail was included or in mixed state
6418 SendToBoth("exclude all\n");
6419 WriteMap(0xFF); // fill map completely
6420 // now re-include selected moves
6421 j = 0; // count them
6422 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6423 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6424 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6427 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6430 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6431 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6432 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6439 DefaultPromoChoice (int white)
6442 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6443 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6444 result = WhiteFerz; // no choice
6445 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6446 result= WhiteKing; // in Suicide Q is the last thing we want
6447 else if(gameInfo.variant == VariantSpartan)
6448 result = white ? WhiteQueen : WhiteAngel;
6449 else result = WhiteQueen;
6450 if(!white) result = WHITE_TO_BLACK result;
6454 static int autoQueen; // [HGM] oneclick
6457 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6459 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6460 /* [HGM] add Shogi promotions */
6461 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6462 ChessSquare piece, partner;
6466 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6467 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6469 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6470 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6473 piece = boards[currentMove][fromY][fromX];
6474 if(gameInfo.variant == VariantChu) {
6475 int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6476 promotionZoneSize = BOARD_HEIGHT/3;
6477 highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6478 } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6479 promotionZoneSize = BOARD_HEIGHT/3;
6480 highestPromotingPiece = (int)WhiteAlfil;
6481 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6482 promotionZoneSize = 3;
6485 // Treat Lance as Pawn when it is not representing Amazon
6486 if(gameInfo.variant != VariantSuper) {
6487 if(piece == WhiteLance) piece = WhitePawn; else
6488 if(piece == BlackLance) piece = BlackPawn;
6491 // next weed out all moves that do not touch the promotion zone at all
6492 if((int)piece >= BlackPawn) {
6493 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6495 if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6496 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6498 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6499 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6500 if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6504 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6506 // weed out mandatory Shogi promotions
6507 if(gameInfo.variant == VariantShogi) {
6508 if(piece >= BlackPawn) {
6509 if(toY == 0 && piece == BlackPawn ||
6510 toY == 0 && piece == BlackQueen ||
6511 toY <= 1 && piece == BlackKnight) {
6516 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6517 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6518 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6525 // weed out obviously illegal Pawn moves
6526 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6527 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6528 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6529 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6530 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6531 // note we are not allowed to test for valid (non-)capture, due to premove
6534 // we either have a choice what to promote to, or (in Shogi) whether to promote
6535 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6536 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6537 *promoChoice = PieceToChar(BlackFerz); // no choice
6540 // no sense asking what we must promote to if it is going to explode...
6541 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6542 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6545 // give caller the default choice even if we will not make it
6546 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6547 partner = piece; // pieces can promote if the pieceToCharTable says so
6548 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6549 else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6550 if( sweepSelect && gameInfo.variant != VariantGreat
6551 && gameInfo.variant != VariantGrand
6552 && gameInfo.variant != VariantSuper) return FALSE;
6553 if(autoQueen) return FALSE; // predetermined
6555 // suppress promotion popup on illegal moves that are not premoves
6556 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6557 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6558 if(appData.testLegality && !premove) {
6559 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6560 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6561 if(moveType != WhitePromotion && moveType != BlackPromotion)
6569 InPalace (int row, int column)
6570 { /* [HGM] for Xiangqi */
6571 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6572 column < (BOARD_WIDTH + 4)/2 &&
6573 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6578 PieceForSquare (int x, int y)
6580 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6583 return boards[currentMove][y][x];
6587 OKToStartUserMove (int x, int y)
6589 ChessSquare from_piece;
6592 if (matchMode) return FALSE;
6593 if (gameMode == EditPosition) return TRUE;
6595 if (x >= 0 && y >= 0)
6596 from_piece = boards[currentMove][y][x];
6598 from_piece = EmptySquare;
6600 if (from_piece == EmptySquare) return FALSE;
6602 white_piece = (int)from_piece >= (int)WhitePawn &&
6603 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6607 case TwoMachinesPlay:
6615 case MachinePlaysWhite:
6616 case IcsPlayingBlack:
6617 if (appData.zippyPlay) return FALSE;
6619 DisplayMoveError(_("You are playing Black"));
6624 case MachinePlaysBlack:
6625 case IcsPlayingWhite:
6626 if (appData.zippyPlay) return FALSE;
6628 DisplayMoveError(_("You are playing White"));
6633 case PlayFromGameFile:
6634 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6636 if (!white_piece && WhiteOnMove(currentMove)) {
6637 DisplayMoveError(_("It is White's turn"));
6640 if (white_piece && !WhiteOnMove(currentMove)) {
6641 DisplayMoveError(_("It is Black's turn"));
6644 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6645 /* Editing correspondence game history */
6646 /* Could disallow this or prompt for confirmation */
6651 case BeginningOfGame:
6652 if (appData.icsActive) return FALSE;
6653 if (!appData.noChessProgram) {
6655 DisplayMoveError(_("You are playing White"));
6662 if (!white_piece && WhiteOnMove(currentMove)) {
6663 DisplayMoveError(_("It is White's turn"));
6666 if (white_piece && !WhiteOnMove(currentMove)) {
6667 DisplayMoveError(_("It is Black's turn"));
6676 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6677 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6678 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6679 && gameMode != AnalyzeFile && gameMode != Training) {
6680 DisplayMoveError(_("Displayed position is not current"));
6687 OnlyMove (int *x, int *y, Boolean captures)
6689 DisambiguateClosure cl;
6690 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6692 case MachinePlaysBlack:
6693 case IcsPlayingWhite:
6694 case BeginningOfGame:
6695 if(!WhiteOnMove(currentMove)) return FALSE;
6697 case MachinePlaysWhite:
6698 case IcsPlayingBlack:
6699 if(WhiteOnMove(currentMove)) return FALSE;
6706 cl.pieceIn = EmptySquare;
6711 cl.promoCharIn = NULLCHAR;
6712 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6713 if( cl.kind == NormalMove ||
6714 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6715 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6716 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6723 if(cl.kind != ImpossibleMove) return FALSE;
6724 cl.pieceIn = EmptySquare;
6729 cl.promoCharIn = NULLCHAR;
6730 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6731 if( cl.kind == NormalMove ||
6732 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6733 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6734 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6739 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6745 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6746 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6747 int lastLoadGameUseList = FALSE;
6748 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6749 ChessMove lastLoadGameStart = EndOfFile;
6753 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6757 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6759 /* Check if the user is playing in turn. This is complicated because we
6760 let the user "pick up" a piece before it is his turn. So the piece he
6761 tried to pick up may have been captured by the time he puts it down!
6762 Therefore we use the color the user is supposed to be playing in this
6763 test, not the color of the piece that is currently on the starting
6764 square---except in EditGame mode, where the user is playing both
6765 sides; fortunately there the capture race can't happen. (It can
6766 now happen in IcsExamining mode, but that's just too bad. The user
6767 will get a somewhat confusing message in that case.)
6772 case TwoMachinesPlay:
6776 /* We switched into a game mode where moves are not accepted,
6777 perhaps while the mouse button was down. */
6780 case MachinePlaysWhite:
6781 /* User is moving for Black */
6782 if (WhiteOnMove(currentMove)) {
6783 DisplayMoveError(_("It is White's turn"));
6788 case MachinePlaysBlack:
6789 /* User is moving for White */
6790 if (!WhiteOnMove(currentMove)) {
6791 DisplayMoveError(_("It is Black's turn"));
6796 case PlayFromGameFile:
6797 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6800 case BeginningOfGame:
6803 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6804 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6805 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6806 /* User is moving for Black */
6807 if (WhiteOnMove(currentMove)) {
6808 DisplayMoveError(_("It is White's turn"));
6812 /* User is moving for White */
6813 if (!WhiteOnMove(currentMove)) {
6814 DisplayMoveError(_("It is Black's turn"));
6820 case IcsPlayingBlack:
6821 /* User is moving for Black */
6822 if (WhiteOnMove(currentMove)) {
6823 if (!appData.premove) {
6824 DisplayMoveError(_("It is White's turn"));
6825 } else if (toX >= 0 && toY >= 0) {
6828 premoveFromX = fromX;
6829 premoveFromY = fromY;
6830 premovePromoChar = promoChar;
6832 if (appData.debugMode)
6833 fprintf(debugFP, "Got premove: fromX %d,"
6834 "fromY %d, toX %d, toY %d\n",
6835 fromX, fromY, toX, toY);
6841 case IcsPlayingWhite:
6842 /* User is moving for White */
6843 if (!WhiteOnMove(currentMove)) {
6844 if (!appData.premove) {
6845 DisplayMoveError(_("It is Black's turn"));
6846 } else if (toX >= 0 && toY >= 0) {
6849 premoveFromX = fromX;
6850 premoveFromY = fromY;
6851 premovePromoChar = promoChar;
6853 if (appData.debugMode)
6854 fprintf(debugFP, "Got premove: fromX %d,"
6855 "fromY %d, toX %d, toY %d\n",
6856 fromX, fromY, toX, toY);
6866 /* EditPosition, empty square, or different color piece;
6867 click-click move is possible */
6868 if (toX == -2 || toY == -2) {
6869 boards[0][fromY][fromX] = EmptySquare;
6870 DrawPosition(FALSE, boards[currentMove]);
6872 } else if (toX >= 0 && toY >= 0) {
6873 boards[0][toY][toX] = boards[0][fromY][fromX];
6874 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6875 if(boards[0][fromY][0] != EmptySquare) {
6876 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6877 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6880 if(fromX == BOARD_RGHT+1) {
6881 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6882 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6883 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6886 boards[0][fromY][fromX] = gatingPiece;
6887 DrawPosition(FALSE, boards[currentMove]);
6893 if(toX < 0 || toY < 0) return;
6894 pup = boards[currentMove][toY][toX];
6896 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6897 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6898 if( pup != EmptySquare ) return;
6899 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6900 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6901 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6902 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6903 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6904 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6905 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6909 /* [HGM] always test for legality, to get promotion info */
6910 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6911 fromY, fromX, toY, toX, promoChar);
6913 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6915 /* [HGM] but possibly ignore an IllegalMove result */
6916 if (appData.testLegality) {
6917 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6918 DisplayMoveError(_("Illegal move"));
6923 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6924 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6925 ClearPremoveHighlights(); // was included
6926 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6930 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6933 /* Common tail of UserMoveEvent and DropMenuEvent */
6935 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6939 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6940 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6941 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6942 if(WhiteOnMove(currentMove)) {
6943 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6945 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6949 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6950 move type in caller when we know the move is a legal promotion */
6951 if(moveType == NormalMove && promoChar)
6952 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6954 /* [HGM] <popupFix> The following if has been moved here from
6955 UserMoveEvent(). Because it seemed to belong here (why not allow
6956 piece drops in training games?), and because it can only be
6957 performed after it is known to what we promote. */
6958 if (gameMode == Training) {
6959 /* compare the move played on the board to the next move in the
6960 * game. If they match, display the move and the opponent's response.
6961 * If they don't match, display an error message.
6965 CopyBoard(testBoard, boards[currentMove]);
6966 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6968 if (CompareBoards(testBoard, boards[currentMove+1])) {
6969 ForwardInner(currentMove+1);
6971 /* Autoplay the opponent's response.
6972 * if appData.animate was TRUE when Training mode was entered,
6973 * the response will be animated.
6975 saveAnimate = appData.animate;
6976 appData.animate = animateTraining;
6977 ForwardInner(currentMove+1);
6978 appData.animate = saveAnimate;
6980 /* check for the end of the game */
6981 if (currentMove >= forwardMostMove) {
6982 gameMode = PlayFromGameFile;
6984 SetTrainingModeOff();
6985 DisplayInformation(_("End of game"));
6988 DisplayError(_("Incorrect move"), 0);
6993 /* Ok, now we know that the move is good, so we can kill
6994 the previous line in Analysis Mode */
6995 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6996 && currentMove < forwardMostMove) {
6997 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6998 else forwardMostMove = currentMove;
7003 /* If we need the chess program but it's dead, restart it */
7004 ResurrectChessProgram();
7006 /* A user move restarts a paused game*/
7010 thinkOutput[0] = NULLCHAR;
7012 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7014 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7015 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7019 if (gameMode == BeginningOfGame) {
7020 if (appData.noChessProgram) {
7021 gameMode = EditGame;
7025 gameMode = MachinePlaysBlack;
7028 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7030 if (first.sendName) {
7031 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7032 SendToProgram(buf, &first);
7039 /* Relay move to ICS or chess engine */
7040 if (appData.icsActive) {
7041 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7042 gameMode == IcsExamining) {
7043 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7044 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7046 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7048 // also send plain move, in case ICS does not understand atomic claims
7049 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7053 if (first.sendTime && (gameMode == BeginningOfGame ||
7054 gameMode == MachinePlaysWhite ||
7055 gameMode == MachinePlaysBlack)) {
7056 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7058 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7059 // [HGM] book: if program might be playing, let it use book
7060 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7061 first.maybeThinking = TRUE;
7062 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7063 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7064 SendBoard(&first, currentMove+1);
7065 if(second.analyzing) {
7066 if(!second.useSetboard) SendToProgram("undo\n", &second);
7067 SendBoard(&second, currentMove+1);
7070 SendMoveToProgram(forwardMostMove-1, &first);
7071 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7073 if (currentMove == cmailOldMove + 1) {
7074 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7078 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7082 if(appData.testLegality)
7083 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7089 if (WhiteOnMove(currentMove)) {
7090 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7092 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7096 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7101 case MachinePlaysBlack:
7102 case MachinePlaysWhite:
7103 /* disable certain menu options while machine is thinking */
7104 SetMachineThinkingEnables();
7111 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7112 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7114 if(bookHit) { // [HGM] book: simulate book reply
7115 static char bookMove[MSG_SIZ]; // a bit generous?
7117 programStats.nodes = programStats.depth = programStats.time =
7118 programStats.score = programStats.got_only_move = 0;
7119 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7121 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7122 strcat(bookMove, bookHit);
7123 HandleMachineMove(bookMove, &first);
7129 MarkByFEN(char *fen)
7132 if(!appData.markers || !appData.highlightDragging) return;
7133 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7134 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7138 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7139 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7140 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7141 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7142 if(*fen == 'T') marker[r][f++] = 0; else
7143 if(*fen == 'Y') marker[r][f++] = 1; else
7144 if(*fen == 'G') marker[r][f++] = 3; else
7145 if(*fen == 'B') marker[r][f++] = 4; else
7146 if(*fen == 'C') marker[r][f++] = 5; else
7147 if(*fen == 'M') marker[r][f++] = 6; else
7148 if(*fen == 'W') marker[r][f++] = 7; else
7149 if(*fen == 'D') marker[r][f++] = 8; else
7150 if(*fen == 'R') marker[r][f++] = 2; else {
7151 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7154 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7158 DrawPosition(TRUE, NULL);
7161 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7164 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7166 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7167 Markers *m = (Markers *) closure;
7168 if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7169 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7170 || kind == WhiteCapturesEnPassant
7171 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7172 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7176 MarkTargetSquares (int clear)
7179 if(clear) { // no reason to ever suppress clearing
7180 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;
7181 if(!sum) return; // nothing was cleared,no redraw needed
7184 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7185 !appData.testLegality || gameMode == EditPosition) return;
7186 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7187 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7188 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7190 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7193 DrawPosition(FALSE, NULL);
7197 Explode (Board board, int fromX, int fromY, int toX, int toY)
7199 if(gameInfo.variant == VariantAtomic &&
7200 (board[toY][toX] != EmptySquare || // capture?
7201 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7202 board[fromY][fromX] == BlackPawn )
7204 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7210 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7213 CanPromote (ChessSquare piece, int y)
7215 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7216 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7217 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7218 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7219 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7220 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7221 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7222 return (piece == BlackPawn && y <= zone ||
7223 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7224 piece == BlackLance && y == 1 ||
7225 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7229 HoverEvent (int xPix, int yPix, int x, int y)
7231 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7233 if(!first.highlight) return;
7234 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7235 if(x == oldX && y == oldY) return; // only do something if we enter new square
7236 oldFromX = fromX; oldFromY = fromY;
7237 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7238 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7239 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7240 else if(oldX != x || oldY != y) {
7241 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7242 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7243 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7244 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7246 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7247 SendToProgram(buf, &first);
7250 // SetHighlights(fromX, fromY, x, y);
7254 void ReportClick(char *action, int x, int y)
7256 char buf[MSG_SIZ]; // Inform engine of what user does
7258 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7259 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7260 if(!first.highlight || gameMode == EditPosition) return;
7261 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7262 SendToProgram(buf, &first);
7266 LeftClick (ClickType clickType, int xPix, int yPix)
7269 Boolean saveAnimate;
7270 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7271 char promoChoice = NULLCHAR;
7273 static TimeMark lastClickTime, prevClickTime;
7275 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7277 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7279 if (clickType == Press) ErrorPopDown();
7280 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7282 x = EventToSquare(xPix, BOARD_WIDTH);
7283 y = EventToSquare(yPix, BOARD_HEIGHT);
7284 if (!flipView && y >= 0) {
7285 y = BOARD_HEIGHT - 1 - y;
7287 if (flipView && x >= 0) {
7288 x = BOARD_WIDTH - 1 - x;
7291 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7292 defaultPromoChoice = promoSweep;
7293 promoSweep = EmptySquare; // terminate sweep
7294 promoDefaultAltered = TRUE;
7295 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7298 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7299 if(clickType == Release) return; // ignore upclick of click-click destination
7300 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7301 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7302 if(gameInfo.holdingsWidth &&
7303 (WhiteOnMove(currentMove)
7304 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7305 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7306 // click in right holdings, for determining promotion piece
7307 ChessSquare p = boards[currentMove][y][x];
7308 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7309 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7310 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7311 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7316 DrawPosition(FALSE, boards[currentMove]);
7320 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7321 if(clickType == Press
7322 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7323 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7324 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7327 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7328 // could be static click on premove from-square: abort premove
7330 ClearPremoveHighlights();
7333 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7334 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7336 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7337 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7338 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7339 defaultPromoChoice = DefaultPromoChoice(side);
7342 autoQueen = appData.alwaysPromoteToQueen;
7346 gatingPiece = EmptySquare;
7347 if (clickType != Press) {
7348 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7349 DragPieceEnd(xPix, yPix); dragging = 0;
7350 DrawPosition(FALSE, NULL);
7354 doubleClick = FALSE;
7355 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7356 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7358 fromX = x; fromY = y; toX = toY = killX = killY = -1;
7359 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7360 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7361 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7363 if (OKToStartUserMove(fromX, fromY)) {
7365 ReportClick("lift", x, y);
7366 MarkTargetSquares(0);
7367 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7368 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7369 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7370 promoSweep = defaultPromoChoice;
7371 selectFlag = 0; lastX = xPix; lastY = yPix;
7372 Sweep(0); // Pawn that is going to promote: preview promotion piece
7373 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7375 if (appData.highlightDragging) {
7376 SetHighlights(fromX, fromY, -1, -1);
7380 } else fromX = fromY = -1;
7386 if (clickType == Press && gameMode != EditPosition) {
7391 // ignore off-board to clicks
7392 if(y < 0 || x < 0) return;
7394 /* Check if clicking again on the same color piece */
7395 fromP = boards[currentMove][fromY][fromX];
7396 toP = boards[currentMove][y][x];
7397 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7398 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7399 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7400 WhitePawn <= toP && toP <= WhiteKing &&
7401 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7402 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7403 (BlackPawn <= fromP && fromP <= BlackKing &&
7404 BlackPawn <= toP && toP <= BlackKing &&
7405 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7406 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7407 /* Clicked again on same color piece -- changed his mind */
7408 second = (x == fromX && y == fromY);
7410 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7411 second = FALSE; // first double-click rather than scond click
7412 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7414 promoDefaultAltered = FALSE;
7415 MarkTargetSquares(1);
7416 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7417 if (appData.highlightDragging) {
7418 SetHighlights(x, y, -1, -1);
7422 if (OKToStartUserMove(x, y)) {
7423 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7424 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7425 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7426 gatingPiece = boards[currentMove][fromY][fromX];
7427 else gatingPiece = doubleClick ? fromP : EmptySquare;
7429 fromY = y; dragging = 1;
7430 ReportClick("lift", x, y);
7431 MarkTargetSquares(0);
7432 DragPieceBegin(xPix, yPix, FALSE);
7433 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7434 promoSweep = defaultPromoChoice;
7435 selectFlag = 0; lastX = xPix; lastY = yPix;
7436 Sweep(0); // Pawn that is going to promote: preview promotion piece
7440 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7443 // ignore clicks on holdings
7444 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7447 if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7448 DragPieceEnd(xPix, yPix); dragging = 0;
7450 // a deferred attempt to click-click move an empty square on top of a piece
7451 boards[currentMove][y][x] = EmptySquare;
7453 DrawPosition(FALSE, boards[currentMove]);
7454 fromX = fromY = -1; clearFlag = 0;
7457 if (appData.animateDragging) {
7458 /* Undo animation damage if any */
7459 DrawPosition(FALSE, NULL);
7461 if (second || sweepSelecting) {
7462 /* Second up/down in same square; just abort move */
7463 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7464 second = sweepSelecting = 0;
7466 gatingPiece = EmptySquare;
7467 MarkTargetSquares(1);
7470 ClearPremoveHighlights();
7472 /* First upclick in same square; start click-click mode */
7473 SetHighlights(x, y, -1, -1);
7480 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7481 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7482 DisplayMessage(_("only marked squares are legal"),"");
7483 DrawPosition(TRUE, NULL);
7484 return; // ignore to-click
7487 /* we now have a different from- and (possibly off-board) to-square */
7488 /* Completed move */
7489 if(!sweepSelecting) {
7494 saveAnimate = appData.animate;
7495 if (clickType == Press) {
7496 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7497 // must be Edit Position mode with empty-square selected
7498 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7499 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7502 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7505 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7506 killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7508 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7509 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7510 if(appData.sweepSelect) {
7511 ChessSquare piece = boards[currentMove][fromY][fromX];
7512 promoSweep = defaultPromoChoice;
7513 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) promoSweep = piece; else
7514 if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7515 selectFlag = 0; lastX = xPix; lastY = yPix;
7516 Sweep(0); // Pawn that is going to promote: preview promotion piece
7518 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7519 MarkTargetSquares(1);
7521 return; // promo popup appears on up-click
7523 /* Finish clickclick move */
7524 if (appData.animate || appData.highlightLastMove) {
7525 SetHighlights(fromX, fromY, toX, toY);
7529 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7530 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7531 if (appData.animate || appData.highlightLastMove) {
7532 SetHighlights(fromX, fromY, toX, toY);
7538 // [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
7539 /* Finish drag move */
7540 if (appData.highlightLastMove) {
7541 SetHighlights(fromX, fromY, toX, toY);
7546 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7547 dragging *= 2; // flag button-less dragging if we are dragging
7548 MarkTargetSquares(1);
7549 if(x == killX && y == killY) killX = killY = -1; else {
7550 killX = x; killY = y; //remeber this square as intermediate
7551 ReportClick("put", x, y); // and inform engine
7552 ReportClick("lift", x, y);
7553 MarkTargetSquares(0);
7557 DragPieceEnd(xPix, yPix); dragging = 0;
7558 /* Don't animate move and drag both */
7559 appData.animate = FALSE;
7562 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7563 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7564 ChessSquare piece = boards[currentMove][fromY][fromX];
7565 if(gameMode == EditPosition && piece != EmptySquare &&
7566 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7569 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7570 n = PieceToNumber(piece - (int)BlackPawn);
7571 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7572 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7573 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7575 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7576 n = PieceToNumber(piece);
7577 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7578 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7579 boards[currentMove][n][BOARD_WIDTH-2]++;
7581 boards[currentMove][fromY][fromX] = EmptySquare;
7585 MarkTargetSquares(1);
7586 DrawPosition(TRUE, boards[currentMove]);
7590 // off-board moves should not be highlighted
7591 if(x < 0 || y < 0) ClearHighlights();
7592 else ReportClick("put", x, y);
7594 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7596 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7597 SetHighlights(fromX, fromY, toX, toY);
7598 MarkTargetSquares(1);
7599 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7600 // [HGM] super: promotion to captured piece selected from holdings
7601 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7602 promotionChoice = TRUE;
7603 // kludge follows to temporarily execute move on display, without promoting yet
7604 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7605 boards[currentMove][toY][toX] = p;
7606 DrawPosition(FALSE, boards[currentMove]);
7607 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7608 boards[currentMove][toY][toX] = q;
7609 DisplayMessage("Click in holdings to choose piece", "");
7612 PromotionPopUp(promoChoice);
7614 int oldMove = currentMove;
7615 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7616 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7617 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7618 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7619 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7620 DrawPosition(TRUE, boards[currentMove]);
7621 MarkTargetSquares(1);
7624 appData.animate = saveAnimate;
7625 if (appData.animate || appData.animateDragging) {
7626 /* Undo animation damage if needed */
7627 DrawPosition(FALSE, NULL);
7632 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7633 { // front-end-free part taken out of PieceMenuPopup
7634 int whichMenu; int xSqr, ySqr;
7636 if(seekGraphUp) { // [HGM] seekgraph
7637 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7638 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7642 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7643 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7644 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7645 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7646 if(action == Press) {
7647 originalFlip = flipView;
7648 flipView = !flipView; // temporarily flip board to see game from partners perspective
7649 DrawPosition(TRUE, partnerBoard);
7650 DisplayMessage(partnerStatus, "");
7652 } else if(action == Release) {
7653 flipView = originalFlip;
7654 DrawPosition(TRUE, boards[currentMove]);
7660 xSqr = EventToSquare(x, BOARD_WIDTH);
7661 ySqr = EventToSquare(y, BOARD_HEIGHT);
7662 if (action == Release) {
7663 if(pieceSweep != EmptySquare) {
7664 EditPositionMenuEvent(pieceSweep, toX, toY);
7665 pieceSweep = EmptySquare;
7666 } else UnLoadPV(); // [HGM] pv
7668 if (action != Press) return -2; // return code to be ignored
7671 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7673 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7674 if (xSqr < 0 || ySqr < 0) return -1;
7675 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7676 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7677 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7678 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7682 if(!appData.icsEngineAnalyze) return -1;
7683 case IcsPlayingWhite:
7684 case IcsPlayingBlack:
7685 if(!appData.zippyPlay) goto noZip;
7688 case MachinePlaysWhite:
7689 case MachinePlaysBlack:
7690 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7691 if (!appData.dropMenu) {
7693 return 2; // flag front-end to grab mouse events
7695 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7696 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7699 if (xSqr < 0 || ySqr < 0) return -1;
7700 if (!appData.dropMenu || appData.testLegality &&
7701 gameInfo.variant != VariantBughouse &&
7702 gameInfo.variant != VariantCrazyhouse) return -1;
7703 whichMenu = 1; // drop menu
7709 if (((*fromX = xSqr) < 0) ||
7710 ((*fromY = ySqr) < 0)) {
7711 *fromX = *fromY = -1;
7715 *fromX = BOARD_WIDTH - 1 - *fromX;
7717 *fromY = BOARD_HEIGHT - 1 - *fromY;
7723 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7725 // char * hint = lastHint;
7726 FrontEndProgramStats stats;
7728 stats.which = cps == &first ? 0 : 1;
7729 stats.depth = cpstats->depth;
7730 stats.nodes = cpstats->nodes;
7731 stats.score = cpstats->score;
7732 stats.time = cpstats->time;
7733 stats.pv = cpstats->movelist;
7734 stats.hint = lastHint;
7735 stats.an_move_index = 0;
7736 stats.an_move_count = 0;
7738 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7739 stats.hint = cpstats->move_name;
7740 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7741 stats.an_move_count = cpstats->nr_moves;
7744 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
7746 SetProgramStats( &stats );
7750 ClearEngineOutputPane (int which)
7752 static FrontEndProgramStats dummyStats;
7753 dummyStats.which = which;
7754 dummyStats.pv = "#";
7755 SetProgramStats( &dummyStats );
7758 #define MAXPLAYERS 500
7761 TourneyStandings (int display)
7763 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7764 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7765 char result, *p, *names[MAXPLAYERS];
7767 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7768 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7769 names[0] = p = strdup(appData.participants);
7770 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7772 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7774 while(result = appData.results[nr]) {
7775 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7776 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7777 wScore = bScore = 0;
7779 case '+': wScore = 2; break;
7780 case '-': bScore = 2; break;
7781 case '=': wScore = bScore = 1; break;
7783 case '*': return strdup("busy"); // tourney not finished
7791 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7792 for(w=0; w<nPlayers; w++) {
7794 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7795 ranking[w] = b; points[w] = bScore; score[b] = -2;
7797 p = malloc(nPlayers*34+1);
7798 for(w=0; w<nPlayers && w<display; w++)
7799 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7805 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7806 { // count all piece types
7808 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7809 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7810 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7813 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7814 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7815 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7816 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7817 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7818 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7823 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7825 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7826 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7828 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7829 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7830 if(myPawns == 2 && nMine == 3) // KPP
7831 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7832 if(myPawns == 1 && nMine == 2) // KP
7833 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7834 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7835 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7836 if(myPawns) return FALSE;
7837 if(pCnt[WhiteRook+side])
7838 return pCnt[BlackRook-side] ||
7839 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7840 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7841 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7842 if(pCnt[WhiteCannon+side]) {
7843 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7844 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7846 if(pCnt[WhiteKnight+side])
7847 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7852 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7854 VariantClass v = gameInfo.variant;
7856 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7857 if(v == VariantShatranj) return TRUE; // always winnable through baring
7858 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7859 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7861 if(v == VariantXiangqi) {
7862 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7864 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7865 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7866 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7867 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7868 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7869 if(stale) // we have at least one last-rank P plus perhaps C
7870 return majors // KPKX
7871 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7873 return pCnt[WhiteFerz+side] // KCAK
7874 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7875 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7876 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7878 } else if(v == VariantKnightmate) {
7879 if(nMine == 1) return FALSE;
7880 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7881 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7882 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7884 if(nMine == 1) return FALSE; // bare King
7885 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
7886 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7887 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7888 // by now we have King + 1 piece (or multiple Bishops on the same color)
7889 if(pCnt[WhiteKnight+side])
7890 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7891 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7892 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7894 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7895 if(pCnt[WhiteAlfil+side])
7896 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7897 if(pCnt[WhiteWazir+side])
7898 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7905 CompareWithRights (Board b1, Board b2)
7908 if(!CompareBoards(b1, b2)) return FALSE;
7909 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7910 /* compare castling rights */
7911 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7912 rights++; /* King lost rights, while rook still had them */
7913 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7914 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7915 rights++; /* but at least one rook lost them */
7917 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7919 if( b1[CASTLING][5] != NoRights ) {
7920 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7927 Adjudicate (ChessProgramState *cps)
7928 { // [HGM] some adjudications useful with buggy engines
7929 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7930 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7931 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7932 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7933 int k, drop, count = 0; static int bare = 1;
7934 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7935 Boolean canAdjudicate = !appData.icsActive;
7937 // most tests only when we understand the game, i.e. legality-checking on
7938 if( appData.testLegality )
7939 { /* [HGM] Some more adjudications for obstinate engines */
7940 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7941 static int moveCount = 6;
7943 char *reason = NULL;
7945 /* Count what is on board. */
7946 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7948 /* Some material-based adjudications that have to be made before stalemate test */
7949 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7950 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7951 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7952 if(canAdjudicate && appData.checkMates) {
7954 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7955 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7956 "Xboard adjudication: King destroyed", GE_XBOARD );
7961 /* Bare King in Shatranj (loses) or Losers (wins) */
7962 if( nrW == 1 || nrB == 1) {
7963 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7964 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7965 if(canAdjudicate && appData.checkMates) {
7967 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7968 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7969 "Xboard adjudication: Bare king", GE_XBOARD );
7973 if( gameInfo.variant == VariantShatranj && --bare < 0)
7975 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7976 if(canAdjudicate && appData.checkMates) {
7977 /* but only adjudicate if adjudication enabled */
7979 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7980 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7981 "Xboard adjudication: Bare king", GE_XBOARD );
7988 // don't wait for engine to announce game end if we can judge ourselves
7989 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7991 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7992 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7993 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7994 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7997 reason = "Xboard adjudication: 3rd check";
7998 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8008 reason = "Xboard adjudication: Stalemate";
8009 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8010 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8011 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8012 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8013 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8014 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8015 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8016 EP_CHECKMATE : EP_WINS);
8017 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8018 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8022 reason = "Xboard adjudication: Checkmate";
8023 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8024 if(gameInfo.variant == VariantShogi) {
8025 if(forwardMostMove > backwardMostMove
8026 && moveList[forwardMostMove-1][1] == '@'
8027 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8028 reason = "XBoard adjudication: pawn-drop mate";
8029 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8035 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8037 result = GameIsDrawn; break;
8039 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8041 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8045 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8047 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8048 GameEnds( result, reason, GE_XBOARD );
8052 /* Next absolutely insufficient mating material. */
8053 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8054 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8055 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8057 /* always flag draws, for judging claims */
8058 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8060 if(canAdjudicate && appData.materialDraws) {
8061 /* but only adjudicate them if adjudication enabled */
8062 if(engineOpponent) {
8063 SendToProgram("force\n", engineOpponent); // suppress reply
8064 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8066 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8071 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8072 if(gameInfo.variant == VariantXiangqi ?
8073 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8075 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8076 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8077 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8078 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8080 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8081 { /* if the first 3 moves do not show a tactical win, declare draw */
8082 if(engineOpponent) {
8083 SendToProgram("force\n", engineOpponent); // suppress reply
8084 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8086 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8089 } else moveCount = 6;
8092 // Repetition draws and 50-move rule can be applied independently of legality testing
8094 /* Check for rep-draws */
8096 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8097 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8098 for(k = forwardMostMove-2;
8099 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8100 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8101 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8104 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8105 /* compare castling rights */
8106 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8107 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8108 rights++; /* King lost rights, while rook still had them */
8109 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8110 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8111 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8112 rights++; /* but at least one rook lost them */
8114 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8115 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8117 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8118 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8119 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8122 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8123 && appData.drawRepeats > 1) {
8124 /* adjudicate after user-specified nr of repeats */
8125 int result = GameIsDrawn;
8126 char *details = "XBoard adjudication: repetition draw";
8127 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8128 // [HGM] xiangqi: check for forbidden perpetuals
8129 int m, ourPerpetual = 1, hisPerpetual = 1;
8130 for(m=forwardMostMove; m>k; m-=2) {
8131 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8132 ourPerpetual = 0; // the current mover did not always check
8133 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8134 hisPerpetual = 0; // the opponent did not always check
8136 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8137 ourPerpetual, hisPerpetual);
8138 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8139 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8140 details = "Xboard adjudication: perpetual checking";
8142 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8143 break; // (or we would have caught him before). Abort repetition-checking loop.
8145 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8146 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8148 details = "Xboard adjudication: repetition";
8150 } else // it must be XQ
8151 // Now check for perpetual chases
8152 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8153 hisPerpetual = PerpetualChase(k, forwardMostMove);
8154 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8155 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8156 static char resdet[MSG_SIZ];
8157 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8159 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8161 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8162 break; // Abort repetition-checking loop.
8164 // if neither of us is checking or chasing all the time, or both are, it is draw
8166 if(engineOpponent) {
8167 SendToProgram("force\n", engineOpponent); // suppress reply
8168 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8170 GameEnds( result, details, GE_XBOARD );
8173 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8174 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8178 /* Now we test for 50-move draws. Determine ply count */
8179 count = forwardMostMove;
8180 /* look for last irreversble move */
8181 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8183 /* if we hit starting position, add initial plies */
8184 if( count == backwardMostMove )
8185 count -= initialRulePlies;
8186 count = forwardMostMove - count;
8187 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8188 // adjust reversible move counter for checks in Xiangqi
8189 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8190 if(i < backwardMostMove) i = backwardMostMove;
8191 while(i <= forwardMostMove) {
8192 lastCheck = inCheck; // check evasion does not count
8193 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8194 if(inCheck || lastCheck) count--; // check does not count
8199 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8200 /* this is used to judge if draw claims are legal */
8201 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8202 if(engineOpponent) {
8203 SendToProgram("force\n", engineOpponent); // suppress reply
8204 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8206 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8210 /* if draw offer is pending, treat it as a draw claim
8211 * when draw condition present, to allow engines a way to
8212 * claim draws before making their move to avoid a race
8213 * condition occurring after their move
8215 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8217 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8218 p = "Draw claim: 50-move rule";
8219 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8220 p = "Draw claim: 3-fold repetition";
8221 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8222 p = "Draw claim: insufficient mating material";
8223 if( p != NULL && canAdjudicate) {
8224 if(engineOpponent) {
8225 SendToProgram("force\n", engineOpponent); // suppress reply
8226 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8228 GameEnds( GameIsDrawn, p, GE_XBOARD );
8233 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8234 if(engineOpponent) {
8235 SendToProgram("force\n", engineOpponent); // suppress reply
8236 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8238 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8245 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8246 { // [HGM] book: this routine intercepts moves to simulate book replies
8247 char *bookHit = NULL;
8249 //first determine if the incoming move brings opponent into his book
8250 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8251 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8252 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8253 if(bookHit != NULL && !cps->bookSuspend) {
8254 // make sure opponent is not going to reply after receiving move to book position
8255 SendToProgram("force\n", cps);
8256 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8258 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8259 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8260 // now arrange restart after book miss
8262 // after a book hit we never send 'go', and the code after the call to this routine
8263 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8264 char buf[MSG_SIZ], *move = bookHit;
8266 int fromX, fromY, toX, toY;
8270 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8271 &fromX, &fromY, &toX, &toY, &promoChar)) {
8272 (void) CoordsToAlgebraic(boards[forwardMostMove],
8273 PosFlags(forwardMostMove),
8274 fromY, fromX, toY, toX, promoChar, move);
8276 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8280 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8281 SendToProgram(buf, cps);
8282 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8283 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8284 SendToProgram("go\n", cps);
8285 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8286 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8287 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8288 SendToProgram("go\n", cps);
8289 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8291 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8295 LoadError (char *errmess, ChessProgramState *cps)
8296 { // unloads engine and switches back to -ncp mode if it was first
8297 if(cps->initDone) return FALSE;
8298 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8299 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8302 appData.noChessProgram = TRUE;
8303 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8304 gameMode = BeginningOfGame; ModeHighlight();
8307 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8308 DisplayMessage("", ""); // erase waiting message
8309 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8314 ChessProgramState *savedState;
8316 DeferredBookMove (void)
8318 if(savedState->lastPing != savedState->lastPong)
8319 ScheduleDelayedEvent(DeferredBookMove, 10);
8321 HandleMachineMove(savedMessage, savedState);
8324 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8325 static ChessProgramState *stalledEngine;
8326 static char stashedInputMove[MSG_SIZ];
8329 HandleMachineMove (char *message, ChessProgramState *cps)
8331 static char firstLeg[20];
8332 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8333 char realname[MSG_SIZ];
8334 int fromX, fromY, toX, toY;
8336 char promoChar, roar;
8338 int machineWhite, oldError;
8341 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8342 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8343 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8344 DisplayError(_("Invalid pairing from pairing engine"), 0);
8347 pairingReceived = 1;
8349 return; // Skim the pairing messages here.
8352 oldError = cps->userError; cps->userError = 0;
8354 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8356 * Kludge to ignore BEL characters
8358 while (*message == '\007') message++;
8361 * [HGM] engine debug message: ignore lines starting with '#' character
8363 if(cps->debug && *message == '#') return;
8366 * Look for book output
8368 if (cps == &first && bookRequested) {
8369 if (message[0] == '\t' || message[0] == ' ') {
8370 /* Part of the book output is here; append it */
8371 strcat(bookOutput, message);
8372 strcat(bookOutput, " \n");
8374 } else if (bookOutput[0] != NULLCHAR) {
8375 /* All of book output has arrived; display it */
8376 char *p = bookOutput;
8377 while (*p != NULLCHAR) {
8378 if (*p == '\t') *p = ' ';
8381 DisplayInformation(bookOutput);
8382 bookRequested = FALSE;
8383 /* Fall through to parse the current output */
8388 * Look for machine move.
8390 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8391 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8393 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8394 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8395 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8396 stalledEngine = cps;
8397 if(appData.ponderNextMove) { // bring opponent out of ponder
8398 if(gameMode == TwoMachinesPlay) {
8399 if(cps->other->pause)
8400 PauseEngine(cps->other);
8402 SendToProgram("easy\n", cps->other);
8409 /* This method is only useful on engines that support ping */
8410 if (cps->lastPing != cps->lastPong) {
8411 if (gameMode == BeginningOfGame) {
8412 /* Extra move from before last new; ignore */
8413 if (appData.debugMode) {
8414 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8417 if (appData.debugMode) {
8418 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8419 cps->which, gameMode);
8422 SendToProgram("undo\n", cps);
8428 case BeginningOfGame:
8429 /* Extra move from before last reset; ignore */
8430 if (appData.debugMode) {
8431 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8438 /* Extra move after we tried to stop. The mode test is
8439 not a reliable way of detecting this problem, but it's
8440 the best we can do on engines that don't support ping.
8442 if (appData.debugMode) {
8443 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8444 cps->which, gameMode);
8446 SendToProgram("undo\n", cps);
8449 case MachinePlaysWhite:
8450 case IcsPlayingWhite:
8451 machineWhite = TRUE;
8454 case MachinePlaysBlack:
8455 case IcsPlayingBlack:
8456 machineWhite = FALSE;
8459 case TwoMachinesPlay:
8460 machineWhite = (cps->twoMachinesColor[0] == 'w');
8463 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8464 if (appData.debugMode) {
8466 "Ignoring move out of turn by %s, gameMode %d"
8467 ", forwardMost %d\n",
8468 cps->which, gameMode, forwardMostMove);
8473 if(cps->alphaRank) AlphaRank(machineMove, 4);
8475 // [HGM] lion: (some very limited) support for Alien protocol
8477 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8478 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8480 } else if(firstLeg[0]) { // there was a previous leg;
8481 // only support case where same piece makes two step (and don't even test that!)
8482 char buf[20], *p = machineMove+1, *q = buf+1, f;
8483 safeStrCpy(buf, machineMove, 20);
8484 while(isdigit(*q)) q++; // find start of to-square
8485 safeStrCpy(machineMove, firstLeg, 20);
8486 while(isdigit(*p)) p++;
8487 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8488 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8489 firstLeg[0] = NULLCHAR;
8492 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8493 &fromX, &fromY, &toX, &toY, &promoChar)) {
8494 /* Machine move could not be parsed; ignore it. */
8495 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8496 machineMove, _(cps->which));
8497 DisplayMoveError(buf1);
8498 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8499 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8500 if (gameMode == TwoMachinesPlay) {
8501 GameEnds(machineWhite ? BlackWins : WhiteWins,
8507 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8508 /* So we have to redo legality test with true e.p. status here, */
8509 /* to make sure an illegal e.p. capture does not slip through, */
8510 /* to cause a forfeit on a justified illegal-move complaint */
8511 /* of the opponent. */
8512 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8514 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8515 fromY, fromX, toY, toX, promoChar);
8516 if(moveType == IllegalMove) {
8517 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8518 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8519 GameEnds(machineWhite ? BlackWins : WhiteWins,
8522 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8523 /* [HGM] Kludge to handle engines that send FRC-style castling
8524 when they shouldn't (like TSCP-Gothic) */
8526 case WhiteASideCastleFR:
8527 case BlackASideCastleFR:
8529 currentMoveString[2]++;
8531 case WhiteHSideCastleFR:
8532 case BlackHSideCastleFR:
8534 currentMoveString[2]--;
8536 default: ; // nothing to do, but suppresses warning of pedantic compilers
8539 hintRequested = FALSE;
8540 lastHint[0] = NULLCHAR;
8541 bookRequested = FALSE;
8542 /* Program may be pondering now */
8543 cps->maybeThinking = TRUE;
8544 if (cps->sendTime == 2) cps->sendTime = 1;
8545 if (cps->offeredDraw) cps->offeredDraw--;
8547 /* [AS] Save move info*/
8548 pvInfoList[ forwardMostMove ].score = programStats.score;
8549 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8550 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8552 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8554 /* Test suites abort the 'game' after one move */
8555 if(*appData.finger) {
8557 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8558 if(!f) f = fopen(appData.finger, "w");
8559 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8560 else { DisplayFatalError("Bad output file", errno, 0); return; }
8562 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8565 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8566 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8569 while( count < adjudicateLossPlies ) {
8570 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8573 score = -score; /* Flip score for winning side */
8576 if( score > adjudicateLossThreshold ) {
8583 if( count >= adjudicateLossPlies ) {
8584 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8586 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8587 "Xboard adjudication",
8594 if(Adjudicate(cps)) {
8595 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8596 return; // [HGM] adjudicate: for all automatic game ends
8600 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8602 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8603 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8605 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8607 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8609 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8610 char buf[3*MSG_SIZ];
8612 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8613 programStats.score / 100.,
8615 programStats.time / 100.,
8616 (unsigned int)programStats.nodes,
8617 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8618 programStats.movelist);
8620 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8625 /* [AS] Clear stats for next move */
8626 ClearProgramStats();
8627 thinkOutput[0] = NULLCHAR;
8628 hiddenThinkOutputState = 0;
8631 if (gameMode == TwoMachinesPlay) {
8632 /* [HGM] relaying draw offers moved to after reception of move */
8633 /* and interpreting offer as claim if it brings draw condition */
8634 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8635 SendToProgram("draw\n", cps->other);
8637 if (cps->other->sendTime) {
8638 SendTimeRemaining(cps->other,
8639 cps->other->twoMachinesColor[0] == 'w');
8641 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8642 if (firstMove && !bookHit) {
8644 if (cps->other->useColors) {
8645 SendToProgram(cps->other->twoMachinesColor, cps->other);
8647 SendToProgram("go\n", cps->other);
8649 cps->other->maybeThinking = TRUE;
8652 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8654 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8656 if (!pausing && appData.ringBellAfterMoves) {
8657 if(!roar) RingBell();
8661 * Reenable menu items that were disabled while
8662 * machine was thinking
8664 if (gameMode != TwoMachinesPlay)
8665 SetUserThinkingEnables();
8667 // [HGM] book: after book hit opponent has received move and is now in force mode
8668 // force the book reply into it, and then fake that it outputted this move by jumping
8669 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8671 static char bookMove[MSG_SIZ]; // a bit generous?
8673 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8674 strcat(bookMove, bookHit);
8677 programStats.nodes = programStats.depth = programStats.time =
8678 programStats.score = programStats.got_only_move = 0;
8679 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8681 if(cps->lastPing != cps->lastPong) {
8682 savedMessage = message; // args for deferred call
8684 ScheduleDelayedEvent(DeferredBookMove, 10);
8693 /* Set special modes for chess engines. Later something general
8694 * could be added here; for now there is just one kludge feature,
8695 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8696 * when "xboard" is given as an interactive command.
8698 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8699 cps->useSigint = FALSE;
8700 cps->useSigterm = FALSE;
8702 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8703 ParseFeatures(message+8, cps);
8704 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8707 if (!strncmp(message, "setup ", 6) &&
8708 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8709 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8710 ) { // [HGM] allow first engine to define opening position
8711 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8712 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8714 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8715 if(startedFromSetupPosition) return;
8716 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8718 while(message[s] && message[s++] != ' ');
8719 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8720 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8721 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8722 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8723 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8724 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8727 ParseFEN(boards[0], &dummy, message+s, FALSE);
8728 DrawPosition(TRUE, boards[0]);
8729 startedFromSetupPosition = TRUE;
8732 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8733 * want this, I was asked to put it in, and obliged.
8735 if (!strncmp(message, "setboard ", 9)) {
8736 Board initial_position;
8738 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8740 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8741 DisplayError(_("Bad FEN received from engine"), 0);
8745 CopyBoard(boards[0], initial_position);
8746 initialRulePlies = FENrulePlies;
8747 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8748 else gameMode = MachinePlaysBlack;
8749 DrawPosition(FALSE, boards[currentMove]);
8755 * Look for communication commands
8757 if (!strncmp(message, "telluser ", 9)) {
8758 if(message[9] == '\\' && message[10] == '\\')
8759 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8761 DisplayNote(message + 9);
8764 if (!strncmp(message, "tellusererror ", 14)) {
8766 if(message[14] == '\\' && message[15] == '\\')
8767 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8769 DisplayError(message + 14, 0);
8772 if (!strncmp(message, "tellopponent ", 13)) {
8773 if (appData.icsActive) {
8775 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8779 DisplayNote(message + 13);
8783 if (!strncmp(message, "tellothers ", 11)) {
8784 if (appData.icsActive) {
8786 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8789 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8792 if (!strncmp(message, "tellall ", 8)) {
8793 if (appData.icsActive) {
8795 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8799 DisplayNote(message + 8);
8803 if (strncmp(message, "warning", 7) == 0) {
8804 /* Undocumented feature, use tellusererror in new code */
8805 DisplayError(message, 0);
8808 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8809 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8810 strcat(realname, " query");
8811 AskQuestion(realname, buf2, buf1, cps->pr);
8814 /* Commands from the engine directly to ICS. We don't allow these to be
8815 * sent until we are logged on. Crafty kibitzes have been known to
8816 * interfere with the login process.
8819 if (!strncmp(message, "tellics ", 8)) {
8820 SendToICS(message + 8);
8824 if (!strncmp(message, "tellicsnoalias ", 15)) {
8825 SendToICS(ics_prefix);
8826 SendToICS(message + 15);
8830 /* The following are for backward compatibility only */
8831 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8832 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8833 SendToICS(ics_prefix);
8839 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8840 if(initPing == cps->lastPong) {
8841 if(gameInfo.variant == VariantUnknown) {
8842 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8843 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8844 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8850 if(!strncmp(message, "highlight ", 10)) {
8851 if(appData.testLegality && appData.markers) return;
8852 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8855 if(!strncmp(message, "click ", 6)) {
8856 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8857 if(appData.testLegality || !appData.oneClick) return;
8858 sscanf(message+6, "%c%d%c", &f, &y, &c);
8859 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8860 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8861 x = x*squareSize + (x+1)*lineGap + squareSize/2;
8862 y = y*squareSize + (y+1)*lineGap + squareSize/2;
8863 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8864 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8865 LeftClick(Release, lastLeftX, lastLeftY);
8866 controlKey = (c == ',');
8867 LeftClick(Press, x, y);
8868 LeftClick(Release, x, y);
8869 first.highlight = f;
8873 * If the move is illegal, cancel it and redraw the board.
8874 * Also deal with other error cases. Matching is rather loose
8875 * here to accommodate engines written before the spec.
8877 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8878 strncmp(message, "Error", 5) == 0) {
8879 if (StrStr(message, "name") ||
8880 StrStr(message, "rating") || StrStr(message, "?") ||
8881 StrStr(message, "result") || StrStr(message, "board") ||
8882 StrStr(message, "bk") || StrStr(message, "computer") ||
8883 StrStr(message, "variant") || StrStr(message, "hint") ||
8884 StrStr(message, "random") || StrStr(message, "depth") ||
8885 StrStr(message, "accepted")) {
8888 if (StrStr(message, "protover")) {
8889 /* Program is responding to input, so it's apparently done
8890 initializing, and this error message indicates it is
8891 protocol version 1. So we don't need to wait any longer
8892 for it to initialize and send feature commands. */
8893 FeatureDone(cps, 1);
8894 cps->protocolVersion = 1;
8897 cps->maybeThinking = FALSE;
8899 if (StrStr(message, "draw")) {
8900 /* Program doesn't have "draw" command */
8901 cps->sendDrawOffers = 0;
8904 if (cps->sendTime != 1 &&
8905 (StrStr(message, "time") || StrStr(message, "otim"))) {
8906 /* Program apparently doesn't have "time" or "otim" command */
8910 if (StrStr(message, "analyze")) {
8911 cps->analysisSupport = FALSE;
8912 cps->analyzing = FALSE;
8913 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8914 EditGameEvent(); // [HGM] try to preserve loaded game
8915 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8916 DisplayError(buf2, 0);
8919 if (StrStr(message, "(no matching move)st")) {
8920 /* Special kludge for GNU Chess 4 only */
8921 cps->stKludge = TRUE;
8922 SendTimeControl(cps, movesPerSession, timeControl,
8923 timeIncrement, appData.searchDepth,
8927 if (StrStr(message, "(no matching move)sd")) {
8928 /* Special kludge for GNU Chess 4 only */
8929 cps->sdKludge = TRUE;
8930 SendTimeControl(cps, movesPerSession, timeControl,
8931 timeIncrement, appData.searchDepth,
8935 if (!StrStr(message, "llegal")) {
8938 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8939 gameMode == IcsIdle) return;
8940 if (forwardMostMove <= backwardMostMove) return;
8941 if (pausing) PauseEvent();
8942 if(appData.forceIllegal) {
8943 // [HGM] illegal: machine refused move; force position after move into it
8944 SendToProgram("force\n", cps);
8945 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8946 // we have a real problem now, as SendBoard will use the a2a3 kludge
8947 // when black is to move, while there might be nothing on a2 or black
8948 // might already have the move. So send the board as if white has the move.
8949 // But first we must change the stm of the engine, as it refused the last move
8950 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8951 if(WhiteOnMove(forwardMostMove)) {
8952 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8953 SendBoard(cps, forwardMostMove); // kludgeless board
8955 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8956 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8957 SendBoard(cps, forwardMostMove+1); // kludgeless board
8959 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8960 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8961 gameMode == TwoMachinesPlay)
8962 SendToProgram("go\n", cps);
8965 if (gameMode == PlayFromGameFile) {
8966 /* Stop reading this game file */
8967 gameMode = EditGame;
8970 /* [HGM] illegal-move claim should forfeit game when Xboard */
8971 /* only passes fully legal moves */
8972 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8973 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8974 "False illegal-move claim", GE_XBOARD );
8975 return; // do not take back move we tested as valid
8977 currentMove = forwardMostMove-1;
8978 DisplayMove(currentMove-1); /* before DisplayMoveError */
8979 SwitchClocks(forwardMostMove-1); // [HGM] race
8980 DisplayBothClocks();
8981 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8982 parseList[currentMove], _(cps->which));
8983 DisplayMoveError(buf1);
8984 DrawPosition(FALSE, boards[currentMove]);
8986 SetUserThinkingEnables();
8989 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8990 /* Program has a broken "time" command that
8991 outputs a string not ending in newline.
8997 * If chess program startup fails, exit with an error message.
8998 * Attempts to recover here are futile. [HGM] Well, we try anyway
9000 if ((StrStr(message, "unknown host") != NULL)
9001 || (StrStr(message, "No remote directory") != NULL)
9002 || (StrStr(message, "not found") != NULL)
9003 || (StrStr(message, "No such file") != NULL)
9004 || (StrStr(message, "can't alloc") != NULL)
9005 || (StrStr(message, "Permission denied") != NULL)) {
9007 cps->maybeThinking = FALSE;
9008 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9009 _(cps->which), cps->program, cps->host, message);
9010 RemoveInputSource(cps->isr);
9011 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9012 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9013 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9019 * Look for hint output
9021 if (sscanf(message, "Hint: %s", buf1) == 1) {
9022 if (cps == &first && hintRequested) {
9023 hintRequested = FALSE;
9024 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9025 &fromX, &fromY, &toX, &toY, &promoChar)) {
9026 (void) CoordsToAlgebraic(boards[forwardMostMove],
9027 PosFlags(forwardMostMove),
9028 fromY, fromX, toY, toX, promoChar, buf1);
9029 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9030 DisplayInformation(buf2);
9032 /* Hint move could not be parsed!? */
9033 snprintf(buf2, sizeof(buf2),
9034 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9035 buf1, _(cps->which));
9036 DisplayError(buf2, 0);
9039 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9045 * Ignore other messages if game is not in progress
9047 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9048 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9051 * look for win, lose, draw, or draw offer
9053 if (strncmp(message, "1-0", 3) == 0) {
9054 char *p, *q, *r = "";
9055 p = strchr(message, '{');
9063 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9065 } else if (strncmp(message, "0-1", 3) == 0) {
9066 char *p, *q, *r = "";
9067 p = strchr(message, '{');
9075 /* Kludge for Arasan 4.1 bug */
9076 if (strcmp(r, "Black resigns") == 0) {
9077 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9080 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9082 } else if (strncmp(message, "1/2", 3) == 0) {
9083 char *p, *q, *r = "";
9084 p = strchr(message, '{');
9093 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9096 } else if (strncmp(message, "White resign", 12) == 0) {
9097 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9099 } else if (strncmp(message, "Black resign", 12) == 0) {
9100 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9102 } else if (strncmp(message, "White matches", 13) == 0 ||
9103 strncmp(message, "Black matches", 13) == 0 ) {
9104 /* [HGM] ignore GNUShogi noises */
9106 } else if (strncmp(message, "White", 5) == 0 &&
9107 message[5] != '(' &&
9108 StrStr(message, "Black") == NULL) {
9109 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9111 } else if (strncmp(message, "Black", 5) == 0 &&
9112 message[5] != '(') {
9113 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9115 } else if (strcmp(message, "resign") == 0 ||
9116 strcmp(message, "computer resigns") == 0) {
9118 case MachinePlaysBlack:
9119 case IcsPlayingBlack:
9120 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9122 case MachinePlaysWhite:
9123 case IcsPlayingWhite:
9124 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9126 case TwoMachinesPlay:
9127 if (cps->twoMachinesColor[0] == 'w')
9128 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9130 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9137 } else if (strncmp(message, "opponent mates", 14) == 0) {
9139 case MachinePlaysBlack:
9140 case IcsPlayingBlack:
9141 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9143 case MachinePlaysWhite:
9144 case IcsPlayingWhite:
9145 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9147 case TwoMachinesPlay:
9148 if (cps->twoMachinesColor[0] == 'w')
9149 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9151 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9158 } else if (strncmp(message, "computer mates", 14) == 0) {
9160 case MachinePlaysBlack:
9161 case IcsPlayingBlack:
9162 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9164 case MachinePlaysWhite:
9165 case IcsPlayingWhite:
9166 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9168 case TwoMachinesPlay:
9169 if (cps->twoMachinesColor[0] == 'w')
9170 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9172 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9179 } else if (strncmp(message, "checkmate", 9) == 0) {
9180 if (WhiteOnMove(forwardMostMove)) {
9181 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9183 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9186 } else if (strstr(message, "Draw") != NULL ||
9187 strstr(message, "game is a draw") != NULL) {
9188 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9190 } else if (strstr(message, "offer") != NULL &&
9191 strstr(message, "draw") != NULL) {
9193 if (appData.zippyPlay && first.initDone) {
9194 /* Relay offer to ICS */
9195 SendToICS(ics_prefix);
9196 SendToICS("draw\n");
9199 cps->offeredDraw = 2; /* valid until this engine moves twice */
9200 if (gameMode == TwoMachinesPlay) {
9201 if (cps->other->offeredDraw) {
9202 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9203 /* [HGM] in two-machine mode we delay relaying draw offer */
9204 /* until after we also have move, to see if it is really claim */
9206 } else if (gameMode == MachinePlaysWhite ||
9207 gameMode == MachinePlaysBlack) {
9208 if (userOfferedDraw) {
9209 DisplayInformation(_("Machine accepts your draw offer"));
9210 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9212 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9219 * Look for thinking output
9221 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9222 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9224 int plylev, mvleft, mvtot, curscore, time;
9225 char mvname[MOVE_LEN];
9229 int prefixHint = FALSE;
9230 mvname[0] = NULLCHAR;
9233 case MachinePlaysBlack:
9234 case IcsPlayingBlack:
9235 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9237 case MachinePlaysWhite:
9238 case IcsPlayingWhite:
9239 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9244 case IcsObserving: /* [DM] icsEngineAnalyze */
9245 if (!appData.icsEngineAnalyze) ignore = TRUE;
9247 case TwoMachinesPlay:
9248 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9258 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9260 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9261 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9263 if (plyext != ' ' && plyext != '\t') {
9267 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9268 if( cps->scoreIsAbsolute &&
9269 ( gameMode == MachinePlaysBlack ||
9270 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9271 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9272 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9273 !WhiteOnMove(currentMove)
9276 curscore = -curscore;
9279 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9281 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9284 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9285 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9286 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9287 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9288 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9289 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9293 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9294 DisplayError(_("failed writing PV"), 0);
9297 tempStats.depth = plylev;
9298 tempStats.nodes = nodes;
9299 tempStats.time = time;
9300 tempStats.score = curscore;
9301 tempStats.got_only_move = 0;
9303 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9306 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9307 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9308 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9309 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9310 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9311 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9312 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9313 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9316 /* Buffer overflow protection */
9317 if (pv[0] != NULLCHAR) {
9318 if (strlen(pv) >= sizeof(tempStats.movelist)
9319 && appData.debugMode) {
9321 "PV is too long; using the first %u bytes.\n",
9322 (unsigned) sizeof(tempStats.movelist) - 1);
9325 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9327 sprintf(tempStats.movelist, " no PV\n");
9330 if (tempStats.seen_stat) {
9331 tempStats.ok_to_send = 1;
9334 if (strchr(tempStats.movelist, '(') != NULL) {
9335 tempStats.line_is_book = 1;
9336 tempStats.nr_moves = 0;
9337 tempStats.moves_left = 0;
9339 tempStats.line_is_book = 0;
9342 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9343 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9345 SendProgramStatsToFrontend( cps, &tempStats );
9348 [AS] Protect the thinkOutput buffer from overflow... this
9349 is only useful if buf1 hasn't overflowed first!
9351 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9353 (gameMode == TwoMachinesPlay ?
9354 ToUpper(cps->twoMachinesColor[0]) : ' '),
9355 ((double) curscore) / 100.0,
9356 prefixHint ? lastHint : "",
9357 prefixHint ? " " : "" );
9359 if( buf1[0] != NULLCHAR ) {
9360 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9362 if( strlen(pv) > max_len ) {
9363 if( appData.debugMode) {
9364 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9366 pv[max_len+1] = '\0';
9369 strcat( thinkOutput, pv);
9372 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9373 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9374 DisplayMove(currentMove - 1);
9378 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9379 /* crafty (9.25+) says "(only move) <move>"
9380 * if there is only 1 legal move
9382 sscanf(p, "(only move) %s", buf1);
9383 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9384 sprintf(programStats.movelist, "%s (only move)", buf1);
9385 programStats.depth = 1;
9386 programStats.nr_moves = 1;
9387 programStats.moves_left = 1;
9388 programStats.nodes = 1;
9389 programStats.time = 1;
9390 programStats.got_only_move = 1;
9392 /* Not really, but we also use this member to
9393 mean "line isn't going to change" (Crafty
9394 isn't searching, so stats won't change) */
9395 programStats.line_is_book = 1;
9397 SendProgramStatsToFrontend( cps, &programStats );
9399 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9400 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9401 DisplayMove(currentMove - 1);
9404 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9405 &time, &nodes, &plylev, &mvleft,
9406 &mvtot, mvname) >= 5) {
9407 /* The stat01: line is from Crafty (9.29+) in response
9408 to the "." command */
9409 programStats.seen_stat = 1;
9410 cps->maybeThinking = TRUE;
9412 if (programStats.got_only_move || !appData.periodicUpdates)
9415 programStats.depth = plylev;
9416 programStats.time = time;
9417 programStats.nodes = nodes;
9418 programStats.moves_left = mvleft;
9419 programStats.nr_moves = mvtot;
9420 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9421 programStats.ok_to_send = 1;
9422 programStats.movelist[0] = '\0';
9424 SendProgramStatsToFrontend( cps, &programStats );
9428 } else if (strncmp(message,"++",2) == 0) {
9429 /* Crafty 9.29+ outputs this */
9430 programStats.got_fail = 2;
9433 } else if (strncmp(message,"--",2) == 0) {
9434 /* Crafty 9.29+ outputs this */
9435 programStats.got_fail = 1;
9438 } else if (thinkOutput[0] != NULLCHAR &&
9439 strncmp(message, " ", 4) == 0) {
9440 unsigned message_len;
9443 while (*p && *p == ' ') p++;
9445 message_len = strlen( p );
9447 /* [AS] Avoid buffer overflow */
9448 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9449 strcat(thinkOutput, " ");
9450 strcat(thinkOutput, p);
9453 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9454 strcat(programStats.movelist, " ");
9455 strcat(programStats.movelist, p);
9458 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9459 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9460 DisplayMove(currentMove - 1);
9468 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9469 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9471 ChessProgramStats cpstats;
9473 if (plyext != ' ' && plyext != '\t') {
9477 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9478 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9479 curscore = -curscore;
9482 cpstats.depth = plylev;
9483 cpstats.nodes = nodes;
9484 cpstats.time = time;
9485 cpstats.score = curscore;
9486 cpstats.got_only_move = 0;
9487 cpstats.movelist[0] = '\0';
9489 if (buf1[0] != NULLCHAR) {
9490 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9493 cpstats.ok_to_send = 0;
9494 cpstats.line_is_book = 0;
9495 cpstats.nr_moves = 0;
9496 cpstats.moves_left = 0;
9498 SendProgramStatsToFrontend( cps, &cpstats );
9505 /* Parse a game score from the character string "game", and
9506 record it as the history of the current game. The game
9507 score is NOT assumed to start from the standard position.
9508 The display is not updated in any way.
9511 ParseGameHistory (char *game)
9514 int fromX, fromY, toX, toY, boardIndex;
9519 if (appData.debugMode)
9520 fprintf(debugFP, "Parsing game history: %s\n", game);
9522 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9523 gameInfo.site = StrSave(appData.icsHost);
9524 gameInfo.date = PGNDate();
9525 gameInfo.round = StrSave("-");
9527 /* Parse out names of players */
9528 while (*game == ' ') game++;
9530 while (*game != ' ') *p++ = *game++;
9532 gameInfo.white = StrSave(buf);
9533 while (*game == ' ') game++;
9535 while (*game != ' ' && *game != '\n') *p++ = *game++;
9537 gameInfo.black = StrSave(buf);
9540 boardIndex = blackPlaysFirst ? 1 : 0;
9543 yyboardindex = boardIndex;
9544 moveType = (ChessMove) Myylex();
9546 case IllegalMove: /* maybe suicide chess, etc. */
9547 if (appData.debugMode) {
9548 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9549 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9550 setbuf(debugFP, NULL);
9552 case WhitePromotion:
9553 case BlackPromotion:
9554 case WhiteNonPromotion:
9555 case BlackNonPromotion:
9558 case WhiteCapturesEnPassant:
9559 case BlackCapturesEnPassant:
9560 case WhiteKingSideCastle:
9561 case WhiteQueenSideCastle:
9562 case BlackKingSideCastle:
9563 case BlackQueenSideCastle:
9564 case WhiteKingSideCastleWild:
9565 case WhiteQueenSideCastleWild:
9566 case BlackKingSideCastleWild:
9567 case BlackQueenSideCastleWild:
9569 case WhiteHSideCastleFR:
9570 case WhiteASideCastleFR:
9571 case BlackHSideCastleFR:
9572 case BlackASideCastleFR:
9574 fromX = currentMoveString[0] - AAA;
9575 fromY = currentMoveString[1] - ONE;
9576 toX = currentMoveString[2] - AAA;
9577 toY = currentMoveString[3] - ONE;
9578 promoChar = currentMoveString[4];
9582 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9583 fromX = moveType == WhiteDrop ?
9584 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9585 (int) CharToPiece(ToLower(currentMoveString[0]));
9587 toX = currentMoveString[2] - AAA;
9588 toY = currentMoveString[3] - ONE;
9589 promoChar = NULLCHAR;
9593 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9594 if (appData.debugMode) {
9595 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9596 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9597 setbuf(debugFP, NULL);
9599 DisplayError(buf, 0);
9601 case ImpossibleMove:
9603 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9604 if (appData.debugMode) {
9605 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9606 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9607 setbuf(debugFP, NULL);
9609 DisplayError(buf, 0);
9612 if (boardIndex < backwardMostMove) {
9613 /* Oops, gap. How did that happen? */
9614 DisplayError(_("Gap in move list"), 0);
9617 backwardMostMove = blackPlaysFirst ? 1 : 0;
9618 if (boardIndex > forwardMostMove) {
9619 forwardMostMove = boardIndex;
9623 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9624 strcat(parseList[boardIndex-1], " ");
9625 strcat(parseList[boardIndex-1], yy_text);
9637 case GameUnfinished:
9638 if (gameMode == IcsExamining) {
9639 if (boardIndex < backwardMostMove) {
9640 /* Oops, gap. How did that happen? */
9643 backwardMostMove = blackPlaysFirst ? 1 : 0;
9646 gameInfo.result = moveType;
9647 p = strchr(yy_text, '{');
9648 if (p == NULL) p = strchr(yy_text, '(');
9651 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9653 q = strchr(p, *p == '{' ? '}' : ')');
9654 if (q != NULL) *q = NULLCHAR;
9657 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9658 gameInfo.resultDetails = StrSave(p);
9661 if (boardIndex >= forwardMostMove &&
9662 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9663 backwardMostMove = blackPlaysFirst ? 1 : 0;
9666 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9667 fromY, fromX, toY, toX, promoChar,
9668 parseList[boardIndex]);
9669 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9670 /* currentMoveString is set as a side-effect of yylex */
9671 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9672 strcat(moveList[boardIndex], "\n");
9674 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9675 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9681 if(gameInfo.variant != VariantShogi)
9682 strcat(parseList[boardIndex - 1], "+");
9686 strcat(parseList[boardIndex - 1], "#");
9693 /* Apply a move to the given board */
9695 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9697 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9698 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9700 /* [HGM] compute & store e.p. status and castling rights for new position */
9701 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9703 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9704 oldEP = (signed char)board[EP_STATUS];
9705 board[EP_STATUS] = EP_NONE;
9707 if (fromY == DROP_RANK) {
9709 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9710 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9713 piece = board[toY][toX] = (ChessSquare) fromX;
9718 if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9719 victim = board[killY][killX],
9720 board[killY][killX] = EmptySquare,
9721 board[EP_STATUS] = EP_CAPTURE;
9723 if( board[toY][toX] != EmptySquare ) {
9724 board[EP_STATUS] = EP_CAPTURE;
9725 if( (fromX != toX || fromY != toY) && // not igui!
9726 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9727 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
9728 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9732 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9733 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9734 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9736 if( board[fromY][fromX] == WhitePawn ) {
9737 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9738 board[EP_STATUS] = EP_PAWN_MOVE;
9740 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9741 gameInfo.variant != VariantBerolina || toX < fromX)
9742 board[EP_STATUS] = toX | berolina;
9743 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9744 gameInfo.variant != VariantBerolina || toX > fromX)
9745 board[EP_STATUS] = toX;
9748 if( board[fromY][fromX] == BlackPawn ) {
9749 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9750 board[EP_STATUS] = EP_PAWN_MOVE;
9751 if( toY-fromY== -2) {
9752 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9753 gameInfo.variant != VariantBerolina || toX < fromX)
9754 board[EP_STATUS] = toX | berolina;
9755 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9756 gameInfo.variant != VariantBerolina || toX > fromX)
9757 board[EP_STATUS] = toX;
9761 for(i=0; i<nrCastlingRights; i++) {
9762 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9763 board[CASTLING][i] == toX && castlingRank[i] == toY
9764 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9767 if(gameInfo.variant == VariantSChess) { // update virginity
9768 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9769 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9770 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9771 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9774 if (fromX == toX && fromY == toY) return;
9776 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9777 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9778 if(gameInfo.variant == VariantKnightmate)
9779 king += (int) WhiteUnicorn - (int) WhiteKing;
9781 /* Code added by Tord: */
9782 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9783 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9784 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9785 board[fromY][fromX] = EmptySquare;
9786 board[toY][toX] = EmptySquare;
9787 if((toX > fromX) != (piece == WhiteRook)) {
9788 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9790 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9792 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9793 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9794 board[fromY][fromX] = EmptySquare;
9795 board[toY][toX] = EmptySquare;
9796 if((toX > fromX) != (piece == BlackRook)) {
9797 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9799 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9801 /* End of code added by Tord */
9803 } else if (board[fromY][fromX] == king
9804 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9805 && toY == fromY && toX > fromX+1) {
9806 board[fromY][fromX] = EmptySquare;
9807 board[toY][toX] = king;
9808 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9809 board[fromY][BOARD_RGHT-1] = EmptySquare;
9810 } else if (board[fromY][fromX] == king
9811 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9812 && toY == fromY && toX < fromX-1) {
9813 board[fromY][fromX] = EmptySquare;
9814 board[toY][toX] = king;
9815 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9816 board[fromY][BOARD_LEFT] = EmptySquare;
9817 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9818 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9819 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9821 /* white pawn promotion */
9822 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9823 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9824 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9825 board[fromY][fromX] = EmptySquare;
9826 } else if ((fromY >= BOARD_HEIGHT>>1)
9827 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9829 && gameInfo.variant != VariantXiangqi
9830 && gameInfo.variant != VariantBerolina
9831 && (board[fromY][fromX] == WhitePawn)
9832 && (board[toY][toX] == EmptySquare)) {
9833 board[fromY][fromX] = EmptySquare;
9834 board[toY][toX] = WhitePawn;
9835 captured = board[toY - 1][toX];
9836 board[toY - 1][toX] = EmptySquare;
9837 } else if ((fromY == BOARD_HEIGHT-4)
9839 && gameInfo.variant == VariantBerolina
9840 && (board[fromY][fromX] == WhitePawn)
9841 && (board[toY][toX] == EmptySquare)) {
9842 board[fromY][fromX] = EmptySquare;
9843 board[toY][toX] = WhitePawn;
9844 if(oldEP & EP_BEROLIN_A) {
9845 captured = board[fromY][fromX-1];
9846 board[fromY][fromX-1] = EmptySquare;
9847 }else{ captured = board[fromY][fromX+1];
9848 board[fromY][fromX+1] = EmptySquare;
9850 } else if (board[fromY][fromX] == king
9851 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9852 && toY == fromY && toX > fromX+1) {
9853 board[fromY][fromX] = EmptySquare;
9854 board[toY][toX] = king;
9855 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9856 board[fromY][BOARD_RGHT-1] = EmptySquare;
9857 } else if (board[fromY][fromX] == king
9858 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9859 && toY == fromY && toX < fromX-1) {
9860 board[fromY][fromX] = EmptySquare;
9861 board[toY][toX] = king;
9862 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9863 board[fromY][BOARD_LEFT] = EmptySquare;
9864 } else if (fromY == 7 && fromX == 3
9865 && board[fromY][fromX] == BlackKing
9866 && toY == 7 && toX == 5) {
9867 board[fromY][fromX] = EmptySquare;
9868 board[toY][toX] = BlackKing;
9869 board[fromY][7] = EmptySquare;
9870 board[toY][4] = BlackRook;
9871 } else if (fromY == 7 && fromX == 3
9872 && board[fromY][fromX] == BlackKing
9873 && toY == 7 && toX == 1) {
9874 board[fromY][fromX] = EmptySquare;
9875 board[toY][toX] = BlackKing;
9876 board[fromY][0] = EmptySquare;
9877 board[toY][2] = BlackRook;
9878 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9879 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9880 && toY < promoRank && promoChar
9882 /* black pawn promotion */
9883 board[toY][toX] = CharToPiece(ToLower(promoChar));
9884 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9885 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9886 board[fromY][fromX] = EmptySquare;
9887 } else if ((fromY < BOARD_HEIGHT>>1)
9888 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9890 && gameInfo.variant != VariantXiangqi
9891 && gameInfo.variant != VariantBerolina
9892 && (board[fromY][fromX] == BlackPawn)
9893 && (board[toY][toX] == EmptySquare)) {
9894 board[fromY][fromX] = EmptySquare;
9895 board[toY][toX] = BlackPawn;
9896 captured = board[toY + 1][toX];
9897 board[toY + 1][toX] = EmptySquare;
9898 } else if ((fromY == 3)
9900 && gameInfo.variant == VariantBerolina
9901 && (board[fromY][fromX] == BlackPawn)
9902 && (board[toY][toX] == EmptySquare)) {
9903 board[fromY][fromX] = EmptySquare;
9904 board[toY][toX] = BlackPawn;
9905 if(oldEP & EP_BEROLIN_A) {
9906 captured = board[fromY][fromX-1];
9907 board[fromY][fromX-1] = EmptySquare;
9908 }else{ captured = board[fromY][fromX+1];
9909 board[fromY][fromX+1] = EmptySquare;
9912 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9913 board[fromY][fromX] = EmptySquare;
9914 board[toY][toX] = piece;
9918 if (gameInfo.holdingsWidth != 0) {
9920 /* !!A lot more code needs to be written to support holdings */
9921 /* [HGM] OK, so I have written it. Holdings are stored in the */
9922 /* penultimate board files, so they are automaticlly stored */
9923 /* in the game history. */
9924 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9925 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9926 /* Delete from holdings, by decreasing count */
9927 /* and erasing image if necessary */
9928 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9929 if(p < (int) BlackPawn) { /* white drop */
9930 p -= (int)WhitePawn;
9931 p = PieceToNumber((ChessSquare)p);
9932 if(p >= gameInfo.holdingsSize) p = 0;
9933 if(--board[p][BOARD_WIDTH-2] <= 0)
9934 board[p][BOARD_WIDTH-1] = EmptySquare;
9935 if((int)board[p][BOARD_WIDTH-2] < 0)
9936 board[p][BOARD_WIDTH-2] = 0;
9937 } else { /* black drop */
9938 p -= (int)BlackPawn;
9939 p = PieceToNumber((ChessSquare)p);
9940 if(p >= gameInfo.holdingsSize) p = 0;
9941 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9942 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9943 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9944 board[BOARD_HEIGHT-1-p][1] = 0;
9947 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9948 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9949 /* [HGM] holdings: Add to holdings, if holdings exist */
9950 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9951 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9952 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9955 if (p >= (int) BlackPawn) {
9956 p -= (int)BlackPawn;
9957 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9958 /* in Shogi restore piece to its original first */
9959 captured = (ChessSquare) (DEMOTED captured);
9962 p = PieceToNumber((ChessSquare)p);
9963 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9964 board[p][BOARD_WIDTH-2]++;
9965 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9967 p -= (int)WhitePawn;
9968 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9969 captured = (ChessSquare) (DEMOTED captured);
9972 p = PieceToNumber((ChessSquare)p);
9973 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9974 board[BOARD_HEIGHT-1-p][1]++;
9975 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9978 } else if (gameInfo.variant == VariantAtomic) {
9979 if (captured != EmptySquare) {
9981 for (y = toY-1; y <= toY+1; y++) {
9982 for (x = toX-1; x <= toX+1; x++) {
9983 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9984 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9985 board[y][x] = EmptySquare;
9989 board[toY][toX] = EmptySquare;
9992 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9993 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9995 if(promoChar == '+') {
9996 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9997 board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9998 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
9999 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10000 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10001 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10002 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10003 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10004 board[toY][toX] = newPiece;
10006 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10007 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10008 // [HGM] superchess: take promotion piece out of holdings
10009 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10010 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10011 if(!--board[k][BOARD_WIDTH-2])
10012 board[k][BOARD_WIDTH-1] = EmptySquare;
10014 if(!--board[BOARD_HEIGHT-1-k][1])
10015 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10020 /* Updates forwardMostMove */
10022 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10024 int x = toX, y = toY;
10025 char *s = parseList[forwardMostMove];
10026 ChessSquare p = boards[forwardMostMove][toY][toX];
10027 // forwardMostMove++; // [HGM] bare: moved downstream
10029 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10030 (void) CoordsToAlgebraic(boards[forwardMostMove],
10031 PosFlags(forwardMostMove),
10032 fromY, fromX, y, x, promoChar,
10034 if(killX >= 0 && killY >= 0)
10035 sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10037 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10038 int timeLeft; static int lastLoadFlag=0; int king, piece;
10039 piece = boards[forwardMostMove][fromY][fromX];
10040 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10041 if(gameInfo.variant == VariantKnightmate)
10042 king += (int) WhiteUnicorn - (int) WhiteKing;
10043 if(forwardMostMove == 0) {
10044 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10045 fprintf(serverMoves, "%s;", UserName());
10046 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10047 fprintf(serverMoves, "%s;", second.tidy);
10048 fprintf(serverMoves, "%s;", first.tidy);
10049 if(gameMode == MachinePlaysWhite)
10050 fprintf(serverMoves, "%s;", UserName());
10051 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10052 fprintf(serverMoves, "%s;", second.tidy);
10053 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10054 lastLoadFlag = loadFlag;
10056 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10057 // print castling suffix
10058 if( toY == fromY && piece == king ) {
10060 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10062 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10065 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10066 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10067 boards[forwardMostMove][toY][toX] == EmptySquare
10068 && fromX != toX && fromY != toY)
10069 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10070 // promotion suffix
10071 if(promoChar != NULLCHAR) {
10072 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10073 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10074 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10075 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10078 char buf[MOVE_LEN*2], *p; int len;
10079 fprintf(serverMoves, "/%d/%d",
10080 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10081 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10082 else timeLeft = blackTimeRemaining/1000;
10083 fprintf(serverMoves, "/%d", timeLeft);
10084 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10085 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10086 if(p = strchr(buf, '=')) *p = NULLCHAR;
10087 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10088 fprintf(serverMoves, "/%s", buf);
10090 fflush(serverMoves);
10093 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10094 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10097 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10098 if (commentList[forwardMostMove+1] != NULL) {
10099 free(commentList[forwardMostMove+1]);
10100 commentList[forwardMostMove+1] = NULL;
10102 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10103 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10104 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10105 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10106 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10107 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10108 adjustedClock = FALSE;
10109 gameInfo.result = GameUnfinished;
10110 if (gameInfo.resultDetails != NULL) {
10111 free(gameInfo.resultDetails);
10112 gameInfo.resultDetails = NULL;
10114 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10115 moveList[forwardMostMove - 1]);
10116 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10122 if(gameInfo.variant != VariantShogi)
10123 strcat(parseList[forwardMostMove - 1], "+");
10127 strcat(parseList[forwardMostMove - 1], "#");
10132 /* Updates currentMove if not pausing */
10134 ShowMove (int fromX, int fromY, int toX, int toY)
10136 int instant = (gameMode == PlayFromGameFile) ?
10137 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10138 if(appData.noGUI) return;
10139 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10141 if (forwardMostMove == currentMove + 1) {
10142 AnimateMove(boards[forwardMostMove - 1],
10143 fromX, fromY, toX, toY);
10146 currentMove = forwardMostMove;
10149 killX = killY = -1; // [HGM] lion: used up
10151 if (instant) return;
10153 DisplayMove(currentMove - 1);
10154 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10155 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10156 SetHighlights(fromX, fromY, toX, toY);
10159 DrawPosition(FALSE, boards[currentMove]);
10160 DisplayBothClocks();
10161 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10165 SendEgtPath (ChessProgramState *cps)
10166 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10167 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10169 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10172 char c, *q = name+1, *r, *s;
10174 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10175 while(*p && *p != ',') *q++ = *p++;
10176 *q++ = ':'; *q = 0;
10177 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10178 strcmp(name, ",nalimov:") == 0 ) {
10179 // take nalimov path from the menu-changeable option first, if it is defined
10180 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10181 SendToProgram(buf,cps); // send egtbpath command for nalimov
10183 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10184 (s = StrStr(appData.egtFormats, name)) != NULL) {
10185 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10186 s = r = StrStr(s, ":") + 1; // beginning of path info
10187 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10188 c = *r; *r = 0; // temporarily null-terminate path info
10189 *--q = 0; // strip of trailig ':' from name
10190 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10192 SendToProgram(buf,cps); // send egtbpath command for this format
10194 if(*p == ',') p++; // read away comma to position for next format name
10199 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10201 int width = 8, height = 8, holdings = 0; // most common sizes
10202 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10203 // correct the deviations default for each variant
10204 if( v == VariantXiangqi ) width = 9, height = 10;
10205 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10206 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10207 if( v == VariantCapablanca || v == VariantCapaRandom ||
10208 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10210 if( v == VariantCourier ) width = 12;
10211 if( v == VariantSuper ) holdings = 8;
10212 if( v == VariantGreat ) width = 10, holdings = 8;
10213 if( v == VariantSChess ) holdings = 7;
10214 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10215 if( v == VariantChuChess) width = 10, height = 10;
10216 if( v == VariantChu ) width = 12, height = 12;
10217 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10218 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10219 holdingsSize >= 0 && holdingsSize != holdings;
10222 char variantError[MSG_SIZ];
10225 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10226 { // returns error message (recognizable by upper-case) if engine does not support the variant
10227 char *p, *variant = VariantName(v);
10228 static char b[MSG_SIZ];
10229 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10230 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10231 holdingsSize, variant); // cook up sized variant name
10232 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10233 if(StrStr(list, b) == NULL) {
10234 // specific sized variant not known, check if general sizing allowed
10235 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10236 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10237 boardWidth, boardHeight, holdingsSize, engine);
10240 /* [HGM] here we really should compare with the maximum supported board size */
10242 } else snprintf(b, MSG_SIZ,"%s", variant);
10243 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10244 p = StrStr(list, b);
10245 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10247 // occurs not at all in list, or only as sub-string
10248 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10249 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10250 int l = strlen(variantError);
10252 while(p != list && p[-1] != ',') p--;
10253 q = strchr(p, ',');
10254 if(q) *q = NULLCHAR;
10255 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10264 InitChessProgram (ChessProgramState *cps, int setup)
10265 /* setup needed to setup FRC opening position */
10267 char buf[MSG_SIZ], *b;
10268 if (appData.noChessProgram) return;
10269 hintRequested = FALSE;
10270 bookRequested = FALSE;
10272 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10273 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10274 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10275 if(cps->memSize) { /* [HGM] memory */
10276 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10277 SendToProgram(buf, cps);
10279 SendEgtPath(cps); /* [HGM] EGT */
10280 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10281 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10282 SendToProgram(buf, cps);
10285 SendToProgram(cps->initString, cps);
10286 if (gameInfo.variant != VariantNormal &&
10287 gameInfo.variant != VariantLoadable
10288 /* [HGM] also send variant if board size non-standard */
10289 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10291 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10292 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10294 DisplayFatalError(variantError, 0, 1);
10298 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10299 SendToProgram(buf, cps);
10301 currentlyInitializedVariant = gameInfo.variant;
10303 /* [HGM] send opening position in FRC to first engine */
10305 SendToProgram("force\n", cps);
10307 /* engine is now in force mode! Set flag to wake it up after first move. */
10308 setboardSpoiledMachineBlack = 1;
10311 if (cps->sendICS) {
10312 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10313 SendToProgram(buf, cps);
10315 cps->maybeThinking = FALSE;
10316 cps->offeredDraw = 0;
10317 if (!appData.icsActive) {
10318 SendTimeControl(cps, movesPerSession, timeControl,
10319 timeIncrement, appData.searchDepth,
10322 if (appData.showThinking
10323 // [HGM] thinking: four options require thinking output to be sent
10324 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10326 SendToProgram("post\n", cps);
10328 SendToProgram("hard\n", cps);
10329 if (!appData.ponderNextMove) {
10330 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10331 it without being sure what state we are in first. "hard"
10332 is not a toggle, so that one is OK.
10334 SendToProgram("easy\n", cps);
10336 if (cps->usePing) {
10337 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10338 SendToProgram(buf, cps);
10340 cps->initDone = TRUE;
10341 ClearEngineOutputPane(cps == &second);
10346 ResendOptions (ChessProgramState *cps)
10347 { // send the stored value of the options
10350 Option *opt = cps->option;
10351 for(i=0; i<cps->nrOptions; i++, opt++) {
10352 switch(opt->type) {
10356 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10359 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10362 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10368 SendToProgram(buf, cps);
10373 StartChessProgram (ChessProgramState *cps)
10378 if (appData.noChessProgram) return;
10379 cps->initDone = FALSE;
10381 if (strcmp(cps->host, "localhost") == 0) {
10382 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10383 } else if (*appData.remoteShell == NULLCHAR) {
10384 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10386 if (*appData.remoteUser == NULLCHAR) {
10387 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10390 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10391 cps->host, appData.remoteUser, cps->program);
10393 err = StartChildProcess(buf, "", &cps->pr);
10397 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10398 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10399 if(cps != &first) return;
10400 appData.noChessProgram = TRUE;
10403 // DisplayFatalError(buf, err, 1);
10404 // cps->pr = NoProc;
10405 // cps->isr = NULL;
10409 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10410 if (cps->protocolVersion > 1) {
10411 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10412 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10413 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10414 cps->comboCnt = 0; // and values of combo boxes
10416 SendToProgram(buf, cps);
10417 if(cps->reload) ResendOptions(cps);
10419 SendToProgram("xboard\n", cps);
10424 TwoMachinesEventIfReady P((void))
10426 static int curMess = 0;
10427 if (first.lastPing != first.lastPong) {
10428 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10429 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10432 if (second.lastPing != second.lastPong) {
10433 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10434 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10437 DisplayMessage("", ""); curMess = 0;
10438 TwoMachinesEvent();
10442 MakeName (char *template)
10446 static char buf[MSG_SIZ];
10450 clock = time((time_t *)NULL);
10451 tm = localtime(&clock);
10453 while(*p++ = *template++) if(p[-1] == '%') {
10454 switch(*template++) {
10455 case 0: *p = 0; return buf;
10456 case 'Y': i = tm->tm_year+1900; break;
10457 case 'y': i = tm->tm_year-100; break;
10458 case 'M': i = tm->tm_mon+1; break;
10459 case 'd': i = tm->tm_mday; break;
10460 case 'h': i = tm->tm_hour; break;
10461 case 'm': i = tm->tm_min; break;
10462 case 's': i = tm->tm_sec; break;
10465 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10471 CountPlayers (char *p)
10474 while(p = strchr(p, '\n')) p++, n++; // count participants
10479 WriteTourneyFile (char *results, FILE *f)
10480 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10481 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10482 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10483 // create a file with tournament description
10484 fprintf(f, "-participants {%s}\n", appData.participants);
10485 fprintf(f, "-seedBase %d\n", appData.seedBase);
10486 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10487 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10488 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10489 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10490 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10491 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10492 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10493 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10494 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10495 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10496 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10497 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10498 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10499 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10500 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10501 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10502 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10503 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10504 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10505 fprintf(f, "-smpCores %d\n", appData.smpCores);
10507 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10509 fprintf(f, "-mps %d\n", appData.movesPerSession);
10510 fprintf(f, "-tc %s\n", appData.timeControl);
10511 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10513 fprintf(f, "-results \"%s\"\n", results);
10518 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10521 Substitute (char *participants, int expunge)
10523 int i, changed, changes=0, nPlayers=0;
10524 char *p, *q, *r, buf[MSG_SIZ];
10525 if(participants == NULL) return;
10526 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10527 r = p = participants; q = appData.participants;
10528 while(*p && *p == *q) {
10529 if(*p == '\n') r = p+1, nPlayers++;
10532 if(*p) { // difference
10533 while(*p && *p++ != '\n');
10534 while(*q && *q++ != '\n');
10535 changed = nPlayers;
10536 changes = 1 + (strcmp(p, q) != 0);
10538 if(changes == 1) { // a single engine mnemonic was changed
10539 q = r; while(*q) nPlayers += (*q++ == '\n');
10540 p = buf; while(*r && (*p = *r++) != '\n') p++;
10542 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10543 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10544 if(mnemonic[i]) { // The substitute is valid
10546 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10547 flock(fileno(f), LOCK_EX);
10548 ParseArgsFromFile(f);
10549 fseek(f, 0, SEEK_SET);
10550 FREE(appData.participants); appData.participants = participants;
10551 if(expunge) { // erase results of replaced engine
10552 int len = strlen(appData.results), w, b, dummy;
10553 for(i=0; i<len; i++) {
10554 Pairing(i, nPlayers, &w, &b, &dummy);
10555 if((w == changed || b == changed) && appData.results[i] == '*') {
10556 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10561 for(i=0; i<len; i++) {
10562 Pairing(i, nPlayers, &w, &b, &dummy);
10563 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10566 WriteTourneyFile(appData.results, f);
10567 fclose(f); // release lock
10570 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10572 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10573 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10574 free(participants);
10579 CheckPlayers (char *participants)
10582 char buf[MSG_SIZ], *p;
10583 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10584 while(p = strchr(participants, '\n')) {
10586 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10588 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10590 DisplayError(buf, 0);
10594 participants = p + 1;
10600 CreateTourney (char *name)
10603 if(matchMode && strcmp(name, appData.tourneyFile)) {
10604 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10606 if(name[0] == NULLCHAR) {
10607 if(appData.participants[0])
10608 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10611 f = fopen(name, "r");
10612 if(f) { // file exists
10613 ASSIGN(appData.tourneyFile, name);
10614 ParseArgsFromFile(f); // parse it
10616 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10617 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10618 DisplayError(_("Not enough participants"), 0);
10621 if(CheckPlayers(appData.participants)) return 0;
10622 ASSIGN(appData.tourneyFile, name);
10623 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10624 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10627 appData.noChessProgram = FALSE;
10628 appData.clockMode = TRUE;
10634 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10636 char buf[MSG_SIZ], *p, *q;
10637 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10638 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10639 skip = !all && group[0]; // if group requested, we start in skip mode
10640 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10641 p = names; q = buf; header = 0;
10642 while(*p && *p != '\n') *q++ = *p++;
10644 if(*p == '\n') p++;
10645 if(buf[0] == '#') {
10646 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10647 depth++; // we must be entering a new group
10648 if(all) continue; // suppress printing group headers when complete list requested
10650 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10652 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10653 if(engineList[i]) free(engineList[i]);
10654 engineList[i] = strdup(buf);
10655 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10656 if(engineMnemonic[i]) free(engineMnemonic[i]);
10657 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10659 sscanf(q + 8, "%s", buf + strlen(buf));
10662 engineMnemonic[i] = strdup(buf);
10665 engineList[i] = engineMnemonic[i] = NULL;
10669 // following implemented as macro to avoid type limitations
10670 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10673 SwapEngines (int n)
10674 { // swap settings for first engine and other engine (so far only some selected options)
10679 SWAP(chessProgram, p)
10681 SWAP(hasOwnBookUCI, h)
10682 SWAP(protocolVersion, h)
10684 SWAP(scoreIsAbsolute, h)
10689 SWAP(engOptions, p)
10690 SWAP(engInitString, p)
10691 SWAP(computerString, p)
10693 SWAP(fenOverride, p)
10695 SWAP(accumulateTC, h)
10700 GetEngineLine (char *s, int n)
10704 extern char *icsNames;
10705 if(!s || !*s) return 0;
10706 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10707 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10708 if(!mnemonic[i]) return 0;
10709 if(n == 11) return 1; // just testing if there was a match
10710 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10711 if(n == 1) SwapEngines(n);
10712 ParseArgsFromString(buf);
10713 if(n == 1) SwapEngines(n);
10714 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10715 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10716 ParseArgsFromString(buf);
10722 SetPlayer (int player, char *p)
10723 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10725 char buf[MSG_SIZ], *engineName;
10726 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10727 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10728 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10730 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10731 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10732 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10733 ParseArgsFromString(buf);
10734 } else { // no engine with this nickname is installed!
10735 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10736 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10737 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10739 DisplayError(buf, 0);
10746 char *recentEngines;
10749 RecentEngineEvent (int nr)
10752 // SwapEngines(1); // bump first to second
10753 // ReplaceEngine(&second, 1); // and load it there
10754 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10755 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10756 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10757 ReplaceEngine(&first, 0);
10758 FloatToFront(&appData.recentEngineList, command[n]);
10763 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10764 { // determine players from game number
10765 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10767 if(appData.tourneyType == 0) {
10768 roundsPerCycle = (nPlayers - 1) | 1;
10769 pairingsPerRound = nPlayers / 2;
10770 } else if(appData.tourneyType > 0) {
10771 roundsPerCycle = nPlayers - appData.tourneyType;
10772 pairingsPerRound = appData.tourneyType;
10774 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10775 gamesPerCycle = gamesPerRound * roundsPerCycle;
10776 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10777 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10778 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10779 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10780 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10781 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10783 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10784 if(appData.roundSync) *syncInterval = gamesPerRound;
10786 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10788 if(appData.tourneyType == 0) {
10789 if(curPairing == (nPlayers-1)/2 ) {
10790 *whitePlayer = curRound;
10791 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10793 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10794 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10795 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10796 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10798 } else if(appData.tourneyType > 1) {
10799 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10800 *whitePlayer = curRound + appData.tourneyType;
10801 } else if(appData.tourneyType > 0) {
10802 *whitePlayer = curPairing;
10803 *blackPlayer = curRound + appData.tourneyType;
10806 // take care of white/black alternation per round.
10807 // For cycles and games this is already taken care of by default, derived from matchGame!
10808 return curRound & 1;
10812 NextTourneyGame (int nr, int *swapColors)
10813 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10815 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10817 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10818 tf = fopen(appData.tourneyFile, "r");
10819 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10820 ParseArgsFromFile(tf); fclose(tf);
10821 InitTimeControls(); // TC might be altered from tourney file
10823 nPlayers = CountPlayers(appData.participants); // count participants
10824 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10825 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10828 p = q = appData.results;
10829 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10830 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10831 DisplayMessage(_("Waiting for other game(s)"),"");
10832 waitingForGame = TRUE;
10833 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10836 waitingForGame = FALSE;
10839 if(appData.tourneyType < 0) {
10840 if(nr>=0 && !pairingReceived) {
10842 if(pairing.pr == NoProc) {
10843 if(!appData.pairingEngine[0]) {
10844 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10847 StartChessProgram(&pairing); // starts the pairing engine
10849 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10850 SendToProgram(buf, &pairing);
10851 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10852 SendToProgram(buf, &pairing);
10853 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10855 pairingReceived = 0; // ... so we continue here
10857 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10858 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10859 matchGame = 1; roundNr = nr / syncInterval + 1;
10862 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10864 // redefine engines, engine dir, etc.
10865 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10866 if(first.pr == NoProc) {
10867 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10868 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10870 if(second.pr == NoProc) {
10872 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10873 SwapEngines(1); // and make that valid for second engine by swapping
10874 InitEngine(&second, 1);
10876 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10877 UpdateLogos(FALSE); // leave display to ModeHiglight()
10883 { // performs game initialization that does not invoke engines, and then tries to start the game
10884 int res, firstWhite, swapColors = 0;
10885 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10886 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
10888 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10889 if(strcmp(buf, currentDebugFile)) { // name has changed
10890 FILE *f = fopen(buf, "w");
10891 if(f) { // if opening the new file failed, just keep using the old one
10892 ASSIGN(currentDebugFile, buf);
10896 if(appData.serverFileName) {
10897 if(serverFP) fclose(serverFP);
10898 serverFP = fopen(appData.serverFileName, "w");
10899 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10900 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10904 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10905 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10906 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10907 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10908 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10909 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10910 Reset(FALSE, first.pr != NoProc);
10911 res = LoadGameOrPosition(matchGame); // setup game
10912 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10913 if(!res) return; // abort when bad game/pos file
10914 TwoMachinesEvent();
10918 UserAdjudicationEvent (int result)
10920 ChessMove gameResult = GameIsDrawn;
10923 gameResult = WhiteWins;
10925 else if( result < 0 ) {
10926 gameResult = BlackWins;
10929 if( gameMode == TwoMachinesPlay ) {
10930 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10935 // [HGM] save: calculate checksum of game to make games easily identifiable
10937 StringCheckSum (char *s)
10940 if(s==NULL) return 0;
10941 while(*s) i = i*259 + *s++;
10949 for(i=backwardMostMove; i<forwardMostMove; i++) {
10950 sum += pvInfoList[i].depth;
10951 sum += StringCheckSum(parseList[i]);
10952 sum += StringCheckSum(commentList[i]);
10955 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10956 return sum + StringCheckSum(commentList[i]);
10957 } // end of save patch
10960 GameEnds (ChessMove result, char *resultDetails, int whosays)
10962 GameMode nextGameMode;
10964 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10966 if(endingGame) return; /* [HGM] crash: forbid recursion */
10968 if(twoBoards) { // [HGM] dual: switch back to one board
10969 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10970 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10972 if (appData.debugMode) {
10973 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10974 result, resultDetails ? resultDetails : "(null)", whosays);
10977 fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10979 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10981 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10982 /* If we are playing on ICS, the server decides when the
10983 game is over, but the engine can offer to draw, claim
10987 if (appData.zippyPlay && first.initDone) {
10988 if (result == GameIsDrawn) {
10989 /* In case draw still needs to be claimed */
10990 SendToICS(ics_prefix);
10991 SendToICS("draw\n");
10992 } else if (StrCaseStr(resultDetails, "resign")) {
10993 SendToICS(ics_prefix);
10994 SendToICS("resign\n");
10998 endingGame = 0; /* [HGM] crash */
11002 /* If we're loading the game from a file, stop */
11003 if (whosays == GE_FILE) {
11004 (void) StopLoadGameTimer();
11008 /* Cancel draw offers */
11009 first.offeredDraw = second.offeredDraw = 0;
11011 /* If this is an ICS game, only ICS can really say it's done;
11012 if not, anyone can. */
11013 isIcsGame = (gameMode == IcsPlayingWhite ||
11014 gameMode == IcsPlayingBlack ||
11015 gameMode == IcsObserving ||
11016 gameMode == IcsExamining);
11018 if (!isIcsGame || whosays == GE_ICS) {
11019 /* OK -- not an ICS game, or ICS said it was done */
11021 if (!isIcsGame && !appData.noChessProgram)
11022 SetUserThinkingEnables();
11024 /* [HGM] if a machine claims the game end we verify this claim */
11025 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11026 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11028 ChessMove trueResult = (ChessMove) -1;
11030 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11031 first.twoMachinesColor[0] :
11032 second.twoMachinesColor[0] ;
11034 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11035 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11036 /* [HGM] verify: engine mate claims accepted if they were flagged */
11037 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11039 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11040 /* [HGM] verify: engine mate claims accepted if they were flagged */
11041 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11043 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11044 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11047 // now verify win claims, but not in drop games, as we don't understand those yet
11048 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11049 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11050 (result == WhiteWins && claimer == 'w' ||
11051 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11052 if (appData.debugMode) {
11053 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11054 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11056 if(result != trueResult) {
11057 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11058 result = claimer == 'w' ? BlackWins : WhiteWins;
11059 resultDetails = buf;
11062 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11063 && (forwardMostMove <= backwardMostMove ||
11064 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11065 (claimer=='b')==(forwardMostMove&1))
11067 /* [HGM] verify: draws that were not flagged are false claims */
11068 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11069 result = claimer == 'w' ? BlackWins : WhiteWins;
11070 resultDetails = buf;
11072 /* (Claiming a loss is accepted no questions asked!) */
11073 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11074 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11075 result = GameUnfinished;
11076 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11078 /* [HGM] bare: don't allow bare King to win */
11079 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11080 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11081 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11082 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11083 && result != GameIsDrawn)
11084 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11085 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11086 int p = (signed char)boards[forwardMostMove][i][j] - color;
11087 if(p >= 0 && p <= (int)WhiteKing) k++;
11089 if (appData.debugMode) {
11090 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11091 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11094 result = GameIsDrawn;
11095 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11096 resultDetails = buf;
11102 if(serverMoves != NULL && !loadFlag) { char c = '=';
11103 if(result==WhiteWins) c = '+';
11104 if(result==BlackWins) c = '-';
11105 if(resultDetails != NULL)
11106 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11108 if (resultDetails != NULL) {
11109 gameInfo.result = result;
11110 gameInfo.resultDetails = StrSave(resultDetails);
11112 /* display last move only if game was not loaded from file */
11113 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11114 DisplayMove(currentMove - 1);
11116 if (forwardMostMove != 0) {
11117 if (gameMode != PlayFromGameFile && gameMode != EditGame
11118 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11120 if (*appData.saveGameFile != NULLCHAR) {
11121 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11122 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11124 SaveGameToFile(appData.saveGameFile, TRUE);
11125 } else if (appData.autoSaveGames) {
11126 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11128 if (*appData.savePositionFile != NULLCHAR) {
11129 SavePositionToFile(appData.savePositionFile);
11131 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11135 /* Tell program how game ended in case it is learning */
11136 /* [HGM] Moved this to after saving the PGN, just in case */
11137 /* engine died and we got here through time loss. In that */
11138 /* case we will get a fatal error writing the pipe, which */
11139 /* would otherwise lose us the PGN. */
11140 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11141 /* output during GameEnds should never be fatal anymore */
11142 if (gameMode == MachinePlaysWhite ||
11143 gameMode == MachinePlaysBlack ||
11144 gameMode == TwoMachinesPlay ||
11145 gameMode == IcsPlayingWhite ||
11146 gameMode == IcsPlayingBlack ||
11147 gameMode == BeginningOfGame) {
11149 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11151 if (first.pr != NoProc) {
11152 SendToProgram(buf, &first);
11154 if (second.pr != NoProc &&
11155 gameMode == TwoMachinesPlay) {
11156 SendToProgram(buf, &second);
11161 if (appData.icsActive) {
11162 if (appData.quietPlay &&
11163 (gameMode == IcsPlayingWhite ||
11164 gameMode == IcsPlayingBlack)) {
11165 SendToICS(ics_prefix);
11166 SendToICS("set shout 1\n");
11168 nextGameMode = IcsIdle;
11169 ics_user_moved = FALSE;
11170 /* clean up premove. It's ugly when the game has ended and the
11171 * premove highlights are still on the board.
11174 gotPremove = FALSE;
11175 ClearPremoveHighlights();
11176 DrawPosition(FALSE, boards[currentMove]);
11178 if (whosays == GE_ICS) {
11181 if (gameMode == IcsPlayingWhite)
11183 else if(gameMode == IcsPlayingBlack)
11184 PlayIcsLossSound();
11187 if (gameMode == IcsPlayingBlack)
11189 else if(gameMode == IcsPlayingWhite)
11190 PlayIcsLossSound();
11193 PlayIcsDrawSound();
11196 PlayIcsUnfinishedSound();
11199 if(appData.quitNext) { ExitEvent(0); return; }
11200 } else if (gameMode == EditGame ||
11201 gameMode == PlayFromGameFile ||
11202 gameMode == AnalyzeMode ||
11203 gameMode == AnalyzeFile) {
11204 nextGameMode = gameMode;
11206 nextGameMode = EndOfGame;
11211 nextGameMode = gameMode;
11214 if (appData.noChessProgram) {
11215 gameMode = nextGameMode;
11217 endingGame = 0; /* [HGM] crash */
11222 /* Put first chess program into idle state */
11223 if (first.pr != NoProc &&
11224 (gameMode == MachinePlaysWhite ||
11225 gameMode == MachinePlaysBlack ||
11226 gameMode == TwoMachinesPlay ||
11227 gameMode == IcsPlayingWhite ||
11228 gameMode == IcsPlayingBlack ||
11229 gameMode == BeginningOfGame)) {
11230 SendToProgram("force\n", &first);
11231 if (first.usePing) {
11233 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11234 SendToProgram(buf, &first);
11237 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11238 /* Kill off first chess program */
11239 if (first.isr != NULL)
11240 RemoveInputSource(first.isr);
11243 if (first.pr != NoProc) {
11245 DoSleep( appData.delayBeforeQuit );
11246 SendToProgram("quit\n", &first);
11247 DoSleep( appData.delayAfterQuit );
11248 DestroyChildProcess(first.pr, first.useSigterm);
11249 first.reload = TRUE;
11253 if (second.reuse) {
11254 /* Put second chess program into idle state */
11255 if (second.pr != NoProc &&
11256 gameMode == TwoMachinesPlay) {
11257 SendToProgram("force\n", &second);
11258 if (second.usePing) {
11260 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11261 SendToProgram(buf, &second);
11264 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11265 /* Kill off second chess program */
11266 if (second.isr != NULL)
11267 RemoveInputSource(second.isr);
11270 if (second.pr != NoProc) {
11271 DoSleep( appData.delayBeforeQuit );
11272 SendToProgram("quit\n", &second);
11273 DoSleep( appData.delayAfterQuit );
11274 DestroyChildProcess(second.pr, second.useSigterm);
11275 second.reload = TRUE;
11277 second.pr = NoProc;
11280 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11281 char resChar = '=';
11285 if (first.twoMachinesColor[0] == 'w') {
11288 second.matchWins++;
11293 if (first.twoMachinesColor[0] == 'b') {
11296 second.matchWins++;
11299 case GameUnfinished:
11305 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11306 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11307 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11308 ReserveGame(nextGame, resChar); // sets nextGame
11309 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11310 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11311 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11313 if (nextGame <= appData.matchGames && !abortMatch) {
11314 gameMode = nextGameMode;
11315 matchGame = nextGame; // this will be overruled in tourney mode!
11316 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11317 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11318 endingGame = 0; /* [HGM] crash */
11321 gameMode = nextGameMode;
11322 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11323 first.tidy, second.tidy,
11324 first.matchWins, second.matchWins,
11325 appData.matchGames - (first.matchWins + second.matchWins));
11326 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11327 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11328 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11329 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11330 first.twoMachinesColor = "black\n";
11331 second.twoMachinesColor = "white\n";
11333 first.twoMachinesColor = "white\n";
11334 second.twoMachinesColor = "black\n";
11338 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11339 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11341 gameMode = nextGameMode;
11343 endingGame = 0; /* [HGM] crash */
11344 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11345 if(matchMode == TRUE) { // match through command line: exit with or without popup
11347 ToNrEvent(forwardMostMove);
11348 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11350 } else DisplayFatalError(buf, 0, 0);
11351 } else { // match through menu; just stop, with or without popup
11352 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11355 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11356 } else DisplayNote(buf);
11358 if(ranking) free(ranking);
11362 /* Assumes program was just initialized (initString sent).
11363 Leaves program in force mode. */
11365 FeedMovesToProgram (ChessProgramState *cps, int upto)
11369 if (appData.debugMode)
11370 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11371 startedFromSetupPosition ? "position and " : "",
11372 backwardMostMove, upto, cps->which);
11373 if(currentlyInitializedVariant != gameInfo.variant) {
11375 // [HGM] variantswitch: make engine aware of new variant
11376 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11377 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11378 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11379 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11380 SendToProgram(buf, cps);
11381 currentlyInitializedVariant = gameInfo.variant;
11383 SendToProgram("force\n", cps);
11384 if (startedFromSetupPosition) {
11385 SendBoard(cps, backwardMostMove);
11386 if (appData.debugMode) {
11387 fprintf(debugFP, "feedMoves\n");
11390 for (i = backwardMostMove; i < upto; i++) {
11391 SendMoveToProgram(i, cps);
11397 ResurrectChessProgram ()
11399 /* The chess program may have exited.
11400 If so, restart it and feed it all the moves made so far. */
11401 static int doInit = 0;
11403 if (appData.noChessProgram) return 1;
11405 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11406 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11407 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11408 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11410 if (first.pr != NoProc) return 1;
11411 StartChessProgram(&first);
11413 InitChessProgram(&first, FALSE);
11414 FeedMovesToProgram(&first, currentMove);
11416 if (!first.sendTime) {
11417 /* can't tell gnuchess what its clock should read,
11418 so we bow to its notion. */
11420 timeRemaining[0][currentMove] = whiteTimeRemaining;
11421 timeRemaining[1][currentMove] = blackTimeRemaining;
11424 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11425 appData.icsEngineAnalyze) && first.analysisSupport) {
11426 SendToProgram("analyze\n", &first);
11427 first.analyzing = TRUE;
11433 * Button procedures
11436 Reset (int redraw, int init)
11440 if (appData.debugMode) {
11441 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11442 redraw, init, gameMode);
11444 CleanupTail(); // [HGM] vari: delete any stored variations
11445 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11446 pausing = pauseExamInvalid = FALSE;
11447 startedFromSetupPosition = blackPlaysFirst = FALSE;
11449 whiteFlag = blackFlag = FALSE;
11450 userOfferedDraw = FALSE;
11451 hintRequested = bookRequested = FALSE;
11452 first.maybeThinking = FALSE;
11453 second.maybeThinking = FALSE;
11454 first.bookSuspend = FALSE; // [HGM] book
11455 second.bookSuspend = FALSE;
11456 thinkOutput[0] = NULLCHAR;
11457 lastHint[0] = NULLCHAR;
11458 ClearGameInfo(&gameInfo);
11459 gameInfo.variant = StringToVariant(appData.variant);
11460 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11461 ics_user_moved = ics_clock_paused = FALSE;
11462 ics_getting_history = H_FALSE;
11464 white_holding[0] = black_holding[0] = NULLCHAR;
11465 ClearProgramStats();
11466 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11470 flipView = appData.flipView;
11471 ClearPremoveHighlights();
11472 gotPremove = FALSE;
11473 alarmSounded = FALSE;
11474 killX = killY = -1; // [HGM] lion
11476 GameEnds(EndOfFile, NULL, GE_PLAYER);
11477 if(appData.serverMovesName != NULL) {
11478 /* [HGM] prepare to make moves file for broadcasting */
11479 clock_t t = clock();
11480 if(serverMoves != NULL) fclose(serverMoves);
11481 serverMoves = fopen(appData.serverMovesName, "r");
11482 if(serverMoves != NULL) {
11483 fclose(serverMoves);
11484 /* delay 15 sec before overwriting, so all clients can see end */
11485 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11487 serverMoves = fopen(appData.serverMovesName, "w");
11491 gameMode = BeginningOfGame;
11493 if(appData.icsActive) gameInfo.variant = VariantNormal;
11494 currentMove = forwardMostMove = backwardMostMove = 0;
11495 MarkTargetSquares(1);
11496 InitPosition(redraw);
11497 for (i = 0; i < MAX_MOVES; i++) {
11498 if (commentList[i] != NULL) {
11499 free(commentList[i]);
11500 commentList[i] = NULL;
11504 timeRemaining[0][0] = whiteTimeRemaining;
11505 timeRemaining[1][0] = blackTimeRemaining;
11507 if (first.pr == NoProc) {
11508 StartChessProgram(&first);
11511 InitChessProgram(&first, startedFromSetupPosition);
11514 DisplayMessage("", "");
11515 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11516 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11517 ClearMap(); // [HGM] exclude: invalidate map
11521 AutoPlayGameLoop ()
11524 if (!AutoPlayOneMove())
11526 if (matchMode || appData.timeDelay == 0)
11528 if (appData.timeDelay < 0)
11530 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11538 ReloadGame(1); // next game
11544 int fromX, fromY, toX, toY;
11546 if (appData.debugMode) {
11547 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11550 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11553 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11554 pvInfoList[currentMove].depth = programStats.depth;
11555 pvInfoList[currentMove].score = programStats.score;
11556 pvInfoList[currentMove].time = 0;
11557 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11558 else { // append analysis of final position as comment
11560 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11561 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11563 programStats.depth = 0;
11566 if (currentMove >= forwardMostMove) {
11567 if(gameMode == AnalyzeFile) {
11568 if(appData.loadGameIndex == -1) {
11569 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11570 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11572 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11575 // gameMode = EndOfGame;
11576 // ModeHighlight();
11578 /* [AS] Clear current move marker at the end of a game */
11579 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11584 toX = moveList[currentMove][2] - AAA;
11585 toY = moveList[currentMove][3] - ONE;
11587 if (moveList[currentMove][1] == '@') {
11588 if (appData.highlightLastMove) {
11589 SetHighlights(-1, -1, toX, toY);
11592 fromX = moveList[currentMove][0] - AAA;
11593 fromY = moveList[currentMove][1] - ONE;
11595 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11597 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11599 if (appData.highlightLastMove) {
11600 SetHighlights(fromX, fromY, toX, toY);
11603 DisplayMove(currentMove);
11604 SendMoveToProgram(currentMove++, &first);
11605 DisplayBothClocks();
11606 DrawPosition(FALSE, boards[currentMove]);
11607 // [HGM] PV info: always display, routine tests if empty
11608 DisplayComment(currentMove - 1, commentList[currentMove]);
11614 LoadGameOneMove (ChessMove readAhead)
11616 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11617 char promoChar = NULLCHAR;
11618 ChessMove moveType;
11619 char move[MSG_SIZ];
11622 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11623 gameMode != AnalyzeMode && gameMode != Training) {
11628 yyboardindex = forwardMostMove;
11629 if (readAhead != EndOfFile) {
11630 moveType = readAhead;
11632 if (gameFileFP == NULL)
11634 moveType = (ChessMove) Myylex();
11638 switch (moveType) {
11640 if (appData.debugMode)
11641 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11644 /* append the comment but don't display it */
11645 AppendComment(currentMove, p, FALSE);
11648 case WhiteCapturesEnPassant:
11649 case BlackCapturesEnPassant:
11650 case WhitePromotion:
11651 case BlackPromotion:
11652 case WhiteNonPromotion:
11653 case BlackNonPromotion:
11656 case WhiteKingSideCastle:
11657 case WhiteQueenSideCastle:
11658 case BlackKingSideCastle:
11659 case BlackQueenSideCastle:
11660 case WhiteKingSideCastleWild:
11661 case WhiteQueenSideCastleWild:
11662 case BlackKingSideCastleWild:
11663 case BlackQueenSideCastleWild:
11665 case WhiteHSideCastleFR:
11666 case WhiteASideCastleFR:
11667 case BlackHSideCastleFR:
11668 case BlackASideCastleFR:
11670 if (appData.debugMode)
11671 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11672 fromX = currentMoveString[0] - AAA;
11673 fromY = currentMoveString[1] - ONE;
11674 toX = currentMoveString[2] - AAA;
11675 toY = currentMoveString[3] - ONE;
11676 promoChar = currentMoveString[4];
11677 if(promoChar == ';') promoChar = NULLCHAR;
11682 if (appData.debugMode)
11683 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11684 fromX = moveType == WhiteDrop ?
11685 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11686 (int) CharToPiece(ToLower(currentMoveString[0]));
11688 toX = currentMoveString[2] - AAA;
11689 toY = currentMoveString[3] - ONE;
11695 case GameUnfinished:
11696 if (appData.debugMode)
11697 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11698 p = strchr(yy_text, '{');
11699 if (p == NULL) p = strchr(yy_text, '(');
11702 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11704 q = strchr(p, *p == '{' ? '}' : ')');
11705 if (q != NULL) *q = NULLCHAR;
11708 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11709 GameEnds(moveType, p, GE_FILE);
11711 if (cmailMsgLoaded) {
11713 flipView = WhiteOnMove(currentMove);
11714 if (moveType == GameUnfinished) flipView = !flipView;
11715 if (appData.debugMode)
11716 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11721 if (appData.debugMode)
11722 fprintf(debugFP, "Parser hit end of file\n");
11723 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11729 if (WhiteOnMove(currentMove)) {
11730 GameEnds(BlackWins, "Black mates", GE_FILE);
11732 GameEnds(WhiteWins, "White mates", GE_FILE);
11736 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11742 case MoveNumberOne:
11743 if (lastLoadGameStart == GNUChessGame) {
11744 /* GNUChessGames have numbers, but they aren't move numbers */
11745 if (appData.debugMode)
11746 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11747 yy_text, (int) moveType);
11748 return LoadGameOneMove(EndOfFile); /* tail recursion */
11750 /* else fall thru */
11755 /* Reached start of next game in file */
11756 if (appData.debugMode)
11757 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11758 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11764 if (WhiteOnMove(currentMove)) {
11765 GameEnds(BlackWins, "Black mates", GE_FILE);
11767 GameEnds(WhiteWins, "White mates", GE_FILE);
11771 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11777 case PositionDiagram: /* should not happen; ignore */
11778 case ElapsedTime: /* ignore */
11779 case NAG: /* ignore */
11780 if (appData.debugMode)
11781 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11782 yy_text, (int) moveType);
11783 return LoadGameOneMove(EndOfFile); /* tail recursion */
11786 if (appData.testLegality) {
11787 if (appData.debugMode)
11788 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11789 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11790 (forwardMostMove / 2) + 1,
11791 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11792 DisplayError(move, 0);
11795 if (appData.debugMode)
11796 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11797 yy_text, currentMoveString);
11798 fromX = currentMoveString[0] - AAA;
11799 fromY = currentMoveString[1] - ONE;
11800 toX = currentMoveString[2] - AAA;
11801 toY = currentMoveString[3] - ONE;
11802 promoChar = currentMoveString[4];
11806 case AmbiguousMove:
11807 if (appData.debugMode)
11808 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11809 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11810 (forwardMostMove / 2) + 1,
11811 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11812 DisplayError(move, 0);
11817 case ImpossibleMove:
11818 if (appData.debugMode)
11819 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11820 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11821 (forwardMostMove / 2) + 1,
11822 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11823 DisplayError(move, 0);
11829 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11830 DrawPosition(FALSE, boards[currentMove]);
11831 DisplayBothClocks();
11832 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11833 DisplayComment(currentMove - 1, commentList[currentMove]);
11835 (void) StopLoadGameTimer();
11837 cmailOldMove = forwardMostMove;
11840 /* currentMoveString is set as a side-effect of yylex */
11842 thinkOutput[0] = NULLCHAR;
11843 MakeMove(fromX, fromY, toX, toY, promoChar);
11844 killX = killY = -1; // [HGM] lion: used up
11845 currentMove = forwardMostMove;
11850 /* Load the nth game from the given file */
11852 LoadGameFromFile (char *filename, int n, char *title, int useList)
11857 if (strcmp(filename, "-") == 0) {
11861 f = fopen(filename, "rb");
11863 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11864 DisplayError(buf, errno);
11868 if (fseek(f, 0, 0) == -1) {
11869 /* f is not seekable; probably a pipe */
11872 if (useList && n == 0) {
11873 int error = GameListBuild(f);
11875 DisplayError(_("Cannot build game list"), error);
11876 } else if (!ListEmpty(&gameList) &&
11877 ((ListGame *) gameList.tailPred)->number > 1) {
11878 GameListPopUp(f, title);
11885 return LoadGame(f, n, title, FALSE);
11890 MakeRegisteredMove ()
11892 int fromX, fromY, toX, toY;
11894 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11895 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11898 if (appData.debugMode)
11899 fprintf(debugFP, "Restoring %s for game %d\n",
11900 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11902 thinkOutput[0] = NULLCHAR;
11903 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11904 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11905 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11906 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11907 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11908 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11909 MakeMove(fromX, fromY, toX, toY, promoChar);
11910 ShowMove(fromX, fromY, toX, toY);
11912 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11919 if (WhiteOnMove(currentMove)) {
11920 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11922 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11927 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11934 if (WhiteOnMove(currentMove)) {
11935 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11937 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11942 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11953 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11955 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11959 if (gameNumber > nCmailGames) {
11960 DisplayError(_("No more games in this message"), 0);
11963 if (f == lastLoadGameFP) {
11964 int offset = gameNumber - lastLoadGameNumber;
11966 cmailMsg[0] = NULLCHAR;
11967 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11968 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11969 nCmailMovesRegistered--;
11971 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11972 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11973 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11976 if (! RegisterMove()) return FALSE;
11980 retVal = LoadGame(f, gameNumber, title, useList);
11982 /* Make move registered during previous look at this game, if any */
11983 MakeRegisteredMove();
11985 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11986 commentList[currentMove]
11987 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11988 DisplayComment(currentMove - 1, commentList[currentMove]);
11994 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11996 ReloadGame (int offset)
11998 int gameNumber = lastLoadGameNumber + offset;
11999 if (lastLoadGameFP == NULL) {
12000 DisplayError(_("No game has been loaded yet"), 0);
12003 if (gameNumber <= 0) {
12004 DisplayError(_("Can't back up any further"), 0);
12007 if (cmailMsgLoaded) {
12008 return CmailLoadGame(lastLoadGameFP, gameNumber,
12009 lastLoadGameTitle, lastLoadGameUseList);
12011 return LoadGame(lastLoadGameFP, gameNumber,
12012 lastLoadGameTitle, lastLoadGameUseList);
12016 int keys[EmptySquare+1];
12019 PositionMatches (Board b1, Board b2)
12022 switch(appData.searchMode) {
12023 case 1: return CompareWithRights(b1, b2);
12025 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12026 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12030 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12031 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12032 sum += keys[b1[r][f]] - keys[b2[r][f]];
12036 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12037 sum += keys[b1[r][f]] - keys[b2[r][f]];
12049 int pieceList[256], quickBoard[256];
12050 ChessSquare pieceType[256] = { EmptySquare };
12051 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12052 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12053 int soughtTotal, turn;
12054 Boolean epOK, flipSearch;
12057 unsigned char piece, to;
12060 #define DSIZE (250000)
12062 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12063 Move *moveDatabase = initialSpace;
12064 unsigned int movePtr, dataSize = DSIZE;
12067 MakePieceList (Board board, int *counts)
12069 int r, f, n=Q_PROMO, total=0;
12070 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12071 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12072 int sq = f + (r<<4);
12073 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12074 quickBoard[sq] = ++n;
12076 pieceType[n] = board[r][f];
12077 counts[board[r][f]]++;
12078 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12079 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12083 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12088 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12090 int sq = fromX + (fromY<<4);
12091 int piece = quickBoard[sq];
12092 quickBoard[sq] = 0;
12093 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12094 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12095 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12096 moveDatabase[movePtr++].piece = Q_WCASTL;
12097 quickBoard[sq] = piece;
12098 piece = quickBoard[from]; quickBoard[from] = 0;
12099 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12101 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12102 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12103 moveDatabase[movePtr++].piece = Q_BCASTL;
12104 quickBoard[sq] = piece;
12105 piece = quickBoard[from]; quickBoard[from] = 0;
12106 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12108 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12109 quickBoard[(fromY<<4)+toX] = 0;
12110 moveDatabase[movePtr].piece = Q_EP;
12111 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12112 moveDatabase[movePtr].to = sq;
12114 if(promoPiece != pieceType[piece]) {
12115 moveDatabase[movePtr++].piece = Q_PROMO;
12116 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12118 moveDatabase[movePtr].piece = piece;
12119 quickBoard[sq] = piece;
12124 PackGame (Board board)
12126 Move *newSpace = NULL;
12127 moveDatabase[movePtr].piece = 0; // terminate previous game
12128 if(movePtr > dataSize) {
12129 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12130 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12131 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12134 Move *p = moveDatabase, *q = newSpace;
12135 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12136 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12137 moveDatabase = newSpace;
12138 } else { // calloc failed, we must be out of memory. Too bad...
12139 dataSize = 0; // prevent calloc events for all subsequent games
12140 return 0; // and signal this one isn't cached
12144 MakePieceList(board, counts);
12149 QuickCompare (Board board, int *minCounts, int *maxCounts)
12150 { // compare according to search mode
12152 switch(appData.searchMode)
12154 case 1: // exact position match
12155 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12156 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12157 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12160 case 2: // can have extra material on empty squares
12161 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12162 if(board[r][f] == EmptySquare) continue;
12163 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12166 case 3: // material with exact Pawn structure
12167 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12168 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12169 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12170 } // fall through to material comparison
12171 case 4: // exact material
12172 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12174 case 6: // material range with given imbalance
12175 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12176 // fall through to range comparison
12177 case 5: // material range
12178 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12184 QuickScan (Board board, Move *move)
12185 { // reconstruct game,and compare all positions in it
12186 int cnt=0, stretch=0, total = MakePieceList(board, counts);
12188 int piece = move->piece;
12189 int to = move->to, from = pieceList[piece];
12190 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12191 if(!piece) return -1;
12192 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12193 piece = (++move)->piece;
12194 from = pieceList[piece];
12195 counts[pieceType[piece]]--;
12196 pieceType[piece] = (ChessSquare) move->to;
12197 counts[move->to]++;
12198 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12199 counts[pieceType[quickBoard[to]]]--;
12200 quickBoard[to] = 0; total--;
12203 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12204 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12205 from = pieceList[piece]; // so this must be King
12206 quickBoard[from] = 0;
12207 pieceList[piece] = to;
12208 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12209 quickBoard[from] = 0; // rook
12210 quickBoard[to] = piece;
12211 to = move->to; piece = move->piece;
12215 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12216 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12217 quickBoard[from] = 0;
12219 quickBoard[to] = piece;
12220 pieceList[piece] = to;
12222 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12223 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12224 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12225 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12227 static int lastCounts[EmptySquare+1];
12229 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12230 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12231 } else stretch = 0;
12232 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12241 flipSearch = FALSE;
12242 CopyBoard(soughtBoard, boards[currentMove]);
12243 soughtTotal = MakePieceList(soughtBoard, maxSought);
12244 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12245 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12246 CopyBoard(reverseBoard, boards[currentMove]);
12247 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12248 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12249 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12250 reverseBoard[r][f] = piece;
12252 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12253 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12254 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12255 || (boards[currentMove][CASTLING][2] == NoRights ||
12256 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12257 && (boards[currentMove][CASTLING][5] == NoRights ||
12258 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12261 CopyBoard(flipBoard, soughtBoard);
12262 CopyBoard(rotateBoard, reverseBoard);
12263 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12264 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12265 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12268 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12269 if(appData.searchMode >= 5) {
12270 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12271 MakePieceList(soughtBoard, minSought);
12272 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12274 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12275 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12278 GameInfo dummyInfo;
12279 static int creatingBook;
12282 GameContainsPosition (FILE *f, ListGame *lg)
12284 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12285 int fromX, fromY, toX, toY;
12287 static int initDone=FALSE;
12289 // weed out games based on numerical tag comparison
12290 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12291 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12292 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12293 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12295 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12298 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12299 else CopyBoard(boards[scratch], initialPosition); // default start position
12302 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12303 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12306 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12307 fseek(f, lg->offset, 0);
12310 yyboardindex = scratch;
12311 quickFlag = plyNr+1;
12316 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12322 if(plyNr) return -1; // after we have seen moves, this is for new game
12325 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12326 case ImpossibleMove:
12327 case WhiteWins: // game ends here with these four
12330 case GameUnfinished:
12334 if(appData.testLegality) return -1;
12335 case WhiteCapturesEnPassant:
12336 case BlackCapturesEnPassant:
12337 case WhitePromotion:
12338 case BlackPromotion:
12339 case WhiteNonPromotion:
12340 case BlackNonPromotion:
12343 case WhiteKingSideCastle:
12344 case WhiteQueenSideCastle:
12345 case BlackKingSideCastle:
12346 case BlackQueenSideCastle:
12347 case WhiteKingSideCastleWild:
12348 case WhiteQueenSideCastleWild:
12349 case BlackKingSideCastleWild:
12350 case BlackQueenSideCastleWild:
12351 case WhiteHSideCastleFR:
12352 case WhiteASideCastleFR:
12353 case BlackHSideCastleFR:
12354 case BlackASideCastleFR:
12355 fromX = currentMoveString[0] - AAA;
12356 fromY = currentMoveString[1] - ONE;
12357 toX = currentMoveString[2] - AAA;
12358 toY = currentMoveString[3] - ONE;
12359 promoChar = currentMoveString[4];
12363 fromX = next == WhiteDrop ?
12364 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12365 (int) CharToPiece(ToLower(currentMoveString[0]));
12367 toX = currentMoveString[2] - AAA;
12368 toY = currentMoveString[3] - ONE;
12372 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12374 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12375 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12376 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12377 if(appData.findMirror) {
12378 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12379 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12384 /* Load the nth game from open file f */
12386 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12390 int gn = gameNumber;
12391 ListGame *lg = NULL;
12392 int numPGNTags = 0;
12394 GameMode oldGameMode;
12395 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12397 if (appData.debugMode)
12398 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12400 if (gameMode == Training )
12401 SetTrainingModeOff();
12403 oldGameMode = gameMode;
12404 if (gameMode != BeginningOfGame) {
12405 Reset(FALSE, TRUE);
12407 killX = killY = -1; // [HGM] lion: in case we did not Reset
12410 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12411 fclose(lastLoadGameFP);
12415 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12418 fseek(f, lg->offset, 0);
12419 GameListHighlight(gameNumber);
12420 pos = lg->position;
12424 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12425 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12427 DisplayError(_("Game number out of range"), 0);
12432 if (fseek(f, 0, 0) == -1) {
12433 if (f == lastLoadGameFP ?
12434 gameNumber == lastLoadGameNumber + 1 :
12438 DisplayError(_("Can't seek on game file"), 0);
12443 lastLoadGameFP = f;
12444 lastLoadGameNumber = gameNumber;
12445 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12446 lastLoadGameUseList = useList;
12450 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12451 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12452 lg->gameInfo.black);
12454 } else if (*title != NULLCHAR) {
12455 if (gameNumber > 1) {
12456 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12459 DisplayTitle(title);
12463 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12464 gameMode = PlayFromGameFile;
12468 currentMove = forwardMostMove = backwardMostMove = 0;
12469 CopyBoard(boards[0], initialPosition);
12473 * Skip the first gn-1 games in the file.
12474 * Also skip over anything that precedes an identifiable
12475 * start of game marker, to avoid being confused by
12476 * garbage at the start of the file. Currently
12477 * recognized start of game markers are the move number "1",
12478 * the pattern "gnuchess .* game", the pattern
12479 * "^[#;%] [^ ]* game file", and a PGN tag block.
12480 * A game that starts with one of the latter two patterns
12481 * will also have a move number 1, possibly
12482 * following a position diagram.
12483 * 5-4-02: Let's try being more lenient and allowing a game to
12484 * start with an unnumbered move. Does that break anything?
12486 cm = lastLoadGameStart = EndOfFile;
12488 yyboardindex = forwardMostMove;
12489 cm = (ChessMove) Myylex();
12492 if (cmailMsgLoaded) {
12493 nCmailGames = CMAIL_MAX_GAMES - gn;
12496 DisplayError(_("Game not found in file"), 0);
12503 lastLoadGameStart = cm;
12506 case MoveNumberOne:
12507 switch (lastLoadGameStart) {
12512 case MoveNumberOne:
12514 gn--; /* count this game */
12515 lastLoadGameStart = cm;
12524 switch (lastLoadGameStart) {
12527 case MoveNumberOne:
12529 gn--; /* count this game */
12530 lastLoadGameStart = cm;
12533 lastLoadGameStart = cm; /* game counted already */
12541 yyboardindex = forwardMostMove;
12542 cm = (ChessMove) Myylex();
12543 } while (cm == PGNTag || cm == Comment);
12550 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12551 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12552 != CMAIL_OLD_RESULT) {
12554 cmailResult[ CMAIL_MAX_GAMES
12555 - gn - 1] = CMAIL_OLD_RESULT;
12562 /* Only a NormalMove can be at the start of a game
12563 * without a position diagram. */
12564 if (lastLoadGameStart == EndOfFile ) {
12566 lastLoadGameStart = MoveNumberOne;
12575 if (appData.debugMode)
12576 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12578 if (cm == XBoardGame) {
12579 /* Skip any header junk before position diagram and/or move 1 */
12581 yyboardindex = forwardMostMove;
12582 cm = (ChessMove) Myylex();
12584 if (cm == EndOfFile ||
12585 cm == GNUChessGame || cm == XBoardGame) {
12586 /* Empty game; pretend end-of-file and handle later */
12591 if (cm == MoveNumberOne || cm == PositionDiagram ||
12592 cm == PGNTag || cm == Comment)
12595 } else if (cm == GNUChessGame) {
12596 if (gameInfo.event != NULL) {
12597 free(gameInfo.event);
12599 gameInfo.event = StrSave(yy_text);
12602 startedFromSetupPosition = FALSE;
12603 while (cm == PGNTag) {
12604 if (appData.debugMode)
12605 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12606 err = ParsePGNTag(yy_text, &gameInfo);
12607 if (!err) numPGNTags++;
12609 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12610 if(gameInfo.variant != oldVariant) {
12611 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12612 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12613 InitPosition(TRUE);
12614 oldVariant = gameInfo.variant;
12615 if (appData.debugMode)
12616 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12620 if (gameInfo.fen != NULL) {
12621 Board initial_position;
12622 startedFromSetupPosition = TRUE;
12623 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12625 DisplayError(_("Bad FEN position in file"), 0);
12628 CopyBoard(boards[0], initial_position);
12629 if (blackPlaysFirst) {
12630 currentMove = forwardMostMove = backwardMostMove = 1;
12631 CopyBoard(boards[1], initial_position);
12632 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12633 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12634 timeRemaining[0][1] = whiteTimeRemaining;
12635 timeRemaining[1][1] = blackTimeRemaining;
12636 if (commentList[0] != NULL) {
12637 commentList[1] = commentList[0];
12638 commentList[0] = NULL;
12641 currentMove = forwardMostMove = backwardMostMove = 0;
12643 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12645 initialRulePlies = FENrulePlies;
12646 for( i=0; i< nrCastlingRights; i++ )
12647 initialRights[i] = initial_position[CASTLING][i];
12649 yyboardindex = forwardMostMove;
12650 free(gameInfo.fen);
12651 gameInfo.fen = NULL;
12654 yyboardindex = forwardMostMove;
12655 cm = (ChessMove) Myylex();
12657 /* Handle comments interspersed among the tags */
12658 while (cm == Comment) {
12660 if (appData.debugMode)
12661 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12663 AppendComment(currentMove, p, FALSE);
12664 yyboardindex = forwardMostMove;
12665 cm = (ChessMove) Myylex();
12669 /* don't rely on existence of Event tag since if game was
12670 * pasted from clipboard the Event tag may not exist
12672 if (numPGNTags > 0){
12674 if (gameInfo.variant == VariantNormal) {
12675 VariantClass v = StringToVariant(gameInfo.event);
12676 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12677 if(v < VariantShogi) gameInfo.variant = v;
12680 if( appData.autoDisplayTags ) {
12681 tags = PGNTags(&gameInfo);
12682 TagsPopUp(tags, CmailMsg());
12687 /* Make something up, but don't display it now */
12692 if (cm == PositionDiagram) {
12695 Board initial_position;
12697 if (appData.debugMode)
12698 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12700 if (!startedFromSetupPosition) {
12702 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12703 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12714 initial_position[i][j++] = CharToPiece(*p);
12717 while (*p == ' ' || *p == '\t' ||
12718 *p == '\n' || *p == '\r') p++;
12720 if (strncmp(p, "black", strlen("black"))==0)
12721 blackPlaysFirst = TRUE;
12723 blackPlaysFirst = FALSE;
12724 startedFromSetupPosition = TRUE;
12726 CopyBoard(boards[0], initial_position);
12727 if (blackPlaysFirst) {
12728 currentMove = forwardMostMove = backwardMostMove = 1;
12729 CopyBoard(boards[1], initial_position);
12730 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12731 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12732 timeRemaining[0][1] = whiteTimeRemaining;
12733 timeRemaining[1][1] = blackTimeRemaining;
12734 if (commentList[0] != NULL) {
12735 commentList[1] = commentList[0];
12736 commentList[0] = NULL;
12739 currentMove = forwardMostMove = backwardMostMove = 0;
12742 yyboardindex = forwardMostMove;
12743 cm = (ChessMove) Myylex();
12746 if(!creatingBook) {
12747 if (first.pr == NoProc) {
12748 StartChessProgram(&first);
12750 InitChessProgram(&first, FALSE);
12751 SendToProgram("force\n", &first);
12752 if (startedFromSetupPosition) {
12753 SendBoard(&first, forwardMostMove);
12754 if (appData.debugMode) {
12755 fprintf(debugFP, "Load Game\n");
12757 DisplayBothClocks();
12761 /* [HGM] server: flag to write setup moves in broadcast file as one */
12762 loadFlag = appData.suppressLoadMoves;
12764 while (cm == Comment) {
12766 if (appData.debugMode)
12767 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12769 AppendComment(currentMove, p, FALSE);
12770 yyboardindex = forwardMostMove;
12771 cm = (ChessMove) Myylex();
12774 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12775 cm == WhiteWins || cm == BlackWins ||
12776 cm == GameIsDrawn || cm == GameUnfinished) {
12777 DisplayMessage("", _("No moves in game"));
12778 if (cmailMsgLoaded) {
12779 if (appData.debugMode)
12780 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12784 DrawPosition(FALSE, boards[currentMove]);
12785 DisplayBothClocks();
12786 gameMode = EditGame;
12793 // [HGM] PV info: routine tests if comment empty
12794 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12795 DisplayComment(currentMove - 1, commentList[currentMove]);
12797 if (!matchMode && appData.timeDelay != 0)
12798 DrawPosition(FALSE, boards[currentMove]);
12800 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12801 programStats.ok_to_send = 1;
12804 /* if the first token after the PGN tags is a move
12805 * and not move number 1, retrieve it from the parser
12807 if (cm != MoveNumberOne)
12808 LoadGameOneMove(cm);
12810 /* load the remaining moves from the file */
12811 while (LoadGameOneMove(EndOfFile)) {
12812 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12813 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12816 /* rewind to the start of the game */
12817 currentMove = backwardMostMove;
12819 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12821 if (oldGameMode == AnalyzeFile) {
12822 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12823 AnalyzeFileEvent();
12825 if (oldGameMode == AnalyzeMode) {
12826 AnalyzeFileEvent();
12829 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12830 long int w, b; // [HGM] adjourn: restore saved clock times
12831 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12832 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12833 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12834 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12838 if(creatingBook) return TRUE;
12839 if (!matchMode && pos > 0) {
12840 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12842 if (matchMode || appData.timeDelay == 0) {
12844 } else if (appData.timeDelay > 0) {
12845 AutoPlayGameLoop();
12848 if (appData.debugMode)
12849 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12851 loadFlag = 0; /* [HGM] true game starts */
12855 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12857 ReloadPosition (int offset)
12859 int positionNumber = lastLoadPositionNumber + offset;
12860 if (lastLoadPositionFP == NULL) {
12861 DisplayError(_("No position has been loaded yet"), 0);
12864 if (positionNumber <= 0) {
12865 DisplayError(_("Can't back up any further"), 0);
12868 return LoadPosition(lastLoadPositionFP, positionNumber,
12869 lastLoadPositionTitle);
12872 /* Load the nth position from the given file */
12874 LoadPositionFromFile (char *filename, int n, char *title)
12879 if (strcmp(filename, "-") == 0) {
12880 return LoadPosition(stdin, n, "stdin");
12882 f = fopen(filename, "rb");
12884 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12885 DisplayError(buf, errno);
12888 return LoadPosition(f, n, title);
12893 /* Load the nth position from the given open file, and close it */
12895 LoadPosition (FILE *f, int positionNumber, char *title)
12897 char *p, line[MSG_SIZ];
12898 Board initial_position;
12899 int i, j, fenMode, pn;
12901 if (gameMode == Training )
12902 SetTrainingModeOff();
12904 if (gameMode != BeginningOfGame) {
12905 Reset(FALSE, TRUE);
12907 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12908 fclose(lastLoadPositionFP);
12910 if (positionNumber == 0) positionNumber = 1;
12911 lastLoadPositionFP = f;
12912 lastLoadPositionNumber = positionNumber;
12913 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12914 if (first.pr == NoProc && !appData.noChessProgram) {
12915 StartChessProgram(&first);
12916 InitChessProgram(&first, FALSE);
12918 pn = positionNumber;
12919 if (positionNumber < 0) {
12920 /* Negative position number means to seek to that byte offset */
12921 if (fseek(f, -positionNumber, 0) == -1) {
12922 DisplayError(_("Can't seek on position file"), 0);
12927 if (fseek(f, 0, 0) == -1) {
12928 if (f == lastLoadPositionFP ?
12929 positionNumber == lastLoadPositionNumber + 1 :
12930 positionNumber == 1) {
12933 DisplayError(_("Can't seek on position file"), 0);
12938 /* See if this file is FEN or old-style xboard */
12939 if (fgets(line, MSG_SIZ, f) == NULL) {
12940 DisplayError(_("Position not found in file"), 0);
12943 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12944 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12947 if (fenMode || line[0] == '#') pn--;
12949 /* skip positions before number pn */
12950 if (fgets(line, MSG_SIZ, f) == NULL) {
12952 DisplayError(_("Position not found in file"), 0);
12955 if (fenMode || line[0] == '#') pn--;
12960 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12961 DisplayError(_("Bad FEN position in file"), 0);
12965 (void) fgets(line, MSG_SIZ, f);
12966 (void) fgets(line, MSG_SIZ, f);
12968 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12969 (void) fgets(line, MSG_SIZ, f);
12970 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12973 initial_position[i][j++] = CharToPiece(*p);
12977 blackPlaysFirst = FALSE;
12979 (void) fgets(line, MSG_SIZ, f);
12980 if (strncmp(line, "black", strlen("black"))==0)
12981 blackPlaysFirst = TRUE;
12984 startedFromSetupPosition = TRUE;
12986 CopyBoard(boards[0], initial_position);
12987 if (blackPlaysFirst) {
12988 currentMove = forwardMostMove = backwardMostMove = 1;
12989 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12990 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12991 CopyBoard(boards[1], initial_position);
12992 DisplayMessage("", _("Black to play"));
12994 currentMove = forwardMostMove = backwardMostMove = 0;
12995 DisplayMessage("", _("White to play"));
12997 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12998 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12999 SendToProgram("force\n", &first);
13000 SendBoard(&first, forwardMostMove);
13002 if (appData.debugMode) {
13004 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13005 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13006 fprintf(debugFP, "Load Position\n");
13009 if (positionNumber > 1) {
13010 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13011 DisplayTitle(line);
13013 DisplayTitle(title);
13015 gameMode = EditGame;
13018 timeRemaining[0][1] = whiteTimeRemaining;
13019 timeRemaining[1][1] = blackTimeRemaining;
13020 DrawPosition(FALSE, boards[currentMove]);
13027 CopyPlayerNameIntoFileName (char **dest, char *src)
13029 while (*src != NULLCHAR && *src != ',') {
13034 *(*dest)++ = *src++;
13040 DefaultFileName (char *ext)
13042 static char def[MSG_SIZ];
13045 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13047 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13049 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13051 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13058 /* Save the current game to the given file */
13060 SaveGameToFile (char *filename, int append)
13064 int result, i, t,tot=0;
13066 if (strcmp(filename, "-") == 0) {
13067 return SaveGame(stdout, 0, NULL);
13069 for(i=0; i<10; i++) { // upto 10 tries
13070 f = fopen(filename, append ? "a" : "w");
13071 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13072 if(f || errno != 13) break;
13073 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13077 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13078 DisplayError(buf, errno);
13081 safeStrCpy(buf, lastMsg, MSG_SIZ);
13082 DisplayMessage(_("Waiting for access to save file"), "");
13083 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13084 DisplayMessage(_("Saving game"), "");
13085 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13086 result = SaveGame(f, 0, NULL);
13087 DisplayMessage(buf, "");
13094 SavePart (char *str)
13096 static char buf[MSG_SIZ];
13099 p = strchr(str, ' ');
13100 if (p == NULL) return str;
13101 strncpy(buf, str, p - str);
13102 buf[p - str] = NULLCHAR;
13106 #define PGN_MAX_LINE 75
13108 #define PGN_SIDE_WHITE 0
13109 #define PGN_SIDE_BLACK 1
13112 FindFirstMoveOutOfBook (int side)
13116 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13117 int index = backwardMostMove;
13118 int has_book_hit = 0;
13120 if( (index % 2) != side ) {
13124 while( index < forwardMostMove ) {
13125 /* Check to see if engine is in book */
13126 int depth = pvInfoList[index].depth;
13127 int score = pvInfoList[index].score;
13133 else if( score == 0 && depth == 63 ) {
13134 in_book = 1; /* Zappa */
13136 else if( score == 2 && depth == 99 ) {
13137 in_book = 1; /* Abrok */
13140 has_book_hit += in_book;
13156 GetOutOfBookInfo (char * buf)
13160 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13162 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13163 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13167 if( oob[0] >= 0 || oob[1] >= 0 ) {
13168 for( i=0; i<2; i++ ) {
13172 if( i > 0 && oob[0] >= 0 ) {
13173 strcat( buf, " " );
13176 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13177 sprintf( buf+strlen(buf), "%s%.2f",
13178 pvInfoList[idx].score >= 0 ? "+" : "",
13179 pvInfoList[idx].score / 100.0 );
13185 /* Save game in PGN style and close the file */
13187 SaveGamePGN (FILE *f)
13189 int i, offset, linelen, newblock;
13192 int movelen, numlen, blank;
13193 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13195 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13197 PrintPGNTags(f, &gameInfo);
13199 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13201 if (backwardMostMove > 0 || startedFromSetupPosition) {
13202 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13203 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13204 fprintf(f, "\n{--------------\n");
13205 PrintPosition(f, backwardMostMove);
13206 fprintf(f, "--------------}\n");
13210 /* [AS] Out of book annotation */
13211 if( appData.saveOutOfBookInfo ) {
13214 GetOutOfBookInfo( buf );
13216 if( buf[0] != '\0' ) {
13217 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13224 i = backwardMostMove;
13228 while (i < forwardMostMove) {
13229 /* Print comments preceding this move */
13230 if (commentList[i] != NULL) {
13231 if (linelen > 0) fprintf(f, "\n");
13232 fprintf(f, "%s", commentList[i]);
13237 /* Format move number */
13239 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13242 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13244 numtext[0] = NULLCHAR;
13246 numlen = strlen(numtext);
13249 /* Print move number */
13250 blank = linelen > 0 && numlen > 0;
13251 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13260 fprintf(f, "%s", numtext);
13264 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13265 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13268 blank = linelen > 0 && movelen > 0;
13269 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13278 fprintf(f, "%s", move_buffer);
13279 linelen += movelen;
13281 /* [AS] Add PV info if present */
13282 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13283 /* [HGM] add time */
13284 char buf[MSG_SIZ]; int seconds;
13286 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13292 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13295 seconds = (seconds + 4)/10; // round to full seconds
13297 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13299 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13302 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13303 pvInfoList[i].score >= 0 ? "+" : "",
13304 pvInfoList[i].score / 100.0,
13305 pvInfoList[i].depth,
13308 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13310 /* Print score/depth */
13311 blank = linelen > 0 && movelen > 0;
13312 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13321 fprintf(f, "%s", move_buffer);
13322 linelen += movelen;
13328 /* Start a new line */
13329 if (linelen > 0) fprintf(f, "\n");
13331 /* Print comments after last move */
13332 if (commentList[i] != NULL) {
13333 fprintf(f, "%s\n", commentList[i]);
13337 if (gameInfo.resultDetails != NULL &&
13338 gameInfo.resultDetails[0] != NULLCHAR) {
13339 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13340 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13341 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13342 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13343 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13345 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13349 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13353 /* Save game in old style and close the file */
13355 SaveGameOldStyle (FILE *f)
13360 tm = time((time_t *) NULL);
13362 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13365 if (backwardMostMove > 0 || startedFromSetupPosition) {
13366 fprintf(f, "\n[--------------\n");
13367 PrintPosition(f, backwardMostMove);
13368 fprintf(f, "--------------]\n");
13373 i = backwardMostMove;
13374 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13376 while (i < forwardMostMove) {
13377 if (commentList[i] != NULL) {
13378 fprintf(f, "[%s]\n", commentList[i]);
13381 if ((i % 2) == 1) {
13382 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13385 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13387 if (commentList[i] != NULL) {
13391 if (i >= forwardMostMove) {
13395 fprintf(f, "%s\n", parseList[i]);
13400 if (commentList[i] != NULL) {
13401 fprintf(f, "[%s]\n", commentList[i]);
13404 /* This isn't really the old style, but it's close enough */
13405 if (gameInfo.resultDetails != NULL &&
13406 gameInfo.resultDetails[0] != NULLCHAR) {
13407 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13408 gameInfo.resultDetails);
13410 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13417 /* Save the current game to open file f and close the file */
13419 SaveGame (FILE *f, int dummy, char *dummy2)
13421 if (gameMode == EditPosition) EditPositionDone(TRUE);
13422 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13423 if (appData.oldSaveStyle)
13424 return SaveGameOldStyle(f);
13426 return SaveGamePGN(f);
13429 /* Save the current position to the given file */
13431 SavePositionToFile (char *filename)
13436 if (strcmp(filename, "-") == 0) {
13437 return SavePosition(stdout, 0, NULL);
13439 f = fopen(filename, "a");
13441 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13442 DisplayError(buf, errno);
13445 safeStrCpy(buf, lastMsg, MSG_SIZ);
13446 DisplayMessage(_("Waiting for access to save file"), "");
13447 flock(fileno(f), LOCK_EX); // [HGM] lock
13448 DisplayMessage(_("Saving position"), "");
13449 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13450 SavePosition(f, 0, NULL);
13451 DisplayMessage(buf, "");
13457 /* Save the current position to the given open file and close the file */
13459 SavePosition (FILE *f, int dummy, char *dummy2)
13464 if (gameMode == EditPosition) EditPositionDone(TRUE);
13465 if (appData.oldSaveStyle) {
13466 tm = time((time_t *) NULL);
13468 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13470 fprintf(f, "[--------------\n");
13471 PrintPosition(f, currentMove);
13472 fprintf(f, "--------------]\n");
13474 fen = PositionToFEN(currentMove, NULL, 1);
13475 fprintf(f, "%s\n", fen);
13483 ReloadCmailMsgEvent (int unregister)
13486 static char *inFilename = NULL;
13487 static char *outFilename;
13489 struct stat inbuf, outbuf;
13492 /* Any registered moves are unregistered if unregister is set, */
13493 /* i.e. invoked by the signal handler */
13495 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13496 cmailMoveRegistered[i] = FALSE;
13497 if (cmailCommentList[i] != NULL) {
13498 free(cmailCommentList[i]);
13499 cmailCommentList[i] = NULL;
13502 nCmailMovesRegistered = 0;
13505 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13506 cmailResult[i] = CMAIL_NOT_RESULT;
13510 if (inFilename == NULL) {
13511 /* Because the filenames are static they only get malloced once */
13512 /* and they never get freed */
13513 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13514 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13516 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13517 sprintf(outFilename, "%s.out", appData.cmailGameName);
13520 status = stat(outFilename, &outbuf);
13522 cmailMailedMove = FALSE;
13524 status = stat(inFilename, &inbuf);
13525 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13528 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13529 counts the games, notes how each one terminated, etc.
13531 It would be nice to remove this kludge and instead gather all
13532 the information while building the game list. (And to keep it
13533 in the game list nodes instead of having a bunch of fixed-size
13534 parallel arrays.) Note this will require getting each game's
13535 termination from the PGN tags, as the game list builder does
13536 not process the game moves. --mann
13538 cmailMsgLoaded = TRUE;
13539 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13541 /* Load first game in the file or popup game menu */
13542 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13544 #endif /* !WIN32 */
13552 char string[MSG_SIZ];
13554 if ( cmailMailedMove
13555 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13556 return TRUE; /* Allow free viewing */
13559 /* Unregister move to ensure that we don't leave RegisterMove */
13560 /* with the move registered when the conditions for registering no */
13562 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13563 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13564 nCmailMovesRegistered --;
13566 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13568 free(cmailCommentList[lastLoadGameNumber - 1]);
13569 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13573 if (cmailOldMove == -1) {
13574 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13578 if (currentMove > cmailOldMove + 1) {
13579 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13583 if (currentMove < cmailOldMove) {
13584 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13588 if (forwardMostMove > currentMove) {
13589 /* Silently truncate extra moves */
13593 if ( (currentMove == cmailOldMove + 1)
13594 || ( (currentMove == cmailOldMove)
13595 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13596 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13597 if (gameInfo.result != GameUnfinished) {
13598 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13601 if (commentList[currentMove] != NULL) {
13602 cmailCommentList[lastLoadGameNumber - 1]
13603 = StrSave(commentList[currentMove]);
13605 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13607 if (appData.debugMode)
13608 fprintf(debugFP, "Saving %s for game %d\n",
13609 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13611 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13613 f = fopen(string, "w");
13614 if (appData.oldSaveStyle) {
13615 SaveGameOldStyle(f); /* also closes the file */
13617 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13618 f = fopen(string, "w");
13619 SavePosition(f, 0, NULL); /* also closes the file */
13621 fprintf(f, "{--------------\n");
13622 PrintPosition(f, currentMove);
13623 fprintf(f, "--------------}\n\n");
13625 SaveGame(f, 0, NULL); /* also closes the file*/
13628 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13629 nCmailMovesRegistered ++;
13630 } else if (nCmailGames == 1) {
13631 DisplayError(_("You have not made a move yet"), 0);
13642 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13643 FILE *commandOutput;
13644 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13645 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13651 if (! cmailMsgLoaded) {
13652 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13656 if (nCmailGames == nCmailResults) {
13657 DisplayError(_("No unfinished games"), 0);
13661 #if CMAIL_PROHIBIT_REMAIL
13662 if (cmailMailedMove) {
13663 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);
13664 DisplayError(msg, 0);
13669 if (! (cmailMailedMove || RegisterMove())) return;
13671 if ( cmailMailedMove
13672 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13673 snprintf(string, MSG_SIZ, partCommandString,
13674 appData.debugMode ? " -v" : "", appData.cmailGameName);
13675 commandOutput = popen(string, "r");
13677 if (commandOutput == NULL) {
13678 DisplayError(_("Failed to invoke cmail"), 0);
13680 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13681 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13683 if (nBuffers > 1) {
13684 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13685 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13686 nBytes = MSG_SIZ - 1;
13688 (void) memcpy(msg, buffer, nBytes);
13690 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13692 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13693 cmailMailedMove = TRUE; /* Prevent >1 moves */
13696 for (i = 0; i < nCmailGames; i ++) {
13697 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13702 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13704 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13706 appData.cmailGameName,
13708 LoadGameFromFile(buffer, 1, buffer, FALSE);
13709 cmailMsgLoaded = FALSE;
13713 DisplayInformation(msg);
13714 pclose(commandOutput);
13717 if ((*cmailMsg) != '\0') {
13718 DisplayInformation(cmailMsg);
13723 #endif /* !WIN32 */
13732 int prependComma = 0;
13734 char string[MSG_SIZ]; /* Space for game-list */
13737 if (!cmailMsgLoaded) return "";
13739 if (cmailMailedMove) {
13740 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13742 /* Create a list of games left */
13743 snprintf(string, MSG_SIZ, "[");
13744 for (i = 0; i < nCmailGames; i ++) {
13745 if (! ( cmailMoveRegistered[i]
13746 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13747 if (prependComma) {
13748 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13750 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13754 strcat(string, number);
13757 strcat(string, "]");
13759 if (nCmailMovesRegistered + nCmailResults == 0) {
13760 switch (nCmailGames) {
13762 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13766 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13770 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13775 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13777 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13782 if (nCmailResults == nCmailGames) {
13783 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13785 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13790 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13802 if (gameMode == Training)
13803 SetTrainingModeOff();
13806 cmailMsgLoaded = FALSE;
13807 if (appData.icsActive) {
13808 SendToICS(ics_prefix);
13809 SendToICS("refresh\n");
13814 ExitEvent (int status)
13818 /* Give up on clean exit */
13822 /* Keep trying for clean exit */
13826 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13828 if (telnetISR != NULL) {
13829 RemoveInputSource(telnetISR);
13831 if (icsPR != NoProc) {
13832 DestroyChildProcess(icsPR, TRUE);
13835 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13836 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13838 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13839 /* make sure this other one finishes before killing it! */
13840 if(endingGame) { int count = 0;
13841 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13842 while(endingGame && count++ < 10) DoSleep(1);
13843 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13846 /* Kill off chess programs */
13847 if (first.pr != NoProc) {
13850 DoSleep( appData.delayBeforeQuit );
13851 SendToProgram("quit\n", &first);
13852 DoSleep( appData.delayAfterQuit );
13853 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13855 if (second.pr != NoProc) {
13856 DoSleep( appData.delayBeforeQuit );
13857 SendToProgram("quit\n", &second);
13858 DoSleep( appData.delayAfterQuit );
13859 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13861 if (first.isr != NULL) {
13862 RemoveInputSource(first.isr);
13864 if (second.isr != NULL) {
13865 RemoveInputSource(second.isr);
13868 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13869 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13871 ShutDownFrontEnd();
13876 PauseEngine (ChessProgramState *cps)
13878 SendToProgram("pause\n", cps);
13883 UnPauseEngine (ChessProgramState *cps)
13885 SendToProgram("resume\n", cps);
13892 if (appData.debugMode)
13893 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13897 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13899 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13900 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13901 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13903 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13904 HandleMachineMove(stashedInputMove, stalledEngine);
13905 stalledEngine = NULL;
13908 if (gameMode == MachinePlaysWhite ||
13909 gameMode == TwoMachinesPlay ||
13910 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13911 if(first.pause) UnPauseEngine(&first);
13912 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13913 if(second.pause) UnPauseEngine(&second);
13914 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13917 DisplayBothClocks();
13919 if (gameMode == PlayFromGameFile) {
13920 if (appData.timeDelay >= 0)
13921 AutoPlayGameLoop();
13922 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13923 Reset(FALSE, TRUE);
13924 SendToICS(ics_prefix);
13925 SendToICS("refresh\n");
13926 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13927 ForwardInner(forwardMostMove);
13929 pauseExamInvalid = FALSE;
13931 switch (gameMode) {
13935 pauseExamForwardMostMove = forwardMostMove;
13936 pauseExamInvalid = FALSE;
13939 case IcsPlayingWhite:
13940 case IcsPlayingBlack:
13944 case PlayFromGameFile:
13945 (void) StopLoadGameTimer();
13949 case BeginningOfGame:
13950 if (appData.icsActive) return;
13951 /* else fall through */
13952 case MachinePlaysWhite:
13953 case MachinePlaysBlack:
13954 case TwoMachinesPlay:
13955 if (forwardMostMove == 0)
13956 return; /* don't pause if no one has moved */
13957 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13958 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13959 if(onMove->pause) { // thinking engine can be paused
13960 PauseEngine(onMove); // do it
13961 if(onMove->other->pause) // pondering opponent can always be paused immediately
13962 PauseEngine(onMove->other);
13964 SendToProgram("easy\n", onMove->other);
13966 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13967 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13969 PauseEngine(&first);
13971 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13972 } else { // human on move, pause pondering by either method
13974 PauseEngine(&first);
13975 else if(appData.ponderNextMove)
13976 SendToProgram("easy\n", &first);
13979 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13989 EditCommentEvent ()
13991 char title[MSG_SIZ];
13993 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13994 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13996 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13997 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13998 parseList[currentMove - 1]);
14001 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14008 char *tags = PGNTags(&gameInfo);
14010 EditTagsPopUp(tags, NULL);
14017 if(second.analyzing) {
14018 SendToProgram("exit\n", &second);
14019 second.analyzing = FALSE;
14021 if (second.pr == NoProc) StartChessProgram(&second);
14022 InitChessProgram(&second, FALSE);
14023 FeedMovesToProgram(&second, currentMove);
14025 SendToProgram("analyze\n", &second);
14026 second.analyzing = TRUE;
14030 /* Toggle ShowThinking */
14032 ToggleShowThinking()
14034 appData.showThinking = !appData.showThinking;
14035 ShowThinkingEvent();
14039 AnalyzeModeEvent ()
14043 if (!first.analysisSupport) {
14044 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14045 DisplayError(buf, 0);
14048 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14049 if (appData.icsActive) {
14050 if (gameMode != IcsObserving) {
14051 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14052 DisplayError(buf, 0);
14054 if (appData.icsEngineAnalyze) {
14055 if (appData.debugMode)
14056 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14062 /* if enable, user wants to disable icsEngineAnalyze */
14063 if (appData.icsEngineAnalyze) {
14068 appData.icsEngineAnalyze = TRUE;
14069 if (appData.debugMode)
14070 fprintf(debugFP, "ICS engine analyze starting... \n");
14073 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14074 if (appData.noChessProgram || gameMode == AnalyzeMode)
14077 if (gameMode != AnalyzeFile) {
14078 if (!appData.icsEngineAnalyze) {
14080 if (gameMode != EditGame) return 0;
14082 if (!appData.showThinking) ToggleShowThinking();
14083 ResurrectChessProgram();
14084 SendToProgram("analyze\n", &first);
14085 first.analyzing = TRUE;
14086 /*first.maybeThinking = TRUE;*/
14087 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14088 EngineOutputPopUp();
14090 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14095 StartAnalysisClock();
14096 GetTimeMark(&lastNodeCountTime);
14102 AnalyzeFileEvent ()
14104 if (appData.noChessProgram || gameMode == AnalyzeFile)
14107 if (!first.analysisSupport) {
14109 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14110 DisplayError(buf, 0);
14114 if (gameMode != AnalyzeMode) {
14115 keepInfo = 1; // mere annotating should not alter PGN tags
14118 if (gameMode != EditGame) return;
14119 if (!appData.showThinking) ToggleShowThinking();
14120 ResurrectChessProgram();
14121 SendToProgram("analyze\n", &first);
14122 first.analyzing = TRUE;
14123 /*first.maybeThinking = TRUE;*/
14124 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14125 EngineOutputPopUp();
14127 gameMode = AnalyzeFile;
14131 StartAnalysisClock();
14132 GetTimeMark(&lastNodeCountTime);
14134 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14135 AnalysisPeriodicEvent(1);
14139 MachineWhiteEvent ()
14142 char *bookHit = NULL;
14144 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14148 if (gameMode == PlayFromGameFile ||
14149 gameMode == TwoMachinesPlay ||
14150 gameMode == Training ||
14151 gameMode == AnalyzeMode ||
14152 gameMode == EndOfGame)
14155 if (gameMode == EditPosition)
14156 EditPositionDone(TRUE);
14158 if (!WhiteOnMove(currentMove)) {
14159 DisplayError(_("It is not White's turn"), 0);
14163 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14166 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14167 gameMode == AnalyzeFile)
14170 ResurrectChessProgram(); /* in case it isn't running */
14171 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14172 gameMode = MachinePlaysWhite;
14175 gameMode = MachinePlaysWhite;
14179 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14181 if (first.sendName) {
14182 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14183 SendToProgram(buf, &first);
14185 if (first.sendTime) {
14186 if (first.useColors) {
14187 SendToProgram("black\n", &first); /*gnu kludge*/
14189 SendTimeRemaining(&first, TRUE);
14191 if (first.useColors) {
14192 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14194 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14195 SetMachineThinkingEnables();
14196 first.maybeThinking = TRUE;
14200 if (appData.autoFlipView && !flipView) {
14201 flipView = !flipView;
14202 DrawPosition(FALSE, NULL);
14203 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14206 if(bookHit) { // [HGM] book: simulate book reply
14207 static char bookMove[MSG_SIZ]; // a bit generous?
14209 programStats.nodes = programStats.depth = programStats.time =
14210 programStats.score = programStats.got_only_move = 0;
14211 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14213 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14214 strcat(bookMove, bookHit);
14215 HandleMachineMove(bookMove, &first);
14220 MachineBlackEvent ()
14223 char *bookHit = NULL;
14225 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14229 if (gameMode == PlayFromGameFile ||
14230 gameMode == TwoMachinesPlay ||
14231 gameMode == Training ||
14232 gameMode == AnalyzeMode ||
14233 gameMode == EndOfGame)
14236 if (gameMode == EditPosition)
14237 EditPositionDone(TRUE);
14239 if (WhiteOnMove(currentMove)) {
14240 DisplayError(_("It is not Black's turn"), 0);
14244 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14247 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14248 gameMode == AnalyzeFile)
14251 ResurrectChessProgram(); /* in case it isn't running */
14252 gameMode = MachinePlaysBlack;
14256 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14258 if (first.sendName) {
14259 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14260 SendToProgram(buf, &first);
14262 if (first.sendTime) {
14263 if (first.useColors) {
14264 SendToProgram("white\n", &first); /*gnu kludge*/
14266 SendTimeRemaining(&first, FALSE);
14268 if (first.useColors) {
14269 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14271 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14272 SetMachineThinkingEnables();
14273 first.maybeThinking = TRUE;
14276 if (appData.autoFlipView && flipView) {
14277 flipView = !flipView;
14278 DrawPosition(FALSE, NULL);
14279 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14281 if(bookHit) { // [HGM] book: simulate book reply
14282 static char bookMove[MSG_SIZ]; // a bit generous?
14284 programStats.nodes = programStats.depth = programStats.time =
14285 programStats.score = programStats.got_only_move = 0;
14286 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14288 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14289 strcat(bookMove, bookHit);
14290 HandleMachineMove(bookMove, &first);
14296 DisplayTwoMachinesTitle ()
14299 if (appData.matchGames > 0) {
14300 if(appData.tourneyFile[0]) {
14301 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14302 gameInfo.white, _("vs."), gameInfo.black,
14303 nextGame+1, appData.matchGames+1,
14304 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14306 if (first.twoMachinesColor[0] == 'w') {
14307 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14308 gameInfo.white, _("vs."), gameInfo.black,
14309 first.matchWins, second.matchWins,
14310 matchGame - 1 - (first.matchWins + second.matchWins));
14312 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14313 gameInfo.white, _("vs."), gameInfo.black,
14314 second.matchWins, first.matchWins,
14315 matchGame - 1 - (first.matchWins + second.matchWins));
14318 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14324 SettingsMenuIfReady ()
14326 if (second.lastPing != second.lastPong) {
14327 DisplayMessage("", _("Waiting for second chess program"));
14328 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14332 DisplayMessage("", "");
14333 SettingsPopUp(&second);
14337 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14340 if (cps->pr == NoProc) {
14341 StartChessProgram(cps);
14342 if (cps->protocolVersion == 1) {
14344 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14346 /* kludge: allow timeout for initial "feature" command */
14347 if(retry != TwoMachinesEventIfReady) FreezeUI();
14348 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14349 DisplayMessage("", buf);
14350 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14358 TwoMachinesEvent P((void))
14362 ChessProgramState *onmove;
14363 char *bookHit = NULL;
14364 static int stalling = 0;
14368 if (appData.noChessProgram) return;
14370 switch (gameMode) {
14371 case TwoMachinesPlay:
14373 case MachinePlaysWhite:
14374 case MachinePlaysBlack:
14375 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14376 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14380 case BeginningOfGame:
14381 case PlayFromGameFile:
14384 if (gameMode != EditGame) return;
14387 EditPositionDone(TRUE);
14398 // forwardMostMove = currentMove;
14399 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14400 startingEngine = TRUE;
14402 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14404 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14405 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14406 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14409 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14411 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14412 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14413 startingEngine = FALSE;
14414 DisplayError("second engine does not play this", 0);
14419 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14420 SendToProgram("force\n", &second);
14422 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14425 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14426 if(appData.matchPause>10000 || appData.matchPause<10)
14427 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14428 wait = SubtractTimeMarks(&now, &pauseStart);
14429 if(wait < appData.matchPause) {
14430 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14433 // we are now committed to starting the game
14435 DisplayMessage("", "");
14436 if (startedFromSetupPosition) {
14437 SendBoard(&second, backwardMostMove);
14438 if (appData.debugMode) {
14439 fprintf(debugFP, "Two Machines\n");
14442 for (i = backwardMostMove; i < forwardMostMove; i++) {
14443 SendMoveToProgram(i, &second);
14446 gameMode = TwoMachinesPlay;
14447 pausing = startingEngine = FALSE;
14448 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14450 DisplayTwoMachinesTitle();
14452 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14457 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14458 SendToProgram(first.computerString, &first);
14459 if (first.sendName) {
14460 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14461 SendToProgram(buf, &first);
14463 SendToProgram(second.computerString, &second);
14464 if (second.sendName) {
14465 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14466 SendToProgram(buf, &second);
14470 if (!first.sendTime || !second.sendTime) {
14471 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14472 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14474 if (onmove->sendTime) {
14475 if (onmove->useColors) {
14476 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14478 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14480 if (onmove->useColors) {
14481 SendToProgram(onmove->twoMachinesColor, onmove);
14483 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14484 // SendToProgram("go\n", onmove);
14485 onmove->maybeThinking = TRUE;
14486 SetMachineThinkingEnables();
14490 if(bookHit) { // [HGM] book: simulate book reply
14491 static char bookMove[MSG_SIZ]; // a bit generous?
14493 programStats.nodes = programStats.depth = programStats.time =
14494 programStats.score = programStats.got_only_move = 0;
14495 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14497 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14498 strcat(bookMove, bookHit);
14499 savedMessage = bookMove; // args for deferred call
14500 savedState = onmove;
14501 ScheduleDelayedEvent(DeferredBookMove, 1);
14508 if (gameMode == Training) {
14509 SetTrainingModeOff();
14510 gameMode = PlayFromGameFile;
14511 DisplayMessage("", _("Training mode off"));
14513 gameMode = Training;
14514 animateTraining = appData.animate;
14516 /* make sure we are not already at the end of the game */
14517 if (currentMove < forwardMostMove) {
14518 SetTrainingModeOn();
14519 DisplayMessage("", _("Training mode on"));
14521 gameMode = PlayFromGameFile;
14522 DisplayError(_("Already at end of game"), 0);
14531 if (!appData.icsActive) return;
14532 switch (gameMode) {
14533 case IcsPlayingWhite:
14534 case IcsPlayingBlack:
14537 case BeginningOfGame:
14545 EditPositionDone(TRUE);
14558 gameMode = IcsIdle;
14568 switch (gameMode) {
14570 SetTrainingModeOff();
14572 case MachinePlaysWhite:
14573 case MachinePlaysBlack:
14574 case BeginningOfGame:
14575 SendToProgram("force\n", &first);
14576 SetUserThinkingEnables();
14578 case PlayFromGameFile:
14579 (void) StopLoadGameTimer();
14580 if (gameFileFP != NULL) {
14585 EditPositionDone(TRUE);
14590 SendToProgram("force\n", &first);
14592 case TwoMachinesPlay:
14593 GameEnds(EndOfFile, NULL, GE_PLAYER);
14594 ResurrectChessProgram();
14595 SetUserThinkingEnables();
14598 ResurrectChessProgram();
14600 case IcsPlayingBlack:
14601 case IcsPlayingWhite:
14602 DisplayError(_("Warning: You are still playing a game"), 0);
14605 DisplayError(_("Warning: You are still observing a game"), 0);
14608 DisplayError(_("Warning: You are still examining a game"), 0);
14619 first.offeredDraw = second.offeredDraw = 0;
14621 if (gameMode == PlayFromGameFile) {
14622 whiteTimeRemaining = timeRemaining[0][currentMove];
14623 blackTimeRemaining = timeRemaining[1][currentMove];
14627 if (gameMode == MachinePlaysWhite ||
14628 gameMode == MachinePlaysBlack ||
14629 gameMode == TwoMachinesPlay ||
14630 gameMode == EndOfGame) {
14631 i = forwardMostMove;
14632 while (i > currentMove) {
14633 SendToProgram("undo\n", &first);
14636 if(!adjustedClock) {
14637 whiteTimeRemaining = timeRemaining[0][currentMove];
14638 blackTimeRemaining = timeRemaining[1][currentMove];
14639 DisplayBothClocks();
14641 if (whiteFlag || blackFlag) {
14642 whiteFlag = blackFlag = 0;
14647 gameMode = EditGame;
14654 EditPositionEvent ()
14656 if (gameMode == EditPosition) {
14662 if (gameMode != EditGame) return;
14664 gameMode = EditPosition;
14667 if (currentMove > 0)
14668 CopyBoard(boards[0], boards[currentMove]);
14670 blackPlaysFirst = !WhiteOnMove(currentMove);
14672 currentMove = forwardMostMove = backwardMostMove = 0;
14673 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14675 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14681 /* [DM] icsEngineAnalyze - possible call from other functions */
14682 if (appData.icsEngineAnalyze) {
14683 appData.icsEngineAnalyze = FALSE;
14685 DisplayMessage("",_("Close ICS engine analyze..."));
14687 if (first.analysisSupport && first.analyzing) {
14688 SendToBoth("exit\n");
14689 first.analyzing = second.analyzing = FALSE;
14691 thinkOutput[0] = NULLCHAR;
14695 EditPositionDone (Boolean fakeRights)
14697 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14699 startedFromSetupPosition = TRUE;
14700 InitChessProgram(&first, FALSE);
14701 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14702 boards[0][EP_STATUS] = EP_NONE;
14703 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14704 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14705 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14706 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14707 } else boards[0][CASTLING][2] = NoRights;
14708 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14709 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14710 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14711 } else boards[0][CASTLING][5] = NoRights;
14712 if(gameInfo.variant == VariantSChess) {
14714 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14715 boards[0][VIRGIN][i] = 0;
14716 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14717 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14721 SendToProgram("force\n", &first);
14722 if (blackPlaysFirst) {
14723 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14724 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14725 currentMove = forwardMostMove = backwardMostMove = 1;
14726 CopyBoard(boards[1], boards[0]);
14728 currentMove = forwardMostMove = backwardMostMove = 0;
14730 SendBoard(&first, forwardMostMove);
14731 if (appData.debugMode) {
14732 fprintf(debugFP, "EditPosDone\n");
14735 DisplayMessage("", "");
14736 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14737 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14738 gameMode = EditGame;
14740 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14741 ClearHighlights(); /* [AS] */
14744 /* Pause for `ms' milliseconds */
14745 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14747 TimeDelay (long ms)
14754 } while (SubtractTimeMarks(&m2, &m1) < ms);
14757 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14759 SendMultiLineToICS (char *buf)
14761 char temp[MSG_SIZ+1], *p;
14768 strncpy(temp, buf, len);
14773 if (*p == '\n' || *p == '\r')
14778 strcat(temp, "\n");
14780 SendToPlayer(temp, strlen(temp));
14784 SetWhiteToPlayEvent ()
14786 if (gameMode == EditPosition) {
14787 blackPlaysFirst = FALSE;
14788 DisplayBothClocks(); /* works because currentMove is 0 */
14789 } else if (gameMode == IcsExamining) {
14790 SendToICS(ics_prefix);
14791 SendToICS("tomove white\n");
14796 SetBlackToPlayEvent ()
14798 if (gameMode == EditPosition) {
14799 blackPlaysFirst = TRUE;
14800 currentMove = 1; /* kludge */
14801 DisplayBothClocks();
14803 } else if (gameMode == IcsExamining) {
14804 SendToICS(ics_prefix);
14805 SendToICS("tomove black\n");
14810 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14813 ChessSquare piece = boards[0][y][x];
14814 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14815 static int lastVariant;
14817 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14819 switch (selection) {
14821 CopyBoard(currentBoard, boards[0]);
14822 CopyBoard(menuBoard, initialPosition);
14823 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14824 SendToICS(ics_prefix);
14825 SendToICS("bsetup clear\n");
14826 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14827 SendToICS(ics_prefix);
14828 SendToICS("clearboard\n");
14831 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14832 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14833 for (y = 0; y < BOARD_HEIGHT; y++) {
14834 if (gameMode == IcsExamining) {
14835 if (boards[currentMove][y][x] != EmptySquare) {
14836 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14841 if(boards[0][y][x] != p) nonEmpty++;
14842 boards[0][y][x] = p;
14845 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14847 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14848 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
14849 ChessSquare p = menuBoard[0][x];
14850 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14851 p = menuBoard[BOARD_HEIGHT-1][x];
14852 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14854 DisplayMessage("Clicking clock again restores position", "");
14855 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14856 if(!nonEmpty) { // asked to clear an empty board
14857 CopyBoard(boards[0], menuBoard);
14859 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14860 CopyBoard(boards[0], initialPosition);
14862 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14863 && !CompareBoards(nullBoard, erasedBoard)) {
14864 CopyBoard(boards[0], erasedBoard);
14866 CopyBoard(erasedBoard, currentBoard);
14870 if (gameMode == EditPosition) {
14871 DrawPosition(FALSE, boards[0]);
14876 SetWhiteToPlayEvent();
14880 SetBlackToPlayEvent();
14884 if (gameMode == IcsExamining) {
14885 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14886 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14889 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14890 if(x == BOARD_LEFT-2) {
14891 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14892 boards[0][y][1] = 0;
14894 if(x == BOARD_RGHT+1) {
14895 if(y >= gameInfo.holdingsSize) break;
14896 boards[0][y][BOARD_WIDTH-2] = 0;
14899 boards[0][y][x] = EmptySquare;
14900 DrawPosition(FALSE, boards[0]);
14905 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14906 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14907 selection = (ChessSquare) (PROMOTED piece);
14908 } else if(piece == EmptySquare) selection = WhiteSilver;
14909 else selection = (ChessSquare)((int)piece - 1);
14913 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14914 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14915 selection = (ChessSquare) (DEMOTED piece);
14916 } else if(piece == EmptySquare) selection = BlackSilver;
14917 else selection = (ChessSquare)((int)piece + 1);
14922 if(gameInfo.variant == VariantShatranj ||
14923 gameInfo.variant == VariantXiangqi ||
14924 gameInfo.variant == VariantCourier ||
14925 gameInfo.variant == VariantASEAN ||
14926 gameInfo.variant == VariantMakruk )
14927 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14932 if(gameInfo.variant == VariantXiangqi)
14933 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14934 if(gameInfo.variant == VariantKnightmate)
14935 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14938 if (gameMode == IcsExamining) {
14939 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14940 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14941 PieceToChar(selection), AAA + x, ONE + y);
14944 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14946 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14947 n = PieceToNumber(selection - BlackPawn);
14948 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14949 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14950 boards[0][BOARD_HEIGHT-1-n][1]++;
14952 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14953 n = PieceToNumber(selection);
14954 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14955 boards[0][n][BOARD_WIDTH-1] = selection;
14956 boards[0][n][BOARD_WIDTH-2]++;
14959 boards[0][y][x] = selection;
14960 DrawPosition(TRUE, boards[0]);
14962 fromX = fromY = -1;
14970 DropMenuEvent (ChessSquare selection, int x, int y)
14972 ChessMove moveType;
14974 switch (gameMode) {
14975 case IcsPlayingWhite:
14976 case MachinePlaysBlack:
14977 if (!WhiteOnMove(currentMove)) {
14978 DisplayMoveError(_("It is Black's turn"));
14981 moveType = WhiteDrop;
14983 case IcsPlayingBlack:
14984 case MachinePlaysWhite:
14985 if (WhiteOnMove(currentMove)) {
14986 DisplayMoveError(_("It is White's turn"));
14989 moveType = BlackDrop;
14992 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14998 if (moveType == BlackDrop && selection < BlackPawn) {
14999 selection = (ChessSquare) ((int) selection
15000 + (int) BlackPawn - (int) WhitePawn);
15002 if (boards[currentMove][y][x] != EmptySquare) {
15003 DisplayMoveError(_("That square is occupied"));
15007 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15013 /* Accept a pending offer of any kind from opponent */
15015 if (appData.icsActive) {
15016 SendToICS(ics_prefix);
15017 SendToICS("accept\n");
15018 } else if (cmailMsgLoaded) {
15019 if (currentMove == cmailOldMove &&
15020 commentList[cmailOldMove] != NULL &&
15021 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15022 "Black offers a draw" : "White offers a draw")) {
15024 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15025 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15027 DisplayError(_("There is no pending offer on this move"), 0);
15028 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15031 /* Not used for offers from chess program */
15038 /* Decline a pending offer of any kind from opponent */
15040 if (appData.icsActive) {
15041 SendToICS(ics_prefix);
15042 SendToICS("decline\n");
15043 } else if (cmailMsgLoaded) {
15044 if (currentMove == cmailOldMove &&
15045 commentList[cmailOldMove] != NULL &&
15046 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15047 "Black offers a draw" : "White offers a draw")) {
15049 AppendComment(cmailOldMove, "Draw declined", TRUE);
15050 DisplayComment(cmailOldMove - 1, "Draw declined");
15053 DisplayError(_("There is no pending offer on this move"), 0);
15056 /* Not used for offers from chess program */
15063 /* Issue ICS rematch command */
15064 if (appData.icsActive) {
15065 SendToICS(ics_prefix);
15066 SendToICS("rematch\n");
15073 /* Call your opponent's flag (claim a win on time) */
15074 if (appData.icsActive) {
15075 SendToICS(ics_prefix);
15076 SendToICS("flag\n");
15078 switch (gameMode) {
15081 case MachinePlaysWhite:
15084 GameEnds(GameIsDrawn, "Both players ran out of time",
15087 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15089 DisplayError(_("Your opponent is not out of time"), 0);
15092 case MachinePlaysBlack:
15095 GameEnds(GameIsDrawn, "Both players ran out of time",
15098 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15100 DisplayError(_("Your opponent is not out of time"), 0);
15108 ClockClick (int which)
15109 { // [HGM] code moved to back-end from winboard.c
15110 if(which) { // black clock
15111 if (gameMode == EditPosition || gameMode == IcsExamining) {
15112 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15113 SetBlackToPlayEvent();
15114 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15115 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15116 } else if (shiftKey) {
15117 AdjustClock(which, -1);
15118 } else if (gameMode == IcsPlayingWhite ||
15119 gameMode == MachinePlaysBlack) {
15122 } else { // white clock
15123 if (gameMode == EditPosition || gameMode == IcsExamining) {
15124 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15125 SetWhiteToPlayEvent();
15126 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15127 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15128 } else if (shiftKey) {
15129 AdjustClock(which, -1);
15130 } else if (gameMode == IcsPlayingBlack ||
15131 gameMode == MachinePlaysWhite) {
15140 /* Offer draw or accept pending draw offer from opponent */
15142 if (appData.icsActive) {
15143 /* Note: tournament rules require draw offers to be
15144 made after you make your move but before you punch
15145 your clock. Currently ICS doesn't let you do that;
15146 instead, you immediately punch your clock after making
15147 a move, but you can offer a draw at any time. */
15149 SendToICS(ics_prefix);
15150 SendToICS("draw\n");
15151 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15152 } else if (cmailMsgLoaded) {
15153 if (currentMove == cmailOldMove &&
15154 commentList[cmailOldMove] != NULL &&
15155 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15156 "Black offers a draw" : "White offers a draw")) {
15157 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15158 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15159 } else if (currentMove == cmailOldMove + 1) {
15160 char *offer = WhiteOnMove(cmailOldMove) ?
15161 "White offers a draw" : "Black offers a draw";
15162 AppendComment(currentMove, offer, TRUE);
15163 DisplayComment(currentMove - 1, offer);
15164 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15166 DisplayError(_("You must make your move before offering a draw"), 0);
15167 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15169 } else if (first.offeredDraw) {
15170 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15172 if (first.sendDrawOffers) {
15173 SendToProgram("draw\n", &first);
15174 userOfferedDraw = TRUE;
15182 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15184 if (appData.icsActive) {
15185 SendToICS(ics_prefix);
15186 SendToICS("adjourn\n");
15188 /* Currently GNU Chess doesn't offer or accept Adjourns */
15196 /* Offer Abort or accept pending Abort offer from opponent */
15198 if (appData.icsActive) {
15199 SendToICS(ics_prefix);
15200 SendToICS("abort\n");
15202 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15209 /* Resign. You can do this even if it's not your turn. */
15211 if (appData.icsActive) {
15212 SendToICS(ics_prefix);
15213 SendToICS("resign\n");
15215 switch (gameMode) {
15216 case MachinePlaysWhite:
15217 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15219 case MachinePlaysBlack:
15220 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15223 if (cmailMsgLoaded) {
15225 if (WhiteOnMove(cmailOldMove)) {
15226 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15228 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15230 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15241 StopObservingEvent ()
15243 /* Stop observing current games */
15244 SendToICS(ics_prefix);
15245 SendToICS("unobserve\n");
15249 StopExaminingEvent ()
15251 /* Stop observing current game */
15252 SendToICS(ics_prefix);
15253 SendToICS("unexamine\n");
15257 ForwardInner (int target)
15259 int limit; int oldSeekGraphUp = seekGraphUp;
15261 if (appData.debugMode)
15262 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15263 target, currentMove, forwardMostMove);
15265 if (gameMode == EditPosition)
15268 seekGraphUp = FALSE;
15269 MarkTargetSquares(1);
15271 if (gameMode == PlayFromGameFile && !pausing)
15274 if (gameMode == IcsExamining && pausing)
15275 limit = pauseExamForwardMostMove;
15277 limit = forwardMostMove;
15279 if (target > limit) target = limit;
15281 if (target > 0 && moveList[target - 1][0]) {
15282 int fromX, fromY, toX, toY;
15283 toX = moveList[target - 1][2] - AAA;
15284 toY = moveList[target - 1][3] - ONE;
15285 if (moveList[target - 1][1] == '@') {
15286 if (appData.highlightLastMove) {
15287 SetHighlights(-1, -1, toX, toY);
15290 fromX = moveList[target - 1][0] - AAA;
15291 fromY = moveList[target - 1][1] - ONE;
15292 if (target == currentMove + 1) {
15293 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15295 if (appData.highlightLastMove) {
15296 SetHighlights(fromX, fromY, toX, toY);
15300 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15301 gameMode == Training || gameMode == PlayFromGameFile ||
15302 gameMode == AnalyzeFile) {
15303 while (currentMove < target) {
15304 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15305 SendMoveToProgram(currentMove++, &first);
15308 currentMove = target;
15311 if (gameMode == EditGame || gameMode == EndOfGame) {
15312 whiteTimeRemaining = timeRemaining[0][currentMove];
15313 blackTimeRemaining = timeRemaining[1][currentMove];
15315 DisplayBothClocks();
15316 DisplayMove(currentMove - 1);
15317 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15318 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15319 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15320 DisplayComment(currentMove - 1, commentList[currentMove]);
15322 ClearMap(); // [HGM] exclude: invalidate map
15329 if (gameMode == IcsExamining && !pausing) {
15330 SendToICS(ics_prefix);
15331 SendToICS("forward\n");
15333 ForwardInner(currentMove + 1);
15340 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15341 /* to optimze, we temporarily turn off analysis mode while we feed
15342 * the remaining moves to the engine. Otherwise we get analysis output
15345 if (first.analysisSupport) {
15346 SendToProgram("exit\nforce\n", &first);
15347 first.analyzing = FALSE;
15351 if (gameMode == IcsExamining && !pausing) {
15352 SendToICS(ics_prefix);
15353 SendToICS("forward 999999\n");
15355 ForwardInner(forwardMostMove);
15358 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15359 /* we have fed all the moves, so reactivate analysis mode */
15360 SendToProgram("analyze\n", &first);
15361 first.analyzing = TRUE;
15362 /*first.maybeThinking = TRUE;*/
15363 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15368 BackwardInner (int target)
15370 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15372 if (appData.debugMode)
15373 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15374 target, currentMove, forwardMostMove);
15376 if (gameMode == EditPosition) return;
15377 seekGraphUp = FALSE;
15378 MarkTargetSquares(1);
15379 if (currentMove <= backwardMostMove) {
15381 DrawPosition(full_redraw, boards[currentMove]);
15384 if (gameMode == PlayFromGameFile && !pausing)
15387 if (moveList[target][0]) {
15388 int fromX, fromY, toX, toY;
15389 toX = moveList[target][2] - AAA;
15390 toY = moveList[target][3] - ONE;
15391 if (moveList[target][1] == '@') {
15392 if (appData.highlightLastMove) {
15393 SetHighlights(-1, -1, toX, toY);
15396 fromX = moveList[target][0] - AAA;
15397 fromY = moveList[target][1] - ONE;
15398 if (target == currentMove - 1) {
15399 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15401 if (appData.highlightLastMove) {
15402 SetHighlights(fromX, fromY, toX, toY);
15406 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15407 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15408 while (currentMove > target) {
15409 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15410 // null move cannot be undone. Reload program with move history before it.
15412 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15413 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15415 SendBoard(&first, i);
15416 if(second.analyzing) SendBoard(&second, i);
15417 for(currentMove=i; currentMove<target; currentMove++) {
15418 SendMoveToProgram(currentMove, &first);
15419 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15423 SendToBoth("undo\n");
15427 currentMove = target;
15430 if (gameMode == EditGame || gameMode == EndOfGame) {
15431 whiteTimeRemaining = timeRemaining[0][currentMove];
15432 blackTimeRemaining = timeRemaining[1][currentMove];
15434 DisplayBothClocks();
15435 DisplayMove(currentMove - 1);
15436 DrawPosition(full_redraw, boards[currentMove]);
15437 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15438 // [HGM] PV info: routine tests if comment empty
15439 DisplayComment(currentMove - 1, commentList[currentMove]);
15440 ClearMap(); // [HGM] exclude: invalidate map
15446 if (gameMode == IcsExamining && !pausing) {
15447 SendToICS(ics_prefix);
15448 SendToICS("backward\n");
15450 BackwardInner(currentMove - 1);
15457 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15458 /* to optimize, we temporarily turn off analysis mode while we undo
15459 * all the moves. Otherwise we get analysis output after each undo.
15461 if (first.analysisSupport) {
15462 SendToProgram("exit\nforce\n", &first);
15463 first.analyzing = FALSE;
15467 if (gameMode == IcsExamining && !pausing) {
15468 SendToICS(ics_prefix);
15469 SendToICS("backward 999999\n");
15471 BackwardInner(backwardMostMove);
15474 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15475 /* we have fed all the moves, so reactivate analysis mode */
15476 SendToProgram("analyze\n", &first);
15477 first.analyzing = TRUE;
15478 /*first.maybeThinking = TRUE;*/
15479 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15486 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15487 if (to >= forwardMostMove) to = forwardMostMove;
15488 if (to <= backwardMostMove) to = backwardMostMove;
15489 if (to < currentMove) {
15497 RevertEvent (Boolean annotate)
15499 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15502 if (gameMode != IcsExamining) {
15503 DisplayError(_("You are not examining a game"), 0);
15507 DisplayError(_("You can't revert while pausing"), 0);
15510 SendToICS(ics_prefix);
15511 SendToICS("revert\n");
15515 RetractMoveEvent ()
15517 switch (gameMode) {
15518 case MachinePlaysWhite:
15519 case MachinePlaysBlack:
15520 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15521 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15524 if (forwardMostMove < 2) return;
15525 currentMove = forwardMostMove = forwardMostMove - 2;
15526 whiteTimeRemaining = timeRemaining[0][currentMove];
15527 blackTimeRemaining = timeRemaining[1][currentMove];
15528 DisplayBothClocks();
15529 DisplayMove(currentMove - 1);
15530 ClearHighlights();/*!! could figure this out*/
15531 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15532 SendToProgram("remove\n", &first);
15533 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15536 case BeginningOfGame:
15540 case IcsPlayingWhite:
15541 case IcsPlayingBlack:
15542 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15543 SendToICS(ics_prefix);
15544 SendToICS("takeback 2\n");
15546 SendToICS(ics_prefix);
15547 SendToICS("takeback 1\n");
15556 ChessProgramState *cps;
15558 switch (gameMode) {
15559 case MachinePlaysWhite:
15560 if (!WhiteOnMove(forwardMostMove)) {
15561 DisplayError(_("It is your turn"), 0);
15566 case MachinePlaysBlack:
15567 if (WhiteOnMove(forwardMostMove)) {
15568 DisplayError(_("It is your turn"), 0);
15573 case TwoMachinesPlay:
15574 if (WhiteOnMove(forwardMostMove) ==
15575 (first.twoMachinesColor[0] == 'w')) {
15581 case BeginningOfGame:
15585 SendToProgram("?\n", cps);
15589 TruncateGameEvent ()
15592 if (gameMode != EditGame) return;
15599 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15600 if (forwardMostMove > currentMove) {
15601 if (gameInfo.resultDetails != NULL) {
15602 free(gameInfo.resultDetails);
15603 gameInfo.resultDetails = NULL;
15604 gameInfo.result = GameUnfinished;
15606 forwardMostMove = currentMove;
15607 HistorySet(parseList, backwardMostMove, forwardMostMove,
15615 if (appData.noChessProgram) return;
15616 switch (gameMode) {
15617 case MachinePlaysWhite:
15618 if (WhiteOnMove(forwardMostMove)) {
15619 DisplayError(_("Wait until your turn."), 0);
15623 case BeginningOfGame:
15624 case MachinePlaysBlack:
15625 if (!WhiteOnMove(forwardMostMove)) {
15626 DisplayError(_("Wait until your turn."), 0);
15631 DisplayError(_("No hint available"), 0);
15634 SendToProgram("hint\n", &first);
15635 hintRequested = TRUE;
15641 ListGame * lg = (ListGame *) gameList.head;
15644 static int secondTime = FALSE;
15646 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15647 DisplayError(_("Game list not loaded or empty"), 0);
15651 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15654 DisplayNote(_("Book file exists! Try again for overwrite."));
15658 creatingBook = TRUE;
15659 secondTime = FALSE;
15661 /* Get list size */
15662 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15663 LoadGame(f, nItem, "", TRUE);
15664 AddGameToBook(TRUE);
15665 lg = (ListGame *) lg->node.succ;
15668 creatingBook = FALSE;
15675 if (appData.noChessProgram) return;
15676 switch (gameMode) {
15677 case MachinePlaysWhite:
15678 if (WhiteOnMove(forwardMostMove)) {
15679 DisplayError(_("Wait until your turn."), 0);
15683 case BeginningOfGame:
15684 case MachinePlaysBlack:
15685 if (!WhiteOnMove(forwardMostMove)) {
15686 DisplayError(_("Wait until your turn."), 0);
15691 EditPositionDone(TRUE);
15693 case TwoMachinesPlay:
15698 SendToProgram("bk\n", &first);
15699 bookOutput[0] = NULLCHAR;
15700 bookRequested = TRUE;
15706 char *tags = PGNTags(&gameInfo);
15707 TagsPopUp(tags, CmailMsg());
15711 /* end button procedures */
15714 PrintPosition (FILE *fp, int move)
15718 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15719 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15720 char c = PieceToChar(boards[move][i][j]);
15721 fputc(c == 'x' ? '.' : c, fp);
15722 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15725 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15726 fprintf(fp, "white to play\n");
15728 fprintf(fp, "black to play\n");
15732 PrintOpponents (FILE *fp)
15734 if (gameInfo.white != NULL) {
15735 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15741 /* Find last component of program's own name, using some heuristics */
15743 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15746 int local = (strcmp(host, "localhost") == 0);
15747 while (!local && (p = strchr(prog, ';')) != NULL) {
15749 while (*p == ' ') p++;
15752 if (*prog == '"' || *prog == '\'') {
15753 q = strchr(prog + 1, *prog);
15755 q = strchr(prog, ' ');
15757 if (q == NULL) q = prog + strlen(prog);
15759 while (p >= prog && *p != '/' && *p != '\\') p--;
15761 if(p == prog && *p == '"') p++;
15763 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15764 memcpy(buf, p, q - p);
15765 buf[q - p] = NULLCHAR;
15773 TimeControlTagValue ()
15776 if (!appData.clockMode) {
15777 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15778 } else if (movesPerSession > 0) {
15779 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15780 } else if (timeIncrement == 0) {
15781 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15783 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15785 return StrSave(buf);
15791 /* This routine is used only for certain modes */
15792 VariantClass v = gameInfo.variant;
15793 ChessMove r = GameUnfinished;
15796 if(keepInfo) return;
15798 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15799 r = gameInfo.result;
15800 p = gameInfo.resultDetails;
15801 gameInfo.resultDetails = NULL;
15803 ClearGameInfo(&gameInfo);
15804 gameInfo.variant = v;
15806 switch (gameMode) {
15807 case MachinePlaysWhite:
15808 gameInfo.event = StrSave( appData.pgnEventHeader );
15809 gameInfo.site = StrSave(HostName());
15810 gameInfo.date = PGNDate();
15811 gameInfo.round = StrSave("-");
15812 gameInfo.white = StrSave(first.tidy);
15813 gameInfo.black = StrSave(UserName());
15814 gameInfo.timeControl = TimeControlTagValue();
15817 case MachinePlaysBlack:
15818 gameInfo.event = StrSave( appData.pgnEventHeader );
15819 gameInfo.site = StrSave(HostName());
15820 gameInfo.date = PGNDate();
15821 gameInfo.round = StrSave("-");
15822 gameInfo.white = StrSave(UserName());
15823 gameInfo.black = StrSave(first.tidy);
15824 gameInfo.timeControl = TimeControlTagValue();
15827 case TwoMachinesPlay:
15828 gameInfo.event = StrSave( appData.pgnEventHeader );
15829 gameInfo.site = StrSave(HostName());
15830 gameInfo.date = PGNDate();
15833 snprintf(buf, MSG_SIZ, "%d", roundNr);
15834 gameInfo.round = StrSave(buf);
15836 gameInfo.round = StrSave("-");
15838 if (first.twoMachinesColor[0] == 'w') {
15839 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15840 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15842 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15843 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15845 gameInfo.timeControl = TimeControlTagValue();
15849 gameInfo.event = StrSave("Edited game");
15850 gameInfo.site = StrSave(HostName());
15851 gameInfo.date = PGNDate();
15852 gameInfo.round = StrSave("-");
15853 gameInfo.white = StrSave("-");
15854 gameInfo.black = StrSave("-");
15855 gameInfo.result = r;
15856 gameInfo.resultDetails = p;
15860 gameInfo.event = StrSave("Edited position");
15861 gameInfo.site = StrSave(HostName());
15862 gameInfo.date = PGNDate();
15863 gameInfo.round = StrSave("-");
15864 gameInfo.white = StrSave("-");
15865 gameInfo.black = StrSave("-");
15868 case IcsPlayingWhite:
15869 case IcsPlayingBlack:
15874 case PlayFromGameFile:
15875 gameInfo.event = StrSave("Game from non-PGN file");
15876 gameInfo.site = StrSave(HostName());
15877 gameInfo.date = PGNDate();
15878 gameInfo.round = StrSave("-");
15879 gameInfo.white = StrSave("?");
15880 gameInfo.black = StrSave("?");
15889 ReplaceComment (int index, char *text)
15895 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15896 pvInfoList[index-1].depth == len &&
15897 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15898 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15899 while (*text == '\n') text++;
15900 len = strlen(text);
15901 while (len > 0 && text[len - 1] == '\n') len--;
15903 if (commentList[index] != NULL)
15904 free(commentList[index]);
15907 commentList[index] = NULL;
15910 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15911 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15912 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15913 commentList[index] = (char *) malloc(len + 2);
15914 strncpy(commentList[index], text, len);
15915 commentList[index][len] = '\n';
15916 commentList[index][len + 1] = NULLCHAR;
15918 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15920 commentList[index] = (char *) malloc(len + 7);
15921 safeStrCpy(commentList[index], "{\n", 3);
15922 safeStrCpy(commentList[index]+2, text, len+1);
15923 commentList[index][len+2] = NULLCHAR;
15924 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15925 strcat(commentList[index], "\n}\n");
15930 CrushCRs (char *text)
15938 if (ch == '\r') continue;
15940 } while (ch != '\0');
15944 AppendComment (int index, char *text, Boolean addBraces)
15945 /* addBraces tells if we should add {} */
15950 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15951 if(addBraces == 3) addBraces = 0; else // force appending literally
15952 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15955 while (*text == '\n') text++;
15956 len = strlen(text);
15957 while (len > 0 && text[len - 1] == '\n') len--;
15958 text[len] = NULLCHAR;
15960 if (len == 0) return;
15962 if (commentList[index] != NULL) {
15963 Boolean addClosingBrace = addBraces;
15964 old = commentList[index];
15965 oldlen = strlen(old);
15966 while(commentList[index][oldlen-1] == '\n')
15967 commentList[index][--oldlen] = NULLCHAR;
15968 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15969 safeStrCpy(commentList[index], old, oldlen + len + 6);
15971 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15972 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15973 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15974 while (*text == '\n') { text++; len--; }
15975 commentList[index][--oldlen] = NULLCHAR;
15977 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15978 else strcat(commentList[index], "\n");
15979 strcat(commentList[index], text);
15980 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15981 else strcat(commentList[index], "\n");
15983 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15985 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15986 else commentList[index][0] = NULLCHAR;
15987 strcat(commentList[index], text);
15988 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15989 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15994 FindStr (char * text, char * sub_text)
15996 char * result = strstr( text, sub_text );
15998 if( result != NULL ) {
15999 result += strlen( sub_text );
16005 /* [AS] Try to extract PV info from PGN comment */
16006 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16008 GetInfoFromComment (int index, char * text)
16010 char * sep = text, *p;
16012 if( text != NULL && index > 0 ) {
16015 int time = -1, sec = 0, deci;
16016 char * s_eval = FindStr( text, "[%eval " );
16017 char * s_emt = FindStr( text, "[%emt " );
16019 if( s_eval != NULL || s_emt != NULL ) {
16021 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16026 if( s_eval != NULL ) {
16027 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16031 if( delim != ']' ) {
16036 if( s_emt != NULL ) {
16041 /* We expect something like: [+|-]nnn.nn/dd */
16044 if(*text != '{') return text; // [HGM] braces: must be normal comment
16046 sep = strchr( text, '/' );
16047 if( sep == NULL || sep < (text+4) ) {
16052 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16053 if(p[1] == '(') { // comment starts with PV
16054 p = strchr(p, ')'); // locate end of PV
16055 if(p == NULL || sep < p+5) return text;
16056 // at this point we have something like "{(.*) +0.23/6 ..."
16057 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16058 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16059 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16061 time = -1; sec = -1; deci = -1;
16062 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16063 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16064 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16065 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16069 if( score_lo < 0 || score_lo >= 100 ) {
16073 if(sec >= 0) time = 600*time + 10*sec; else
16074 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16076 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16078 /* [HGM] PV time: now locate end of PV info */
16079 while( *++sep >= '0' && *sep <= '9'); // strip depth
16081 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16083 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16085 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16086 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16097 pvInfoList[index-1].depth = depth;
16098 pvInfoList[index-1].score = score;
16099 pvInfoList[index-1].time = 10*time; // centi-sec
16100 if(*sep == '}') *sep = 0; else *--sep = '{';
16101 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16107 SendToProgram (char *message, ChessProgramState *cps)
16109 int count, outCount, error;
16112 if (cps->pr == NoProc) return;
16115 if (appData.debugMode) {
16118 fprintf(debugFP, "%ld >%-6s: %s",
16119 SubtractTimeMarks(&now, &programStartTime),
16120 cps->which, message);
16122 fprintf(serverFP, "%ld >%-6s: %s",
16123 SubtractTimeMarks(&now, &programStartTime),
16124 cps->which, message), fflush(serverFP);
16127 count = strlen(message);
16128 outCount = OutputToProcess(cps->pr, message, count, &error);
16129 if (outCount < count && !exiting
16130 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16131 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16132 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16133 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16134 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16135 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16136 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16137 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16139 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16140 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16141 gameInfo.result = res;
16143 gameInfo.resultDetails = StrSave(buf);
16145 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16146 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16151 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16155 ChessProgramState *cps = (ChessProgramState *)closure;
16157 if (isr != cps->isr) return; /* Killed intentionally */
16160 RemoveInputSource(cps->isr);
16161 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16162 _(cps->which), cps->program);
16163 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16164 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16165 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16166 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16167 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16168 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16170 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16171 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16172 gameInfo.result = res;
16174 gameInfo.resultDetails = StrSave(buf);
16176 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16177 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16179 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16180 _(cps->which), cps->program);
16181 RemoveInputSource(cps->isr);
16183 /* [AS] Program is misbehaving badly... kill it */
16184 if( count == -2 ) {
16185 DestroyChildProcess( cps->pr, 9 );
16189 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16194 if ((end_str = strchr(message, '\r')) != NULL)
16195 *end_str = NULLCHAR;
16196 if ((end_str = strchr(message, '\n')) != NULL)
16197 *end_str = NULLCHAR;
16199 if (appData.debugMode) {
16200 TimeMark now; int print = 1;
16201 char *quote = ""; char c; int i;
16203 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16204 char start = message[0];
16205 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16206 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16207 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16208 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16209 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16210 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16211 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16212 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16213 sscanf(message, "hint: %c", &c)!=1 &&
16214 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16215 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16216 print = (appData.engineComments >= 2);
16218 message[0] = start; // restore original message
16222 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16223 SubtractTimeMarks(&now, &programStartTime), cps->which,
16227 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16228 SubtractTimeMarks(&now, &programStartTime), cps->which,
16230 message), fflush(serverFP);
16234 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16235 if (appData.icsEngineAnalyze) {
16236 if (strstr(message, "whisper") != NULL ||
16237 strstr(message, "kibitz") != NULL ||
16238 strstr(message, "tellics") != NULL) return;
16241 HandleMachineMove(message, cps);
16246 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16251 if( timeControl_2 > 0 ) {
16252 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16253 tc = timeControl_2;
16256 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16257 inc /= cps->timeOdds;
16258 st /= cps->timeOdds;
16260 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16263 /* Set exact time per move, normally using st command */
16264 if (cps->stKludge) {
16265 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16267 if (seconds == 0) {
16268 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16270 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16273 snprintf(buf, MSG_SIZ, "st %d\n", st);
16276 /* Set conventional or incremental time control, using level command */
16277 if (seconds == 0) {
16278 /* Note old gnuchess bug -- minutes:seconds used to not work.
16279 Fixed in later versions, but still avoid :seconds
16280 when seconds is 0. */
16281 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16283 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16284 seconds, inc/1000.);
16287 SendToProgram(buf, cps);
16289 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16290 /* Orthogonally, limit search to given depth */
16292 if (cps->sdKludge) {
16293 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16295 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16297 SendToProgram(buf, cps);
16300 if(cps->nps >= 0) { /* [HGM] nps */
16301 if(cps->supportsNPS == FALSE)
16302 cps->nps = -1; // don't use if engine explicitly says not supported!
16304 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16305 SendToProgram(buf, cps);
16310 ChessProgramState *
16312 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16314 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16315 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16321 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16323 char message[MSG_SIZ];
16326 /* Note: this routine must be called when the clocks are stopped
16327 or when they have *just* been set or switched; otherwise
16328 it will be off by the time since the current tick started.
16330 if (machineWhite) {
16331 time = whiteTimeRemaining / 10;
16332 otime = blackTimeRemaining / 10;
16334 time = blackTimeRemaining / 10;
16335 otime = whiteTimeRemaining / 10;
16337 /* [HGM] translate opponent's time by time-odds factor */
16338 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16340 if (time <= 0) time = 1;
16341 if (otime <= 0) otime = 1;
16343 snprintf(message, MSG_SIZ, "time %ld\n", time);
16344 SendToProgram(message, cps);
16346 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16347 SendToProgram(message, cps);
16351 EngineDefinedVariant (ChessProgramState *cps, int n)
16352 { // return name of n-th unknown variant that engine supports
16353 static char buf[MSG_SIZ];
16354 char *p, *s = cps->variants;
16355 if(!s) return NULL;
16356 do { // parse string from variants feature
16358 p = strchr(s, ',');
16359 if(p) *p = NULLCHAR;
16360 v = StringToVariant(s);
16361 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16362 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16363 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16366 if(n < 0) return buf;
16372 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16375 int len = strlen(name);
16378 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16380 sscanf(*p, "%d", &val);
16382 while (**p && **p != ' ')
16384 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16385 SendToProgram(buf, cps);
16392 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16395 int len = strlen(name);
16396 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16398 sscanf(*p, "%d", loc);
16399 while (**p && **p != ' ') (*p)++;
16400 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16401 SendToProgram(buf, cps);
16408 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16411 int len = strlen(name);
16412 if (strncmp((*p), name, len) == 0
16413 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16415 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16416 sscanf(*p, "%[^\"]", *loc);
16417 while (**p && **p != '\"') (*p)++;
16418 if (**p == '\"') (*p)++;
16419 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16420 SendToProgram(buf, cps);
16427 ParseOption (Option *opt, ChessProgramState *cps)
16428 // [HGM] options: process the string that defines an engine option, and determine
16429 // name, type, default value, and allowed value range
16431 char *p, *q, buf[MSG_SIZ];
16432 int n, min = (-1)<<31, max = 1<<31, def;
16434 if(p = strstr(opt->name, " -spin ")) {
16435 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16436 if(max < min) max = min; // enforce consistency
16437 if(def < min) def = min;
16438 if(def > max) def = max;
16443 } else if((p = strstr(opt->name, " -slider "))) {
16444 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16445 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16446 if(max < min) max = min; // enforce consistency
16447 if(def < min) def = min;
16448 if(def > max) def = max;
16452 opt->type = Spin; // Slider;
16453 } else if((p = strstr(opt->name, " -string "))) {
16454 opt->textValue = p+9;
16455 opt->type = TextBox;
16456 } else if((p = strstr(opt->name, " -file "))) {
16457 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16458 opt->textValue = p+7;
16459 opt->type = FileName; // FileName;
16460 } else if((p = strstr(opt->name, " -path "))) {
16461 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16462 opt->textValue = p+7;
16463 opt->type = PathName; // PathName;
16464 } else if(p = strstr(opt->name, " -check ")) {
16465 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16466 opt->value = (def != 0);
16467 opt->type = CheckBox;
16468 } else if(p = strstr(opt->name, " -combo ")) {
16469 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16470 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16471 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16472 opt->value = n = 0;
16473 while(q = StrStr(q, " /// ")) {
16474 n++; *q = 0; // count choices, and null-terminate each of them
16476 if(*q == '*') { // remember default, which is marked with * prefix
16480 cps->comboList[cps->comboCnt++] = q;
16482 cps->comboList[cps->comboCnt++] = NULL;
16484 opt->type = ComboBox;
16485 } else if(p = strstr(opt->name, " -button")) {
16486 opt->type = Button;
16487 } else if(p = strstr(opt->name, " -save")) {
16488 opt->type = SaveButton;
16489 } else return FALSE;
16490 *p = 0; // terminate option name
16491 // now look if the command-line options define a setting for this engine option.
16492 if(cps->optionSettings && cps->optionSettings[0])
16493 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16494 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16495 snprintf(buf, MSG_SIZ, "option %s", p);
16496 if(p = strstr(buf, ",")) *p = 0;
16497 if(q = strchr(buf, '=')) switch(opt->type) {
16499 for(n=0; n<opt->max; n++)
16500 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16503 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16507 opt->value = atoi(q+1);
16512 SendToProgram(buf, cps);
16518 FeatureDone (ChessProgramState *cps, int val)
16520 DelayedEventCallback cb = GetDelayedEvent();
16521 if ((cb == InitBackEnd3 && cps == &first) ||
16522 (cb == SettingsMenuIfReady && cps == &second) ||
16523 (cb == LoadEngine) ||
16524 (cb == TwoMachinesEventIfReady)) {
16525 CancelDelayedEvent();
16526 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16528 cps->initDone = val;
16529 if(val) cps->reload = FALSE;
16532 /* Parse feature command from engine */
16534 ParseFeatures (char *args, ChessProgramState *cps)
16542 while (*p == ' ') p++;
16543 if (*p == NULLCHAR) return;
16545 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16546 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16547 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16548 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16549 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16550 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16551 if (BoolFeature(&p, "reuse", &val, cps)) {
16552 /* Engine can disable reuse, but can't enable it if user said no */
16553 if (!val) cps->reuse = FALSE;
16556 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16557 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16558 if (gameMode == TwoMachinesPlay) {
16559 DisplayTwoMachinesTitle();
16565 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16566 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16567 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16568 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16569 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16570 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16571 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16572 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16573 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16574 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16575 if (IntFeature(&p, "done", &val, cps)) {
16576 FeatureDone(cps, val);
16579 /* Added by Tord: */
16580 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16581 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16582 /* End of additions by Tord */
16584 /* [HGM] added features: */
16585 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16586 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16587 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16588 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16589 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16590 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16591 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16592 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16593 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16594 FREE(cps->option[cps->nrOptions].name);
16595 cps->option[cps->nrOptions].name = q; q = NULL;
16596 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16597 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16598 SendToProgram(buf, cps);
16601 if(cps->nrOptions >= MAX_OPTIONS) {
16603 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16604 DisplayError(buf, 0);
16608 /* End of additions by HGM */
16610 /* unknown feature: complain and skip */
16612 while (*q && *q != '=') q++;
16613 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16614 SendToProgram(buf, cps);
16620 while (*p && *p != '\"') p++;
16621 if (*p == '\"') p++;
16623 while (*p && *p != ' ') p++;
16631 PeriodicUpdatesEvent (int newState)
16633 if (newState == appData.periodicUpdates)
16636 appData.periodicUpdates=newState;
16638 /* Display type changes, so update it now */
16639 // DisplayAnalysis();
16641 /* Get the ball rolling again... */
16643 AnalysisPeriodicEvent(1);
16644 StartAnalysisClock();
16649 PonderNextMoveEvent (int newState)
16651 if (newState == appData.ponderNextMove) return;
16652 if (gameMode == EditPosition) EditPositionDone(TRUE);
16654 SendToProgram("hard\n", &first);
16655 if (gameMode == TwoMachinesPlay) {
16656 SendToProgram("hard\n", &second);
16659 SendToProgram("easy\n", &first);
16660 thinkOutput[0] = NULLCHAR;
16661 if (gameMode == TwoMachinesPlay) {
16662 SendToProgram("easy\n", &second);
16665 appData.ponderNextMove = newState;
16669 NewSettingEvent (int option, int *feature, char *command, int value)
16673 if (gameMode == EditPosition) EditPositionDone(TRUE);
16674 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16675 if(feature == NULL || *feature) SendToProgram(buf, &first);
16676 if (gameMode == TwoMachinesPlay) {
16677 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16682 ShowThinkingEvent ()
16683 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16685 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16686 int newState = appData.showThinking
16687 // [HGM] thinking: other features now need thinking output as well
16688 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16690 if (oldState == newState) return;
16691 oldState = newState;
16692 if (gameMode == EditPosition) EditPositionDone(TRUE);
16694 SendToProgram("post\n", &first);
16695 if (gameMode == TwoMachinesPlay) {
16696 SendToProgram("post\n", &second);
16699 SendToProgram("nopost\n", &first);
16700 thinkOutput[0] = NULLCHAR;
16701 if (gameMode == TwoMachinesPlay) {
16702 SendToProgram("nopost\n", &second);
16705 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16709 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16711 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16712 if (pr == NoProc) return;
16713 AskQuestion(title, question, replyPrefix, pr);
16717 TypeInEvent (char firstChar)
16719 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16720 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16721 gameMode == AnalyzeMode || gameMode == EditGame ||
16722 gameMode == EditPosition || gameMode == IcsExamining ||
16723 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16724 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16725 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16726 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16727 gameMode == Training) PopUpMoveDialog(firstChar);
16731 TypeInDoneEvent (char *move)
16734 int n, fromX, fromY, toX, toY;
16736 ChessMove moveType;
16739 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16740 EditPositionPasteFEN(move);
16743 // [HGM] movenum: allow move number to be typed in any mode
16744 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16748 // undocumented kludge: allow command-line option to be typed in!
16749 // (potentially fatal, and does not implement the effect of the option.)
16750 // should only be used for options that are values on which future decisions will be made,
16751 // and definitely not on options that would be used during initialization.
16752 if(strstr(move, "!!! -") == move) {
16753 ParseArgsFromString(move+4);
16757 if (gameMode != EditGame && currentMove != forwardMostMove &&
16758 gameMode != Training) {
16759 DisplayMoveError(_("Displayed move is not current"));
16761 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16762 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16763 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16764 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16765 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16766 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16768 DisplayMoveError(_("Could not parse move"));
16774 DisplayMove (int moveNumber)
16776 char message[MSG_SIZ];
16778 char cpThinkOutput[MSG_SIZ];
16780 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16782 if (moveNumber == forwardMostMove - 1 ||
16783 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16785 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16787 if (strchr(cpThinkOutput, '\n')) {
16788 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16791 *cpThinkOutput = NULLCHAR;
16794 /* [AS] Hide thinking from human user */
16795 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16796 *cpThinkOutput = NULLCHAR;
16797 if( thinkOutput[0] != NULLCHAR ) {
16800 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16801 cpThinkOutput[i] = '.';
16803 cpThinkOutput[i] = NULLCHAR;
16804 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16808 if (moveNumber == forwardMostMove - 1 &&
16809 gameInfo.resultDetails != NULL) {
16810 if (gameInfo.resultDetails[0] == NULLCHAR) {
16811 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16813 snprintf(res, MSG_SIZ, " {%s} %s",
16814 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16820 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16821 DisplayMessage(res, cpThinkOutput);
16823 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16824 WhiteOnMove(moveNumber) ? " " : ".. ",
16825 parseList[moveNumber], res);
16826 DisplayMessage(message, cpThinkOutput);
16831 DisplayComment (int moveNumber, char *text)
16833 char title[MSG_SIZ];
16835 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16836 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16838 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16839 WhiteOnMove(moveNumber) ? " " : ".. ",
16840 parseList[moveNumber]);
16842 if (text != NULL && (appData.autoDisplayComment || commentUp))
16843 CommentPopUp(title, text);
16846 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16847 * might be busy thinking or pondering. It can be omitted if your
16848 * gnuchess is configured to stop thinking immediately on any user
16849 * input. However, that gnuchess feature depends on the FIONREAD
16850 * ioctl, which does not work properly on some flavors of Unix.
16853 Attention (ChessProgramState *cps)
16856 if (!cps->useSigint) return;
16857 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16858 switch (gameMode) {
16859 case MachinePlaysWhite:
16860 case MachinePlaysBlack:
16861 case TwoMachinesPlay:
16862 case IcsPlayingWhite:
16863 case IcsPlayingBlack:
16866 /* Skip if we know it isn't thinking */
16867 if (!cps->maybeThinking) return;
16868 if (appData.debugMode)
16869 fprintf(debugFP, "Interrupting %s\n", cps->which);
16870 InterruptChildProcess(cps->pr);
16871 cps->maybeThinking = FALSE;
16876 #endif /*ATTENTION*/
16882 if (whiteTimeRemaining <= 0) {
16885 if (appData.icsActive) {
16886 if (appData.autoCallFlag &&
16887 gameMode == IcsPlayingBlack && !blackFlag) {
16888 SendToICS(ics_prefix);
16889 SendToICS("flag\n");
16893 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16895 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16896 if (appData.autoCallFlag) {
16897 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16904 if (blackTimeRemaining <= 0) {
16907 if (appData.icsActive) {
16908 if (appData.autoCallFlag &&
16909 gameMode == IcsPlayingWhite && !whiteFlag) {
16910 SendToICS(ics_prefix);
16911 SendToICS("flag\n");
16915 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16917 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16918 if (appData.autoCallFlag) {
16919 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16930 CheckTimeControl ()
16932 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16933 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16936 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16938 if ( !WhiteOnMove(forwardMostMove) ) {
16939 /* White made time control */
16940 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16941 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16942 /* [HGM] time odds: correct new time quota for time odds! */
16943 / WhitePlayer()->timeOdds;
16944 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16946 lastBlack -= blackTimeRemaining;
16947 /* Black made time control */
16948 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16949 / WhitePlayer()->other->timeOdds;
16950 lastWhite = whiteTimeRemaining;
16955 DisplayBothClocks ()
16957 int wom = gameMode == EditPosition ?
16958 !blackPlaysFirst : WhiteOnMove(currentMove);
16959 DisplayWhiteClock(whiteTimeRemaining, wom);
16960 DisplayBlackClock(blackTimeRemaining, !wom);
16964 /* Timekeeping seems to be a portability nightmare. I think everyone
16965 has ftime(), but I'm really not sure, so I'm including some ifdefs
16966 to use other calls if you don't. Clocks will be less accurate if
16967 you have neither ftime nor gettimeofday.
16970 /* VS 2008 requires the #include outside of the function */
16971 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16972 #include <sys/timeb.h>
16975 /* Get the current time as a TimeMark */
16977 GetTimeMark (TimeMark *tm)
16979 #if HAVE_GETTIMEOFDAY
16981 struct timeval timeVal;
16982 struct timezone timeZone;
16984 gettimeofday(&timeVal, &timeZone);
16985 tm->sec = (long) timeVal.tv_sec;
16986 tm->ms = (int) (timeVal.tv_usec / 1000L);
16988 #else /*!HAVE_GETTIMEOFDAY*/
16991 // include <sys/timeb.h> / moved to just above start of function
16992 struct timeb timeB;
16995 tm->sec = (long) timeB.time;
16996 tm->ms = (int) timeB.millitm;
16998 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16999 tm->sec = (long) time(NULL);
17005 /* Return the difference in milliseconds between two
17006 time marks. We assume the difference will fit in a long!
17009 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17011 return 1000L*(tm2->sec - tm1->sec) +
17012 (long) (tm2->ms - tm1->ms);
17017 * Code to manage the game clocks.
17019 * In tournament play, black starts the clock and then white makes a move.
17020 * We give the human user a slight advantage if he is playing white---the
17021 * clocks don't run until he makes his first move, so it takes zero time.
17022 * Also, we don't account for network lag, so we could get out of sync
17023 * with GNU Chess's clock -- but then, referees are always right.
17026 static TimeMark tickStartTM;
17027 static long intendedTickLength;
17030 NextTickLength (long timeRemaining)
17032 long nominalTickLength, nextTickLength;
17034 if (timeRemaining > 0L && timeRemaining <= 10000L)
17035 nominalTickLength = 100L;
17037 nominalTickLength = 1000L;
17038 nextTickLength = timeRemaining % nominalTickLength;
17039 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17041 return nextTickLength;
17044 /* Adjust clock one minute up or down */
17046 AdjustClock (Boolean which, int dir)
17048 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17049 if(which) blackTimeRemaining += 60000*dir;
17050 else whiteTimeRemaining += 60000*dir;
17051 DisplayBothClocks();
17052 adjustedClock = TRUE;
17055 /* Stop clocks and reset to a fresh time control */
17059 (void) StopClockTimer();
17060 if (appData.icsActive) {
17061 whiteTimeRemaining = blackTimeRemaining = 0;
17062 } else if (searchTime) {
17063 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17064 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17065 } else { /* [HGM] correct new time quote for time odds */
17066 whiteTC = blackTC = fullTimeControlString;
17067 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17068 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17070 if (whiteFlag || blackFlag) {
17072 whiteFlag = blackFlag = FALSE;
17074 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17075 DisplayBothClocks();
17076 adjustedClock = FALSE;
17079 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17081 /* Decrement running clock by amount of time that has passed */
17085 long timeRemaining;
17086 long lastTickLength, fudge;
17089 if (!appData.clockMode) return;
17090 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17094 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17096 /* Fudge if we woke up a little too soon */
17097 fudge = intendedTickLength - lastTickLength;
17098 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17100 if (WhiteOnMove(forwardMostMove)) {
17101 if(whiteNPS >= 0) lastTickLength = 0;
17102 timeRemaining = whiteTimeRemaining -= lastTickLength;
17103 if(timeRemaining < 0 && !appData.icsActive) {
17104 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17105 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17106 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17107 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17110 DisplayWhiteClock(whiteTimeRemaining - fudge,
17111 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17113 if(blackNPS >= 0) lastTickLength = 0;
17114 timeRemaining = blackTimeRemaining -= lastTickLength;
17115 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17116 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17118 blackStartMove = forwardMostMove;
17119 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17122 DisplayBlackClock(blackTimeRemaining - fudge,
17123 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17125 if (CheckFlags()) return;
17127 if(twoBoards) { // count down secondary board's clocks as well
17128 activePartnerTime -= lastTickLength;
17130 if(activePartner == 'W')
17131 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17133 DisplayBlackClock(activePartnerTime, TRUE);
17138 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17139 StartClockTimer(intendedTickLength);
17141 /* if the time remaining has fallen below the alarm threshold, sound the
17142 * alarm. if the alarm has sounded and (due to a takeback or time control
17143 * with increment) the time remaining has increased to a level above the
17144 * threshold, reset the alarm so it can sound again.
17147 if (appData.icsActive && appData.icsAlarm) {
17149 /* make sure we are dealing with the user's clock */
17150 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17151 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17154 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17155 alarmSounded = FALSE;
17156 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17158 alarmSounded = TRUE;
17164 /* A player has just moved, so stop the previously running
17165 clock and (if in clock mode) start the other one.
17166 We redisplay both clocks in case we're in ICS mode, because
17167 ICS gives us an update to both clocks after every move.
17168 Note that this routine is called *after* forwardMostMove
17169 is updated, so the last fractional tick must be subtracted
17170 from the color that is *not* on move now.
17173 SwitchClocks (int newMoveNr)
17175 long lastTickLength;
17177 int flagged = FALSE;
17181 if (StopClockTimer() && appData.clockMode) {
17182 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17183 if (!WhiteOnMove(forwardMostMove)) {
17184 if(blackNPS >= 0) lastTickLength = 0;
17185 blackTimeRemaining -= lastTickLength;
17186 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17187 // if(pvInfoList[forwardMostMove].time == -1)
17188 pvInfoList[forwardMostMove].time = // use GUI time
17189 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17191 if(whiteNPS >= 0) lastTickLength = 0;
17192 whiteTimeRemaining -= lastTickLength;
17193 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17194 // if(pvInfoList[forwardMostMove].time == -1)
17195 pvInfoList[forwardMostMove].time =
17196 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17198 flagged = CheckFlags();
17200 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17201 CheckTimeControl();
17203 if (flagged || !appData.clockMode) return;
17205 switch (gameMode) {
17206 case MachinePlaysBlack:
17207 case MachinePlaysWhite:
17208 case BeginningOfGame:
17209 if (pausing) return;
17213 case PlayFromGameFile:
17221 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17222 if(WhiteOnMove(forwardMostMove))
17223 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17224 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17228 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17229 whiteTimeRemaining : blackTimeRemaining);
17230 StartClockTimer(intendedTickLength);
17234 /* Stop both clocks */
17238 long lastTickLength;
17241 if (!StopClockTimer()) return;
17242 if (!appData.clockMode) return;
17246 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17247 if (WhiteOnMove(forwardMostMove)) {
17248 if(whiteNPS >= 0) lastTickLength = 0;
17249 whiteTimeRemaining -= lastTickLength;
17250 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17252 if(blackNPS >= 0) lastTickLength = 0;
17253 blackTimeRemaining -= lastTickLength;
17254 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17259 /* Start clock of player on move. Time may have been reset, so
17260 if clock is already running, stop and restart it. */
17264 (void) StopClockTimer(); /* in case it was running already */
17265 DisplayBothClocks();
17266 if (CheckFlags()) return;
17268 if (!appData.clockMode) return;
17269 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17271 GetTimeMark(&tickStartTM);
17272 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17273 whiteTimeRemaining : blackTimeRemaining);
17275 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17276 whiteNPS = blackNPS = -1;
17277 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17278 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17279 whiteNPS = first.nps;
17280 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17281 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17282 blackNPS = first.nps;
17283 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17284 whiteNPS = second.nps;
17285 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17286 blackNPS = second.nps;
17287 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17289 StartClockTimer(intendedTickLength);
17293 TimeString (long ms)
17295 long second, minute, hour, day;
17297 static char buf[32];
17299 if (ms > 0 && ms <= 9900) {
17300 /* convert milliseconds to tenths, rounding up */
17301 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17303 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17307 /* convert milliseconds to seconds, rounding up */
17308 /* use floating point to avoid strangeness of integer division
17309 with negative dividends on many machines */
17310 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17317 day = second / (60 * 60 * 24);
17318 second = second % (60 * 60 * 24);
17319 hour = second / (60 * 60);
17320 second = second % (60 * 60);
17321 minute = second / 60;
17322 second = second % 60;
17325 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17326 sign, day, hour, minute, second);
17328 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17330 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17337 * This is necessary because some C libraries aren't ANSI C compliant yet.
17340 StrStr (char *string, char *match)
17344 length = strlen(match);
17346 for (i = strlen(string) - length; i >= 0; i--, string++)
17347 if (!strncmp(match, string, length))
17354 StrCaseStr (char *string, char *match)
17358 length = strlen(match);
17360 for (i = strlen(string) - length; i >= 0; i--, string++) {
17361 for (j = 0; j < length; j++) {
17362 if (ToLower(match[j]) != ToLower(string[j]))
17365 if (j == length) return string;
17373 StrCaseCmp (char *s1, char *s2)
17378 c1 = ToLower(*s1++);
17379 c2 = ToLower(*s2++);
17380 if (c1 > c2) return 1;
17381 if (c1 < c2) return -1;
17382 if (c1 == NULLCHAR) return 0;
17390 return isupper(c) ? tolower(c) : c;
17397 return islower(c) ? toupper(c) : c;
17399 #endif /* !_amigados */
17406 if ((ret = (char *) malloc(strlen(s) + 1)))
17408 safeStrCpy(ret, s, strlen(s)+1);
17414 StrSavePtr (char *s, char **savePtr)
17419 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17420 safeStrCpy(*savePtr, s, strlen(s)+1);
17432 clock = time((time_t *)NULL);
17433 tm = localtime(&clock);
17434 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17435 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17436 return StrSave(buf);
17441 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17443 int i, j, fromX, fromY, toX, toY;
17450 whiteToPlay = (gameMode == EditPosition) ?
17451 !blackPlaysFirst : (move % 2 == 0);
17454 /* Piece placement data */
17455 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17456 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17458 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17459 if (boards[move][i][j] == EmptySquare) {
17461 } else { ChessSquare piece = boards[move][i][j];
17462 if (emptycount > 0) {
17463 if(emptycount<10) /* [HGM] can be >= 10 */
17464 *p++ = '0' + emptycount;
17465 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17468 if(PieceToChar(piece) == '+') {
17469 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17471 piece = (ChessSquare)(DEMOTED piece);
17473 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17475 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17476 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17481 if (emptycount > 0) {
17482 if(emptycount<10) /* [HGM] can be >= 10 */
17483 *p++ = '0' + emptycount;
17484 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17491 /* [HGM] print Crazyhouse or Shogi holdings */
17492 if( gameInfo.holdingsWidth ) {
17493 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17495 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17496 piece = boards[move][i][BOARD_WIDTH-1];
17497 if( piece != EmptySquare )
17498 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17499 *p++ = PieceToChar(piece);
17501 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17502 piece = boards[move][BOARD_HEIGHT-i-1][0];
17503 if( piece != EmptySquare )
17504 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17505 *p++ = PieceToChar(piece);
17508 if( q == p ) *p++ = '-';
17514 *p++ = whiteToPlay ? 'w' : 'b';
17517 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17518 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17520 if(nrCastlingRights) {
17522 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17523 /* [HGM] write directly from rights */
17524 if(boards[move][CASTLING][2] != NoRights &&
17525 boards[move][CASTLING][0] != NoRights )
17526 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17527 if(boards[move][CASTLING][2] != NoRights &&
17528 boards[move][CASTLING][1] != NoRights )
17529 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17530 if(boards[move][CASTLING][5] != NoRights &&
17531 boards[move][CASTLING][3] != NoRights )
17532 *p++ = boards[move][CASTLING][3] + AAA;
17533 if(boards[move][CASTLING][5] != NoRights &&
17534 boards[move][CASTLING][4] != NoRights )
17535 *p++ = boards[move][CASTLING][4] + AAA;
17538 /* [HGM] write true castling rights */
17539 if( nrCastlingRights == 6 ) {
17541 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17542 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17543 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17544 boards[move][CASTLING][2] != NoRights );
17545 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17546 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17547 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17548 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17549 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17553 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17554 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17555 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17556 boards[move][CASTLING][5] != NoRights );
17557 if(gameInfo.variant == VariantSChess) {
17558 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17559 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17560 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17561 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17566 if (q == p) *p++ = '-'; /* No castling rights */
17570 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17571 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17572 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17573 /* En passant target square */
17574 if (move > backwardMostMove) {
17575 fromX = moveList[move - 1][0] - AAA;
17576 fromY = moveList[move - 1][1] - ONE;
17577 toX = moveList[move - 1][2] - AAA;
17578 toY = moveList[move - 1][3] - ONE;
17579 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17580 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17581 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17583 /* 2-square pawn move just happened */
17585 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17589 } else if(move == backwardMostMove) {
17590 // [HGM] perhaps we should always do it like this, and forget the above?
17591 if((signed char)boards[move][EP_STATUS] >= 0) {
17592 *p++ = boards[move][EP_STATUS] + AAA;
17593 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17605 { int i = 0, j=move;
17607 /* [HGM] find reversible plies */
17608 if (appData.debugMode) { int k;
17609 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17610 for(k=backwardMostMove; k<=forwardMostMove; k++)
17611 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17615 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17616 if( j == backwardMostMove ) i += initialRulePlies;
17617 sprintf(p, "%d ", i);
17618 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17620 /* Fullmove number */
17621 sprintf(p, "%d", (move / 2) + 1);
17622 } else *--p = NULLCHAR;
17624 return StrSave(buf);
17628 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17632 int emptycount, virgin[BOARD_FILES];
17637 /* Piece placement data */
17638 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17641 if (*p == '/' || *p == ' ' || *p == '[' ) {
17643 emptycount = gameInfo.boardWidth - j;
17644 while (emptycount--)
17645 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17646 if (*p == '/') p++;
17647 else if(autoSize) { // we stumbled unexpectedly into end of board
17648 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17649 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17651 appData.NrRanks = gameInfo.boardHeight - i; i=0;
17654 #if(BOARD_FILES >= 10)
17655 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17656 p++; emptycount=10;
17657 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17658 while (emptycount--)
17659 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17661 } else if (*p == '*') {
17662 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17663 } else if (isdigit(*p)) {
17664 emptycount = *p++ - '0';
17665 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17666 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17667 while (emptycount--)
17668 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17669 } else if (*p == '+' || isalpha(*p)) {
17670 if (j >= gameInfo.boardWidth) return FALSE;
17672 piece = CharToPiece(*++p);
17673 if(piece == EmptySquare) return FALSE; /* unknown piece */
17674 piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17675 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17676 } else piece = CharToPiece(*p++);
17678 if(piece==EmptySquare) return FALSE; /* unknown piece */
17679 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17680 piece = (ChessSquare) (PROMOTED piece);
17681 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17684 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17690 while (*p == '/' || *p == ' ') p++;
17692 if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17694 /* [HGM] by default clear Crazyhouse holdings, if present */
17695 if(gameInfo.holdingsWidth) {
17696 for(i=0; i<BOARD_HEIGHT; i++) {
17697 board[i][0] = EmptySquare; /* black holdings */
17698 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17699 board[i][1] = (ChessSquare) 0; /* black counts */
17700 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17704 /* [HGM] look for Crazyhouse holdings here */
17705 while(*p==' ') p++;
17706 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17708 if(*p == '-' ) p++; /* empty holdings */ else {
17709 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17710 /* if we would allow FEN reading to set board size, we would */
17711 /* have to add holdings and shift the board read so far here */
17712 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17714 if((int) piece >= (int) BlackPawn ) {
17715 i = (int)piece - (int)BlackPawn;
17716 i = PieceToNumber((ChessSquare)i);
17717 if( i >= gameInfo.holdingsSize ) return FALSE;
17718 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17719 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17721 i = (int)piece - (int)WhitePawn;
17722 i = PieceToNumber((ChessSquare)i);
17723 if( i >= gameInfo.holdingsSize ) return FALSE;
17724 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17725 board[i][BOARD_WIDTH-2]++; /* black holdings */
17732 while(*p == ' ') p++;
17736 if(appData.colorNickNames) {
17737 if( c == appData.colorNickNames[0] ) c = 'w'; else
17738 if( c == appData.colorNickNames[1] ) c = 'b';
17742 *blackPlaysFirst = FALSE;
17745 *blackPlaysFirst = TRUE;
17751 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17752 /* return the extra info in global variiables */
17754 /* set defaults in case FEN is incomplete */
17755 board[EP_STATUS] = EP_UNKNOWN;
17756 for(i=0; i<nrCastlingRights; i++ ) {
17757 board[CASTLING][i] =
17758 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17759 } /* assume possible unless obviously impossible */
17760 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17761 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17762 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17763 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17764 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17765 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17766 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17767 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17770 while(*p==' ') p++;
17771 if(nrCastlingRights) {
17772 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17773 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17774 /* castling indicator present, so default becomes no castlings */
17775 for(i=0; i<nrCastlingRights; i++ ) {
17776 board[CASTLING][i] = NoRights;
17779 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17780 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17781 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17782 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17783 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17785 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17786 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17787 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17789 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17790 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17791 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17792 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17793 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17794 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17797 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17798 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17799 board[CASTLING][2] = whiteKingFile;
17800 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17801 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17804 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17805 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17806 board[CASTLING][2] = whiteKingFile;
17807 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17808 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17811 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17812 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17813 board[CASTLING][5] = blackKingFile;
17814 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17815 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17818 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17819 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17820 board[CASTLING][5] = blackKingFile;
17821 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17822 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17825 default: /* FRC castlings */
17826 if(c >= 'a') { /* black rights */
17827 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17828 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17829 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17830 if(i == BOARD_RGHT) break;
17831 board[CASTLING][5] = i;
17833 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17834 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17836 board[CASTLING][3] = c;
17838 board[CASTLING][4] = c;
17839 } else { /* white rights */
17840 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17841 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17842 if(board[0][i] == WhiteKing) break;
17843 if(i == BOARD_RGHT) break;
17844 board[CASTLING][2] = i;
17845 c -= AAA - 'a' + 'A';
17846 if(board[0][c] >= WhiteKing) break;
17848 board[CASTLING][0] = c;
17850 board[CASTLING][1] = c;
17854 for(i=0; i<nrCastlingRights; i++)
17855 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17856 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17857 if (appData.debugMode) {
17858 fprintf(debugFP, "FEN castling rights:");
17859 for(i=0; i<nrCastlingRights; i++)
17860 fprintf(debugFP, " %d", board[CASTLING][i]);
17861 fprintf(debugFP, "\n");
17864 while(*p==' ') p++;
17867 /* read e.p. field in games that know e.p. capture */
17868 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17869 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17870 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17872 p++; board[EP_STATUS] = EP_NONE;
17874 char c = *p++ - AAA;
17876 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17877 if(*p >= '0' && *p <='9') p++;
17878 board[EP_STATUS] = c;
17883 if(sscanf(p, "%d", &i) == 1) {
17884 FENrulePlies = i; /* 50-move ply counter */
17885 /* (The move number is still ignored) */
17892 EditPositionPasteFEN (char *fen)
17895 Board initial_position;
17897 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17898 DisplayError(_("Bad FEN position in clipboard"), 0);
17901 int savedBlackPlaysFirst = blackPlaysFirst;
17902 EditPositionEvent();
17903 blackPlaysFirst = savedBlackPlaysFirst;
17904 CopyBoard(boards[0], initial_position);
17905 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17906 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17907 DisplayBothClocks();
17908 DrawPosition(FALSE, boards[currentMove]);
17913 static char cseq[12] = "\\ ";
17916 set_cont_sequence (char *new_seq)
17921 // handle bad attempts to set the sequence
17923 return 0; // acceptable error - no debug
17925 len = strlen(new_seq);
17926 ret = (len > 0) && (len < sizeof(cseq));
17928 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17929 else if (appData.debugMode)
17930 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17935 reformat a source message so words don't cross the width boundary. internal
17936 newlines are not removed. returns the wrapped size (no null character unless
17937 included in source message). If dest is NULL, only calculate the size required
17938 for the dest buffer. lp argument indicats line position upon entry, and it's
17939 passed back upon exit.
17942 wrap (char *dest, char *src, int count, int width, int *lp)
17944 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17946 cseq_len = strlen(cseq);
17947 old_line = line = *lp;
17948 ansi = len = clen = 0;
17950 for (i=0; i < count; i++)
17952 if (src[i] == '\033')
17955 // if we hit the width, back up
17956 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17958 // store i & len in case the word is too long
17959 old_i = i, old_len = len;
17961 // find the end of the last word
17962 while (i && src[i] != ' ' && src[i] != '\n')
17968 // word too long? restore i & len before splitting it
17969 if ((old_i-i+clen) >= width)
17976 if (i && src[i-1] == ' ')
17979 if (src[i] != ' ' && src[i] != '\n')
17986 // now append the newline and continuation sequence
17991 strncpy(dest+len, cseq, cseq_len);
17999 dest[len] = src[i];
18003 if (src[i] == '\n')
18008 if (dest && appData.debugMode)
18010 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18011 count, width, line, len, *lp);
18012 show_bytes(debugFP, src, count);
18013 fprintf(debugFP, "\ndest: ");
18014 show_bytes(debugFP, dest, len);
18015 fprintf(debugFP, "\n");
18017 *lp = dest ? line : old_line;
18022 // [HGM] vari: routines for shelving variations
18023 Boolean modeRestore = FALSE;
18026 PushInner (int firstMove, int lastMove)
18028 int i, j, nrMoves = lastMove - firstMove;
18030 // push current tail of game on stack
18031 savedResult[storedGames] = gameInfo.result;
18032 savedDetails[storedGames] = gameInfo.resultDetails;
18033 gameInfo.resultDetails = NULL;
18034 savedFirst[storedGames] = firstMove;
18035 savedLast [storedGames] = lastMove;
18036 savedFramePtr[storedGames] = framePtr;
18037 framePtr -= nrMoves; // reserve space for the boards
18038 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18039 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18040 for(j=0; j<MOVE_LEN; j++)
18041 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18042 for(j=0; j<2*MOVE_LEN; j++)
18043 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18044 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18045 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18046 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18047 pvInfoList[firstMove+i-1].depth = 0;
18048 commentList[framePtr+i] = commentList[firstMove+i];
18049 commentList[firstMove+i] = NULL;
18053 forwardMostMove = firstMove; // truncate game so we can start variation
18057 PushTail (int firstMove, int lastMove)
18059 if(appData.icsActive) { // only in local mode
18060 forwardMostMove = currentMove; // mimic old ICS behavior
18063 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18065 PushInner(firstMove, lastMove);
18066 if(storedGames == 1) GreyRevert(FALSE);
18067 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18071 PopInner (Boolean annotate)
18074 char buf[8000], moveBuf[20];
18076 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18077 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18078 nrMoves = savedLast[storedGames] - currentMove;
18081 if(!WhiteOnMove(currentMove))
18082 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18083 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18084 for(i=currentMove; i<forwardMostMove; i++) {
18086 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18087 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18088 strcat(buf, moveBuf);
18089 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18090 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18094 for(i=1; i<=nrMoves; i++) { // copy last variation back
18095 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18096 for(j=0; j<MOVE_LEN; j++)
18097 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18098 for(j=0; j<2*MOVE_LEN; j++)
18099 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18100 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18101 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18102 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18103 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18104 commentList[currentMove+i] = commentList[framePtr+i];
18105 commentList[framePtr+i] = NULL;
18107 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18108 framePtr = savedFramePtr[storedGames];
18109 gameInfo.result = savedResult[storedGames];
18110 if(gameInfo.resultDetails != NULL) {
18111 free(gameInfo.resultDetails);
18113 gameInfo.resultDetails = savedDetails[storedGames];
18114 forwardMostMove = currentMove + nrMoves;
18118 PopTail (Boolean annotate)
18120 if(appData.icsActive) return FALSE; // only in local mode
18121 if(!storedGames) return FALSE; // sanity
18122 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18124 PopInner(annotate);
18125 if(currentMove < forwardMostMove) ForwardEvent(); else
18126 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18128 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18134 { // remove all shelved variations
18136 for(i=0; i<storedGames; i++) {
18137 if(savedDetails[i])
18138 free(savedDetails[i]);
18139 savedDetails[i] = NULL;
18141 for(i=framePtr; i<MAX_MOVES; i++) {
18142 if(commentList[i]) free(commentList[i]);
18143 commentList[i] = NULL;
18145 framePtr = MAX_MOVES-1;
18150 LoadVariation (int index, char *text)
18151 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18152 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18153 int level = 0, move;
18155 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18156 // first find outermost bracketing variation
18157 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18158 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18159 if(*p == '{') wait = '}'; else
18160 if(*p == '[') wait = ']'; else
18161 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18162 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18164 if(*p == wait) wait = NULLCHAR; // closing ]} found
18167 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18168 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18169 end[1] = NULLCHAR; // clip off comment beyond variation
18170 ToNrEvent(currentMove-1);
18171 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18172 // kludge: use ParsePV() to append variation to game
18173 move = currentMove;
18174 ParsePV(start, TRUE, TRUE);
18175 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18176 ClearPremoveHighlights();
18178 ToNrEvent(currentMove+1);
18184 char *p, *q, buf[MSG_SIZ];
18185 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18186 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18187 ParseArgsFromString(buf);
18188 ActivateTheme(TRUE); // also redo colors
18192 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18195 q = appData.themeNames;
18196 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18197 if(appData.useBitmaps) {
18198 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18199 appData.liteBackTextureFile, appData.darkBackTextureFile,
18200 appData.liteBackTextureMode,
18201 appData.darkBackTextureMode );
18203 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18204 Col2Text(2), // lightSquareColor
18205 Col2Text(3) ); // darkSquareColor
18207 if(appData.useBorder) {
18208 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18211 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18213 if(appData.useFont) {
18214 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18215 appData.renderPiecesWithFont,
18216 appData.fontToPieceTable,
18217 Col2Text(9), // appData.fontBackColorWhite
18218 Col2Text(10) ); // appData.fontForeColorBlack
18220 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18221 appData.pieceDirectory);
18222 if(!appData.pieceDirectory[0])
18223 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18224 Col2Text(0), // whitePieceColor
18225 Col2Text(1) ); // blackPieceColor
18227 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18228 Col2Text(4), // highlightSquareColor
18229 Col2Text(5) ); // premoveHighlightColor
18230 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18231 if(insert != q) insert[-1] = NULLCHAR;
18232 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18235 ActivateTheme(FALSE);