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 && gameInfo.variant != VariantChuChess) 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 !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5332 appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5333 (promoSweep == WhiteLion || promoSweep == BlackLion)));
5335 int victim = boards[currentMove][toY][toX];
5336 boards[currentMove][toY][toX] = promoSweep;
5337 DrawPosition(FALSE, boards[currentMove]);
5338 boards[currentMove][toY][toX] = victim;
5340 ChangeDragPiece(promoSweep);
5344 PromoScroll (int x, int y)
5348 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5349 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5350 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5351 if(!step) return FALSE;
5352 lastX = x; lastY = y;
5353 if((promoSweep < BlackPawn) == flipView) step = -step;
5354 if(step > 0) selectFlag = 1;
5355 if(!selectFlag) Sweep(step);
5360 NextPiece (int step)
5362 ChessSquare piece = boards[currentMove][toY][toX];
5365 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5366 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5367 if(!step) step = -1;
5368 } while(PieceToChar(pieceSweep) == '.');
5369 boards[currentMove][toY][toX] = pieceSweep;
5370 DrawPosition(FALSE, boards[currentMove]);
5371 boards[currentMove][toY][toX] = piece;
5373 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5375 AlphaRank (char *move, int n)
5377 // char *p = move, c; int x, y;
5379 if (appData.debugMode) {
5380 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5384 move[2]>='0' && move[2]<='9' &&
5385 move[3]>='a' && move[3]<='x' ) {
5387 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5388 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5390 if(move[0]>='0' && move[0]<='9' &&
5391 move[1]>='a' && move[1]<='x' &&
5392 move[2]>='0' && move[2]<='9' &&
5393 move[3]>='a' && move[3]<='x' ) {
5394 /* input move, Shogi -> normal */
5395 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5396 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5397 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5398 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5401 move[3]>='0' && move[3]<='9' &&
5402 move[2]>='a' && move[2]<='x' ) {
5404 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5405 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5408 move[0]>='a' && move[0]<='x' &&
5409 move[3]>='0' && move[3]<='9' &&
5410 move[2]>='a' && move[2]<='x' ) {
5411 /* output move, normal -> Shogi */
5412 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5413 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5414 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5415 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5416 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5418 if (appData.debugMode) {
5419 fprintf(debugFP, " out = '%s'\n", move);
5423 char yy_textstr[8000];
5425 /* Parser for moves from gnuchess, ICS, or user typein box */
5427 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5429 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5431 switch (*moveType) {
5432 case WhitePromotion:
5433 case BlackPromotion:
5434 case WhiteNonPromotion:
5435 case BlackNonPromotion:
5438 case WhiteCapturesEnPassant:
5439 case BlackCapturesEnPassant:
5440 case WhiteKingSideCastle:
5441 case WhiteQueenSideCastle:
5442 case BlackKingSideCastle:
5443 case BlackQueenSideCastle:
5444 case WhiteKingSideCastleWild:
5445 case WhiteQueenSideCastleWild:
5446 case BlackKingSideCastleWild:
5447 case BlackQueenSideCastleWild:
5448 /* Code added by Tord: */
5449 case WhiteHSideCastleFR:
5450 case WhiteASideCastleFR:
5451 case BlackHSideCastleFR:
5452 case BlackASideCastleFR:
5453 /* End of code added by Tord */
5454 case IllegalMove: /* bug or odd chess variant */
5455 *fromX = currentMoveString[0] - AAA;
5456 *fromY = currentMoveString[1] - ONE;
5457 *toX = currentMoveString[2] - AAA;
5458 *toY = currentMoveString[3] - ONE;
5459 *promoChar = currentMoveString[4];
5460 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5461 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5462 if (appData.debugMode) {
5463 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5465 *fromX = *fromY = *toX = *toY = 0;
5468 if (appData.testLegality) {
5469 return (*moveType != IllegalMove);
5471 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5472 // [HGM] lion: if this is a double move we are less critical
5473 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5478 *fromX = *moveType == WhiteDrop ?
5479 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5480 (int) CharToPiece(ToLower(currentMoveString[0]));
5482 *toX = currentMoveString[2] - AAA;
5483 *toY = currentMoveString[3] - ONE;
5484 *promoChar = NULLCHAR;
5488 case ImpossibleMove:
5498 if (appData.debugMode) {
5499 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5502 *fromX = *fromY = *toX = *toY = 0;
5503 *promoChar = NULLCHAR;
5508 Boolean pushed = FALSE;
5509 char *lastParseAttempt;
5512 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5513 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5514 int fromX, fromY, toX, toY; char promoChar;
5519 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5520 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5521 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5524 endPV = forwardMostMove;
5526 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5527 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5528 lastParseAttempt = pv;
5529 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5530 if(!valid && nr == 0 &&
5531 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5532 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5533 // Hande case where played move is different from leading PV move
5534 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5535 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5536 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5537 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5538 endPV += 2; // if position different, keep this
5539 moveList[endPV-1][0] = fromX + AAA;
5540 moveList[endPV-1][1] = fromY + ONE;
5541 moveList[endPV-1][2] = toX + AAA;
5542 moveList[endPV-1][3] = toY + ONE;
5543 parseList[endPV-1][0] = NULLCHAR;
5544 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5547 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5548 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5549 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5550 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5551 valid++; // allow comments in PV
5555 if(endPV+1 > framePtr) break; // no space, truncate
5558 CopyBoard(boards[endPV], boards[endPV-1]);
5559 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5560 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5561 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5562 CoordsToAlgebraic(boards[endPV - 1],
5563 PosFlags(endPV - 1),
5564 fromY, fromX, toY, toX, promoChar,
5565 parseList[endPV - 1]);
5567 if(atEnd == 2) return; // used hidden, for PV conversion
5568 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5569 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5570 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5571 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5572 DrawPosition(TRUE, boards[currentMove]);
5576 MultiPV (ChessProgramState *cps)
5577 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5579 for(i=0; i<cps->nrOptions; i++)
5580 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5585 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5588 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5590 int startPV, multi, lineStart, origIndex = index;
5591 char *p, buf2[MSG_SIZ];
5592 ChessProgramState *cps = (pane ? &second : &first);
5594 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5595 lastX = x; lastY = y;
5596 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5597 lineStart = startPV = index;
5598 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5599 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5601 do{ while(buf[index] && buf[index] != '\n') index++;
5602 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5604 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5605 int n = cps->option[multi].value;
5606 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5607 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5608 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5609 cps->option[multi].value = n;
5612 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5613 ExcludeClick(origIndex - lineStart);
5616 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5617 *start = startPV; *end = index-1;
5618 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5625 static char buf[10*MSG_SIZ];
5626 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5628 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5629 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5630 for(i = forwardMostMove; i<endPV; i++){
5631 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5632 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5635 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5636 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5637 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5643 LoadPV (int x, int y)
5644 { // called on right mouse click to load PV
5645 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5646 lastX = x; lastY = y;
5647 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5655 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5656 if(endPV < 0) return;
5657 if(appData.autoCopyPV) CopyFENToClipboard();
5659 if(extendGame && currentMove > forwardMostMove) {
5660 Boolean saveAnimate = appData.animate;
5662 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5663 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5664 } else storedGames--; // abandon shelved tail of original game
5667 forwardMostMove = currentMove;
5668 currentMove = oldFMM;
5669 appData.animate = FALSE;
5670 ToNrEvent(forwardMostMove);
5671 appData.animate = saveAnimate;
5673 currentMove = forwardMostMove;
5674 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5675 ClearPremoveHighlights();
5676 DrawPosition(TRUE, boards[currentMove]);
5680 MovePV (int x, int y, int h)
5681 { // step through PV based on mouse coordinates (called on mouse move)
5682 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5684 // we must somehow check if right button is still down (might be released off board!)
5685 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5686 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5687 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5689 lastX = x; lastY = y;
5691 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5692 if(endPV < 0) return;
5693 if(y < margin) step = 1; else
5694 if(y > h - margin) step = -1;
5695 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5696 currentMove += step;
5697 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5698 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5699 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5700 DrawPosition(FALSE, boards[currentMove]);
5704 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5705 // All positions will have equal probability, but the current method will not provide a unique
5706 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5712 int piecesLeft[(int)BlackPawn];
5713 int seed, nrOfShuffles;
5716 GetPositionNumber ()
5717 { // sets global variable seed
5720 seed = appData.defaultFrcPosition;
5721 if(seed < 0) { // randomize based on time for negative FRC position numbers
5722 for(i=0; i<50; i++) seed += random();
5723 seed = random() ^ random() >> 8 ^ random() << 8;
5724 if(seed<0) seed = -seed;
5729 put (Board board, int pieceType, int rank, int n, int shade)
5730 // put the piece on the (n-1)-th empty squares of the given shade
5734 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5735 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5736 board[rank][i] = (ChessSquare) pieceType;
5737 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5739 piecesLeft[pieceType]--;
5748 AddOnePiece (Board board, int pieceType, int rank, int shade)
5749 // calculate where the next piece goes, (any empty square), and put it there
5753 i = seed % squaresLeft[shade];
5754 nrOfShuffles *= squaresLeft[shade];
5755 seed /= squaresLeft[shade];
5756 put(board, pieceType, rank, i, shade);
5760 AddTwoPieces (Board board, int pieceType, int rank)
5761 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5763 int i, n=squaresLeft[ANY], j=n-1, k;
5765 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5766 i = seed % k; // pick one
5769 while(i >= j) i -= j--;
5770 j = n - 1 - j; i += j;
5771 put(board, pieceType, rank, j, ANY);
5772 put(board, pieceType, rank, i, ANY);
5776 SetUpShuffle (Board board, int number)
5780 GetPositionNumber(); nrOfShuffles = 1;
5782 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5783 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5784 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5786 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5788 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5789 p = (int) board[0][i];
5790 if(p < (int) BlackPawn) piecesLeft[p] ++;
5791 board[0][i] = EmptySquare;
5794 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5795 // shuffles restricted to allow normal castling put KRR first
5796 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5797 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5798 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5799 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5800 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5801 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5802 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5803 put(board, WhiteRook, 0, 0, ANY);
5804 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5807 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5808 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5809 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5810 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5811 while(piecesLeft[p] >= 2) {
5812 AddOnePiece(board, p, 0, LITE);
5813 AddOnePiece(board, p, 0, DARK);
5815 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5818 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5819 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5820 // but we leave King and Rooks for last, to possibly obey FRC restriction
5821 if(p == (int)WhiteRook) continue;
5822 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5823 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5826 // now everything is placed, except perhaps King (Unicorn) and Rooks
5828 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5829 // Last King gets castling rights
5830 while(piecesLeft[(int)WhiteUnicorn]) {
5831 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5832 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5835 while(piecesLeft[(int)WhiteKing]) {
5836 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5837 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5842 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5843 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5846 // Only Rooks can be left; simply place them all
5847 while(piecesLeft[(int)WhiteRook]) {
5848 i = put(board, WhiteRook, 0, 0, ANY);
5849 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5852 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5854 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5857 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5858 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5861 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5865 SetCharTable (char *table, const char * map)
5866 /* [HGM] moved here from winboard.c because of its general usefulness */
5867 /* Basically a safe strcpy that uses the last character as King */
5869 int result = FALSE; int NrPieces;
5871 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5872 && NrPieces >= 12 && !(NrPieces&1)) {
5873 int i; /* [HGM] Accept even length from 12 to 34 */
5875 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5876 for( i=0; i<NrPieces/2-1; i++ ) {
5878 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5880 table[(int) WhiteKing] = map[NrPieces/2-1];
5881 table[(int) BlackKing] = map[NrPieces-1];
5890 Prelude (Board board)
5891 { // [HGM] superchess: random selection of exo-pieces
5892 int i, j, k; ChessSquare p;
5893 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5895 GetPositionNumber(); // use FRC position number
5897 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5898 SetCharTable(pieceToChar, appData.pieceToCharTable);
5899 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5900 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5903 j = seed%4; seed /= 4;
5904 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5905 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5906 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5907 j = seed%3 + (seed%3 >= j); seed /= 3;
5908 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5909 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5910 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5911 j = seed%3; seed /= 3;
5912 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5913 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5914 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5915 j = seed%2 + (seed%2 >= j); seed /= 2;
5916 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5917 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5918 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5919 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5920 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5921 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5922 put(board, exoPieces[0], 0, 0, ANY);
5923 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5927 InitPosition (int redraw)
5929 ChessSquare (* pieces)[BOARD_FILES];
5930 int i, j, pawnRow=1, pieceRows=1, overrule,
5931 oldx = gameInfo.boardWidth,
5932 oldy = gameInfo.boardHeight,
5933 oldh = gameInfo.holdingsWidth;
5936 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5938 /* [AS] Initialize pv info list [HGM] and game status */
5940 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5941 pvInfoList[i].depth = 0;
5942 boards[i][EP_STATUS] = EP_NONE;
5943 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5946 initialRulePlies = 0; /* 50-move counter start */
5948 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5949 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5953 /* [HGM] logic here is completely changed. In stead of full positions */
5954 /* the initialized data only consist of the two backranks. The switch */
5955 /* selects which one we will use, which is than copied to the Board */
5956 /* initialPosition, which for the rest is initialized by Pawns and */
5957 /* empty squares. This initial position is then copied to boards[0], */
5958 /* possibly after shuffling, so that it remains available. */
5960 gameInfo.holdingsWidth = 0; /* default board sizes */
5961 gameInfo.boardWidth = 8;
5962 gameInfo.boardHeight = 8;
5963 gameInfo.holdingsSize = 0;
5964 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5965 for(i=0; i<BOARD_FILES-2; i++)
5966 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5967 initialPosition[EP_STATUS] = EP_NONE;
5968 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5969 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5970 SetCharTable(pieceNickName, appData.pieceNickNames);
5971 else SetCharTable(pieceNickName, "............");
5974 switch (gameInfo.variant) {
5975 case VariantFischeRandom:
5976 shuffleOpenings = TRUE;
5979 case VariantShatranj:
5980 pieces = ShatranjArray;
5981 nrCastlingRights = 0;
5982 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5985 pieces = makrukArray;
5986 nrCastlingRights = 0;
5987 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5990 pieces = aseanArray;
5991 nrCastlingRights = 0;
5992 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5994 case VariantTwoKings:
5995 pieces = twoKingsArray;
5998 pieces = GrandArray;
5999 nrCastlingRights = 0;
6000 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6001 gameInfo.boardWidth = 10;
6002 gameInfo.boardHeight = 10;
6003 gameInfo.holdingsSize = 7;
6005 case VariantCapaRandom:
6006 shuffleOpenings = TRUE;
6007 case VariantCapablanca:
6008 pieces = CapablancaArray;
6009 gameInfo.boardWidth = 10;
6010 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6013 pieces = GothicArray;
6014 gameInfo.boardWidth = 10;
6015 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6018 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6019 gameInfo.holdingsSize = 7;
6020 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6023 pieces = JanusArray;
6024 gameInfo.boardWidth = 10;
6025 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6026 nrCastlingRights = 6;
6027 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6028 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6029 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6030 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6031 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6032 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6035 pieces = FalconArray;
6036 gameInfo.boardWidth = 10;
6037 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6039 case VariantXiangqi:
6040 pieces = XiangqiArray;
6041 gameInfo.boardWidth = 9;
6042 gameInfo.boardHeight = 10;
6043 nrCastlingRights = 0;
6044 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6047 pieces = ShogiArray;
6048 gameInfo.boardWidth = 9;
6049 gameInfo.boardHeight = 9;
6050 gameInfo.holdingsSize = 7;
6051 nrCastlingRights = 0;
6052 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6055 pieces = ChuArray; pieceRows = 3;
6056 gameInfo.boardWidth = 12;
6057 gameInfo.boardHeight = 12;
6058 nrCastlingRights = 0;
6059 SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6060 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6062 case VariantCourier:
6063 pieces = CourierArray;
6064 gameInfo.boardWidth = 12;
6065 nrCastlingRights = 0;
6066 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6068 case VariantKnightmate:
6069 pieces = KnightmateArray;
6070 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6072 case VariantSpartan:
6073 pieces = SpartanArray;
6074 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6078 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6080 case VariantChuChess:
6081 pieces = ChuChessArray;
6082 gameInfo.boardWidth = 10;
6083 gameInfo.boardHeight = 10;
6084 SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6087 pieces = fairyArray;
6088 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6091 pieces = GreatArray;
6092 gameInfo.boardWidth = 10;
6093 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6094 gameInfo.holdingsSize = 8;
6098 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6099 gameInfo.holdingsSize = 8;
6100 startedFromSetupPosition = TRUE;
6102 case VariantCrazyhouse:
6103 case VariantBughouse:
6105 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6106 gameInfo.holdingsSize = 5;
6108 case VariantWildCastle:
6110 /* !!?shuffle with kings guaranteed to be on d or e file */
6111 shuffleOpenings = 1;
6113 case VariantNoCastle:
6115 nrCastlingRights = 0;
6116 /* !!?unconstrained back-rank shuffle */
6117 shuffleOpenings = 1;
6122 if(appData.NrFiles >= 0) {
6123 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6124 gameInfo.boardWidth = appData.NrFiles;
6126 if(appData.NrRanks >= 0) {
6127 gameInfo.boardHeight = appData.NrRanks;
6129 if(appData.holdingsSize >= 0) {
6130 i = appData.holdingsSize;
6131 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6132 gameInfo.holdingsSize = i;
6134 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6135 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6136 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6138 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6139 if(pawnRow < 1) pawnRow = 1;
6140 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6141 gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6142 if(gameInfo.variant == VariantChu) pawnRow = 3;
6144 /* User pieceToChar list overrules defaults */
6145 if(appData.pieceToCharTable != NULL)
6146 SetCharTable(pieceToChar, appData.pieceToCharTable);
6148 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6150 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6151 s = (ChessSquare) 0; /* account holding counts in guard band */
6152 for( i=0; i<BOARD_HEIGHT; i++ )
6153 initialPosition[i][j] = s;
6155 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6156 initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6157 initialPosition[pawnRow][j] = WhitePawn;
6158 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6159 if(gameInfo.variant == VariantXiangqi) {
6161 initialPosition[pawnRow][j] =
6162 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6163 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6164 initialPosition[2][j] = WhiteCannon;
6165 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6169 if(gameInfo.variant == VariantChu) {
6170 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6171 initialPosition[pawnRow+1][j] = WhiteCobra,
6172 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6173 for(i=1; i<pieceRows; i++) {
6174 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6175 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6178 if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6179 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6180 initialPosition[0][j] = WhiteRook;
6181 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6184 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth];
6186 if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6187 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6190 initialPosition[1][j] = WhiteBishop;
6191 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6193 initialPosition[1][j] = WhiteRook;
6194 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6197 if( nrCastlingRights == -1) {
6198 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6199 /* This sets default castling rights from none to normal corners */
6200 /* Variants with other castling rights must set them themselves above */
6201 nrCastlingRights = 6;
6203 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6204 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6205 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6206 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6207 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6208 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6211 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6212 if(gameInfo.variant == VariantGreat) { // promotion commoners
6213 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6214 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6215 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6216 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6218 if( gameInfo.variant == VariantSChess ) {
6219 initialPosition[1][0] = BlackMarshall;
6220 initialPosition[2][0] = BlackAngel;
6221 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6222 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6223 initialPosition[1][1] = initialPosition[2][1] =
6224 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6226 if (appData.debugMode) {
6227 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6229 if(shuffleOpenings) {
6230 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6231 startedFromSetupPosition = TRUE;
6233 if(startedFromPositionFile) {
6234 /* [HGM] loadPos: use PositionFile for every new game */
6235 CopyBoard(initialPosition, filePosition);
6236 for(i=0; i<nrCastlingRights; i++)
6237 initialRights[i] = filePosition[CASTLING][i];
6238 startedFromSetupPosition = TRUE;
6241 CopyBoard(boards[0], initialPosition);
6243 if(oldx != gameInfo.boardWidth ||
6244 oldy != gameInfo.boardHeight ||
6245 oldv != gameInfo.variant ||
6246 oldh != gameInfo.holdingsWidth
6248 InitDrawingSizes(-2 ,0);
6250 oldv = gameInfo.variant;
6252 DrawPosition(TRUE, boards[currentMove]);
6256 SendBoard (ChessProgramState *cps, int moveNum)
6258 char message[MSG_SIZ];
6260 if (cps->useSetboard) {
6261 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6262 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6263 SendToProgram(message, cps);
6268 int i, j, left=0, right=BOARD_WIDTH;
6269 /* Kludge to set black to move, avoiding the troublesome and now
6270 * deprecated "black" command.
6272 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6273 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6275 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6277 SendToProgram("edit\n", cps);
6278 SendToProgram("#\n", cps);
6279 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6280 bp = &boards[moveNum][i][left];
6281 for (j = left; j < right; j++, bp++) {
6282 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6283 if ((int) *bp < (int) BlackPawn) {
6284 if(j == BOARD_RGHT+1)
6285 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6286 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6287 if(message[0] == '+' || message[0] == '~') {
6288 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6289 PieceToChar((ChessSquare)(DEMOTED *bp)),
6292 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6293 message[1] = BOARD_RGHT - 1 - j + '1';
6294 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6296 SendToProgram(message, cps);
6301 SendToProgram("c\n", cps);
6302 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6303 bp = &boards[moveNum][i][left];
6304 for (j = left; j < right; j++, bp++) {
6305 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6306 if (((int) *bp != (int) EmptySquare)
6307 && ((int) *bp >= (int) BlackPawn)) {
6308 if(j == BOARD_LEFT-2)
6309 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6310 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6312 if(message[0] == '+' || message[0] == '~') {
6313 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6314 PieceToChar((ChessSquare)(DEMOTED *bp)),
6317 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6318 message[1] = BOARD_RGHT - 1 - j + '1';
6319 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6321 SendToProgram(message, cps);
6326 SendToProgram(".\n", cps);
6328 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6331 char exclusionHeader[MSG_SIZ];
6332 int exCnt, excludePtr;
6333 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6334 static Exclusion excluTab[200];
6335 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6341 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6342 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6348 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6349 excludePtr = 24; exCnt = 0;
6354 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6355 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6356 char buf[2*MOVE_LEN], *p;
6357 Exclusion *e = excluTab;
6359 for(i=0; i<exCnt; i++)
6360 if(e[i].ff == fromX && e[i].fr == fromY &&
6361 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6362 if(i == exCnt) { // was not in exclude list; add it
6363 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6364 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6365 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6368 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6369 excludePtr++; e[i].mark = excludePtr++;
6370 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6373 exclusionHeader[e[i].mark] = state;
6377 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6378 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6382 if((signed char)promoChar == -1) { // kludge to indicate best move
6383 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6384 return 1; // if unparsable, abort
6386 // update exclusion map (resolving toggle by consulting existing state)
6387 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6389 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6390 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6391 excludeMap[k] |= 1<<j;
6392 else excludeMap[k] &= ~(1<<j);
6394 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6396 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6397 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6399 return (state == '+');
6403 ExcludeClick (int index)
6406 Exclusion *e = excluTab;
6407 if(index < 25) { // none, best or tail clicked
6408 if(index < 13) { // none: include all
6409 WriteMap(0); // clear map
6410 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6411 SendToBoth("include all\n"); // and inform engine
6412 } else if(index > 18) { // tail
6413 if(exclusionHeader[19] == '-') { // tail was excluded
6414 SendToBoth("include all\n");
6415 WriteMap(0); // clear map completely
6416 // now re-exclude selected moves
6417 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6418 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6419 } else { // tail was included or in mixed state
6420 SendToBoth("exclude all\n");
6421 WriteMap(0xFF); // fill map completely
6422 // now re-include selected moves
6423 j = 0; // count them
6424 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6425 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6426 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6429 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6432 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6433 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6434 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6441 DefaultPromoChoice (int white)
6444 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6445 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6446 result = WhiteFerz; // no choice
6447 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6448 result= WhiteKing; // in Suicide Q is the last thing we want
6449 else if(gameInfo.variant == VariantSpartan)
6450 result = white ? WhiteQueen : WhiteAngel;
6451 else result = WhiteQueen;
6452 if(!white) result = WHITE_TO_BLACK result;
6456 static int autoQueen; // [HGM] oneclick
6459 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6461 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6462 /* [HGM] add Shogi promotions */
6463 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6464 ChessSquare piece, partner;
6468 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6469 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6471 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6472 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6475 piece = boards[currentMove][fromY][fromX];
6476 if(gameInfo.variant == VariantChu) {
6477 int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6478 promotionZoneSize = BOARD_HEIGHT/3;
6479 highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6480 } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6481 promotionZoneSize = BOARD_HEIGHT/3;
6482 highestPromotingPiece = (int)WhiteAlfil;
6483 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6484 promotionZoneSize = 3;
6487 // Treat Lance as Pawn when it is not representing Amazon
6488 if(gameInfo.variant != VariantSuper) {
6489 if(piece == WhiteLance) piece = WhitePawn; else
6490 if(piece == BlackLance) piece = BlackPawn;
6493 // next weed out all moves that do not touch the promotion zone at all
6494 if((int)piece >= BlackPawn) {
6495 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6497 if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6498 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6500 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6501 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6502 if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6506 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6508 // weed out mandatory Shogi promotions
6509 if(gameInfo.variant == VariantShogi) {
6510 if(piece >= BlackPawn) {
6511 if(toY == 0 && piece == BlackPawn ||
6512 toY == 0 && piece == BlackQueen ||
6513 toY <= 1 && piece == BlackKnight) {
6518 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6519 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6520 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6527 // weed out obviously illegal Pawn moves
6528 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6529 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6530 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6531 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6532 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6533 // note we are not allowed to test for valid (non-)capture, due to premove
6536 // we either have a choice what to promote to, or (in Shogi) whether to promote
6537 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6538 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6539 *promoChoice = PieceToChar(BlackFerz); // no choice
6542 // no sense asking what we must promote to if it is going to explode...
6543 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6544 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6547 // give caller the default choice even if we will not make it
6548 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6549 partner = piece; // pieces can promote if the pieceToCharTable says so
6550 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6551 else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6552 if( sweepSelect && gameInfo.variant != VariantGreat
6553 && gameInfo.variant != VariantGrand
6554 && gameInfo.variant != VariantSuper) return FALSE;
6555 if(autoQueen) return FALSE; // predetermined
6557 // suppress promotion popup on illegal moves that are not premoves
6558 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6559 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6560 if(appData.testLegality && !premove) {
6561 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6562 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6563 if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6564 if(moveType != WhitePromotion && moveType != BlackPromotion)
6572 InPalace (int row, int column)
6573 { /* [HGM] for Xiangqi */
6574 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6575 column < (BOARD_WIDTH + 4)/2 &&
6576 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6581 PieceForSquare (int x, int y)
6583 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6586 return boards[currentMove][y][x];
6590 OKToStartUserMove (int x, int y)
6592 ChessSquare from_piece;
6595 if (matchMode) return FALSE;
6596 if (gameMode == EditPosition) return TRUE;
6598 if (x >= 0 && y >= 0)
6599 from_piece = boards[currentMove][y][x];
6601 from_piece = EmptySquare;
6603 if (from_piece == EmptySquare) return FALSE;
6605 white_piece = (int)from_piece >= (int)WhitePawn &&
6606 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6610 case TwoMachinesPlay:
6618 case MachinePlaysWhite:
6619 case IcsPlayingBlack:
6620 if (appData.zippyPlay) return FALSE;
6622 DisplayMoveError(_("You are playing Black"));
6627 case MachinePlaysBlack:
6628 case IcsPlayingWhite:
6629 if (appData.zippyPlay) return FALSE;
6631 DisplayMoveError(_("You are playing White"));
6636 case PlayFromGameFile:
6637 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6639 if (!white_piece && WhiteOnMove(currentMove)) {
6640 DisplayMoveError(_("It is White's turn"));
6643 if (white_piece && !WhiteOnMove(currentMove)) {
6644 DisplayMoveError(_("It is Black's turn"));
6647 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6648 /* Editing correspondence game history */
6649 /* Could disallow this or prompt for confirmation */
6654 case BeginningOfGame:
6655 if (appData.icsActive) return FALSE;
6656 if (!appData.noChessProgram) {
6658 DisplayMoveError(_("You are playing White"));
6665 if (!white_piece && WhiteOnMove(currentMove)) {
6666 DisplayMoveError(_("It is White's turn"));
6669 if (white_piece && !WhiteOnMove(currentMove)) {
6670 DisplayMoveError(_("It is Black's turn"));
6679 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6680 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6681 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6682 && gameMode != AnalyzeFile && gameMode != Training) {
6683 DisplayMoveError(_("Displayed position is not current"));
6690 OnlyMove (int *x, int *y, Boolean captures)
6692 DisambiguateClosure cl;
6693 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6695 case MachinePlaysBlack:
6696 case IcsPlayingWhite:
6697 case BeginningOfGame:
6698 if(!WhiteOnMove(currentMove)) return FALSE;
6700 case MachinePlaysWhite:
6701 case IcsPlayingBlack:
6702 if(WhiteOnMove(currentMove)) return FALSE;
6709 cl.pieceIn = EmptySquare;
6714 cl.promoCharIn = NULLCHAR;
6715 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6716 if( cl.kind == NormalMove ||
6717 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6718 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6719 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6726 if(cl.kind != ImpossibleMove) return FALSE;
6727 cl.pieceIn = EmptySquare;
6732 cl.promoCharIn = NULLCHAR;
6733 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6734 if( cl.kind == NormalMove ||
6735 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6736 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6737 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6742 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6748 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6749 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6750 int lastLoadGameUseList = FALSE;
6751 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6752 ChessMove lastLoadGameStart = EndOfFile;
6756 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6760 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6762 /* Check if the user is playing in turn. This is complicated because we
6763 let the user "pick up" a piece before it is his turn. So the piece he
6764 tried to pick up may have been captured by the time he puts it down!
6765 Therefore we use the color the user is supposed to be playing in this
6766 test, not the color of the piece that is currently on the starting
6767 square---except in EditGame mode, where the user is playing both
6768 sides; fortunately there the capture race can't happen. (It can
6769 now happen in IcsExamining mode, but that's just too bad. The user
6770 will get a somewhat confusing message in that case.)
6775 case TwoMachinesPlay:
6779 /* We switched into a game mode where moves are not accepted,
6780 perhaps while the mouse button was down. */
6783 case MachinePlaysWhite:
6784 /* User is moving for Black */
6785 if (WhiteOnMove(currentMove)) {
6786 DisplayMoveError(_("It is White's turn"));
6791 case MachinePlaysBlack:
6792 /* User is moving for White */
6793 if (!WhiteOnMove(currentMove)) {
6794 DisplayMoveError(_("It is Black's turn"));
6799 case PlayFromGameFile:
6800 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6803 case BeginningOfGame:
6806 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6807 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6808 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6809 /* User is moving for Black */
6810 if (WhiteOnMove(currentMove)) {
6811 DisplayMoveError(_("It is White's turn"));
6815 /* User is moving for White */
6816 if (!WhiteOnMove(currentMove)) {
6817 DisplayMoveError(_("It is Black's turn"));
6823 case IcsPlayingBlack:
6824 /* User is moving for Black */
6825 if (WhiteOnMove(currentMove)) {
6826 if (!appData.premove) {
6827 DisplayMoveError(_("It is White's turn"));
6828 } else if (toX >= 0 && toY >= 0) {
6831 premoveFromX = fromX;
6832 premoveFromY = fromY;
6833 premovePromoChar = promoChar;
6835 if (appData.debugMode)
6836 fprintf(debugFP, "Got premove: fromX %d,"
6837 "fromY %d, toX %d, toY %d\n",
6838 fromX, fromY, toX, toY);
6844 case IcsPlayingWhite:
6845 /* User is moving for White */
6846 if (!WhiteOnMove(currentMove)) {
6847 if (!appData.premove) {
6848 DisplayMoveError(_("It is Black's turn"));
6849 } else if (toX >= 0 && toY >= 0) {
6852 premoveFromX = fromX;
6853 premoveFromY = fromY;
6854 premovePromoChar = promoChar;
6856 if (appData.debugMode)
6857 fprintf(debugFP, "Got premove: fromX %d,"
6858 "fromY %d, toX %d, toY %d\n",
6859 fromX, fromY, toX, toY);
6869 /* EditPosition, empty square, or different color piece;
6870 click-click move is possible */
6871 if (toX == -2 || toY == -2) {
6872 boards[0][fromY][fromX] = EmptySquare;
6873 DrawPosition(FALSE, boards[currentMove]);
6875 } else if (toX >= 0 && toY >= 0) {
6876 boards[0][toY][toX] = boards[0][fromY][fromX];
6877 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6878 if(boards[0][fromY][0] != EmptySquare) {
6879 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6880 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6883 if(fromX == BOARD_RGHT+1) {
6884 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6885 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6886 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6889 boards[0][fromY][fromX] = gatingPiece;
6890 DrawPosition(FALSE, boards[currentMove]);
6896 if(toX < 0 || toY < 0) return;
6897 pup = boards[currentMove][toY][toX];
6899 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6900 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6901 if( pup != EmptySquare ) return;
6902 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6903 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6904 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6905 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6906 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6907 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6908 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6912 /* [HGM] always test for legality, to get promotion info */
6913 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6914 fromY, fromX, toY, toX, promoChar);
6916 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6918 /* [HGM] but possibly ignore an IllegalMove result */
6919 if (appData.testLegality) {
6920 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6921 DisplayMoveError(_("Illegal move"));
6926 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6927 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6928 ClearPremoveHighlights(); // was included
6929 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6933 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6936 /* Common tail of UserMoveEvent and DropMenuEvent */
6938 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6942 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6943 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6944 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6945 if(WhiteOnMove(currentMove)) {
6946 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6948 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6952 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6953 move type in caller when we know the move is a legal promotion */
6954 if(moveType == NormalMove && promoChar)
6955 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6957 /* [HGM] <popupFix> The following if has been moved here from
6958 UserMoveEvent(). Because it seemed to belong here (why not allow
6959 piece drops in training games?), and because it can only be
6960 performed after it is known to what we promote. */
6961 if (gameMode == Training) {
6962 /* compare the move played on the board to the next move in the
6963 * game. If they match, display the move and the opponent's response.
6964 * If they don't match, display an error message.
6968 CopyBoard(testBoard, boards[currentMove]);
6969 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6971 if (CompareBoards(testBoard, boards[currentMove+1])) {
6972 ForwardInner(currentMove+1);
6974 /* Autoplay the opponent's response.
6975 * if appData.animate was TRUE when Training mode was entered,
6976 * the response will be animated.
6978 saveAnimate = appData.animate;
6979 appData.animate = animateTraining;
6980 ForwardInner(currentMove+1);
6981 appData.animate = saveAnimate;
6983 /* check for the end of the game */
6984 if (currentMove >= forwardMostMove) {
6985 gameMode = PlayFromGameFile;
6987 SetTrainingModeOff();
6988 DisplayInformation(_("End of game"));
6991 DisplayError(_("Incorrect move"), 0);
6996 /* Ok, now we know that the move is good, so we can kill
6997 the previous line in Analysis Mode */
6998 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6999 && currentMove < forwardMostMove) {
7000 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7001 else forwardMostMove = currentMove;
7006 /* If we need the chess program but it's dead, restart it */
7007 ResurrectChessProgram();
7009 /* A user move restarts a paused game*/
7013 thinkOutput[0] = NULLCHAR;
7015 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7017 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7018 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7022 if (gameMode == BeginningOfGame) {
7023 if (appData.noChessProgram) {
7024 gameMode = EditGame;
7028 gameMode = MachinePlaysBlack;
7031 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7033 if (first.sendName) {
7034 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7035 SendToProgram(buf, &first);
7042 /* Relay move to ICS or chess engine */
7043 if (appData.icsActive) {
7044 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7045 gameMode == IcsExamining) {
7046 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7047 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7049 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7051 // also send plain move, in case ICS does not understand atomic claims
7052 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7056 if (first.sendTime && (gameMode == BeginningOfGame ||
7057 gameMode == MachinePlaysWhite ||
7058 gameMode == MachinePlaysBlack)) {
7059 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7061 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7062 // [HGM] book: if program might be playing, let it use book
7063 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7064 first.maybeThinking = TRUE;
7065 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7066 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7067 SendBoard(&first, currentMove+1);
7068 if(second.analyzing) {
7069 if(!second.useSetboard) SendToProgram("undo\n", &second);
7070 SendBoard(&second, currentMove+1);
7073 SendMoveToProgram(forwardMostMove-1, &first);
7074 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7076 if (currentMove == cmailOldMove + 1) {
7077 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7081 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7085 if(appData.testLegality)
7086 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7092 if (WhiteOnMove(currentMove)) {
7093 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7095 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7099 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7104 case MachinePlaysBlack:
7105 case MachinePlaysWhite:
7106 /* disable certain menu options while machine is thinking */
7107 SetMachineThinkingEnables();
7114 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7115 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7117 if(bookHit) { // [HGM] book: simulate book reply
7118 static char bookMove[MSG_SIZ]; // a bit generous?
7120 programStats.nodes = programStats.depth = programStats.time =
7121 programStats.score = programStats.got_only_move = 0;
7122 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7124 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7125 strcat(bookMove, bookHit);
7126 HandleMachineMove(bookMove, &first);
7132 MarkByFEN(char *fen)
7135 if(!appData.markers || !appData.highlightDragging) return;
7136 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7137 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7141 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7142 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7143 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7144 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7145 if(*fen == 'T') marker[r][f++] = 0; else
7146 if(*fen == 'Y') marker[r][f++] = 1; else
7147 if(*fen == 'G') marker[r][f++] = 3; else
7148 if(*fen == 'B') marker[r][f++] = 4; else
7149 if(*fen == 'C') marker[r][f++] = 5; else
7150 if(*fen == 'M') marker[r][f++] = 6; else
7151 if(*fen == 'W') marker[r][f++] = 7; else
7152 if(*fen == 'D') marker[r][f++] = 8; else
7153 if(*fen == 'R') marker[r][f++] = 2; else {
7154 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7157 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7161 DrawPosition(TRUE, NULL);
7164 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7167 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7169 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7170 Markers *m = (Markers *) closure;
7171 if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7172 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7173 || kind == WhiteCapturesEnPassant
7174 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7175 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7179 MarkTargetSquares (int clear)
7182 if(clear) { // no reason to ever suppress clearing
7183 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;
7184 if(!sum) return; // nothing was cleared,no redraw needed
7187 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7188 !appData.testLegality || gameMode == EditPosition) return;
7189 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7190 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7191 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7193 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7196 DrawPosition(FALSE, NULL);
7200 Explode (Board board, int fromX, int fromY, int toX, int toY)
7202 if(gameInfo.variant == VariantAtomic &&
7203 (board[toY][toX] != EmptySquare || // capture?
7204 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7205 board[fromY][fromX] == BlackPawn )
7207 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7213 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7216 CanPromote (ChessSquare piece, int y)
7218 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7219 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7220 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7221 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7222 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7223 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7224 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7225 return (piece == BlackPawn && y <= zone ||
7226 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7227 piece == BlackLance && y == 1 ||
7228 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7232 HoverEvent (int xPix, int yPix, int x, int y)
7234 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7236 if(!first.highlight) return;
7237 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7238 if(x == oldX && y == oldY) return; // only do something if we enter new square
7239 oldFromX = fromX; oldFromY = fromY;
7240 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7241 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7242 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7243 else if(oldX != x || oldY != y) {
7244 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7245 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7246 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7247 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7249 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7250 SendToProgram(buf, &first);
7253 // SetHighlights(fromX, fromY, x, y);
7257 void ReportClick(char *action, int x, int y)
7259 char buf[MSG_SIZ]; // Inform engine of what user does
7261 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7262 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7263 if(!first.highlight || gameMode == EditPosition) return;
7264 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7265 SendToProgram(buf, &first);
7269 LeftClick (ClickType clickType, int xPix, int yPix)
7272 Boolean saveAnimate;
7273 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7274 char promoChoice = NULLCHAR;
7276 static TimeMark lastClickTime, prevClickTime;
7278 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7280 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7282 if (clickType == Press) ErrorPopDown();
7283 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7285 x = EventToSquare(xPix, BOARD_WIDTH);
7286 y = EventToSquare(yPix, BOARD_HEIGHT);
7287 if (!flipView && y >= 0) {
7288 y = BOARD_HEIGHT - 1 - y;
7290 if (flipView && x >= 0) {
7291 x = BOARD_WIDTH - 1 - x;
7294 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7295 defaultPromoChoice = promoSweep;
7296 promoSweep = EmptySquare; // terminate sweep
7297 promoDefaultAltered = TRUE;
7298 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7301 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7302 if(clickType == Release) return; // ignore upclick of click-click destination
7303 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7304 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7305 if(gameInfo.holdingsWidth &&
7306 (WhiteOnMove(currentMove)
7307 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7308 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7309 // click in right holdings, for determining promotion piece
7310 ChessSquare p = boards[currentMove][y][x];
7311 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7312 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7313 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7314 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7319 DrawPosition(FALSE, boards[currentMove]);
7323 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7324 if(clickType == Press
7325 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7326 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7327 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7330 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7331 // could be static click on premove from-square: abort premove
7333 ClearPremoveHighlights();
7336 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7337 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7339 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7340 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7341 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7342 defaultPromoChoice = DefaultPromoChoice(side);
7345 autoQueen = appData.alwaysPromoteToQueen;
7349 gatingPiece = EmptySquare;
7350 if (clickType != Press) {
7351 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7352 DragPieceEnd(xPix, yPix); dragging = 0;
7353 DrawPosition(FALSE, NULL);
7357 doubleClick = FALSE;
7358 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7359 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7361 fromX = x; fromY = y; toX = toY = killX = killY = -1;
7362 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7363 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7364 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7366 if (OKToStartUserMove(fromX, fromY)) {
7368 ReportClick("lift", x, y);
7369 MarkTargetSquares(0);
7370 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7371 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7372 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7373 promoSweep = defaultPromoChoice;
7374 selectFlag = 0; lastX = xPix; lastY = yPix;
7375 Sweep(0); // Pawn that is going to promote: preview promotion piece
7376 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7378 if (appData.highlightDragging) {
7379 SetHighlights(fromX, fromY, -1, -1);
7383 } else fromX = fromY = -1;
7389 if (clickType == Press && gameMode != EditPosition) {
7394 // ignore off-board to clicks
7395 if(y < 0 || x < 0) return;
7397 /* Check if clicking again on the same color piece */
7398 fromP = boards[currentMove][fromY][fromX];
7399 toP = boards[currentMove][y][x];
7400 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7401 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7402 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7403 WhitePawn <= toP && toP <= WhiteKing &&
7404 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7405 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7406 (BlackPawn <= fromP && fromP <= BlackKing &&
7407 BlackPawn <= toP && toP <= BlackKing &&
7408 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7409 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7410 /* Clicked again on same color piece -- changed his mind */
7411 second = (x == fromX && y == fromY);
7413 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7414 second = FALSE; // first double-click rather than scond click
7415 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7417 promoDefaultAltered = FALSE;
7418 MarkTargetSquares(1);
7419 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7420 if (appData.highlightDragging) {
7421 SetHighlights(x, y, -1, -1);
7425 if (OKToStartUserMove(x, y)) {
7426 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7427 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7428 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7429 gatingPiece = boards[currentMove][fromY][fromX];
7430 else gatingPiece = doubleClick ? fromP : EmptySquare;
7432 fromY = y; dragging = 1;
7433 ReportClick("lift", x, y);
7434 MarkTargetSquares(0);
7435 DragPieceBegin(xPix, yPix, FALSE);
7436 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7437 promoSweep = defaultPromoChoice;
7438 selectFlag = 0; lastX = xPix; lastY = yPix;
7439 Sweep(0); // Pawn that is going to promote: preview promotion piece
7443 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7446 // ignore clicks on holdings
7447 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7450 if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7451 DragPieceEnd(xPix, yPix); dragging = 0;
7453 // a deferred attempt to click-click move an empty square on top of a piece
7454 boards[currentMove][y][x] = EmptySquare;
7456 DrawPosition(FALSE, boards[currentMove]);
7457 fromX = fromY = -1; clearFlag = 0;
7460 if (appData.animateDragging) {
7461 /* Undo animation damage if any */
7462 DrawPosition(FALSE, NULL);
7464 if (second || sweepSelecting) {
7465 /* Second up/down in same square; just abort move */
7466 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7467 second = sweepSelecting = 0;
7469 gatingPiece = EmptySquare;
7470 MarkTargetSquares(1);
7473 ClearPremoveHighlights();
7475 /* First upclick in same square; start click-click mode */
7476 SetHighlights(x, y, -1, -1);
7483 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7484 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7485 DisplayMessage(_("only marked squares are legal"),"");
7486 DrawPosition(TRUE, NULL);
7487 return; // ignore to-click
7490 /* we now have a different from- and (possibly off-board) to-square */
7491 /* Completed move */
7492 if(!sweepSelecting) {
7497 piece = boards[currentMove][fromY][fromX];
7499 saveAnimate = appData.animate;
7500 if (clickType == Press) {
7501 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7502 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7503 // must be Edit Position mode with empty-square selected
7504 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7505 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7508 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7511 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7512 killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7514 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7515 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7516 if(appData.sweepSelect) {
7517 promoSweep = defaultPromoChoice;
7518 if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7519 selectFlag = 0; lastX = xPix; lastY = yPix;
7520 Sweep(0); // Pawn that is going to promote: preview promotion piece
7522 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7523 MarkTargetSquares(1);
7525 return; // promo popup appears on up-click
7527 /* Finish clickclick move */
7528 if (appData.animate || appData.highlightLastMove) {
7529 SetHighlights(fromX, fromY, toX, toY);
7533 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7534 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7535 if (appData.animate || appData.highlightLastMove) {
7536 SetHighlights(fromX, fromY, toX, toY);
7542 // [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
7543 /* Finish drag move */
7544 if (appData.highlightLastMove) {
7545 SetHighlights(fromX, fromY, toX, toY);
7550 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7551 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7552 dragging *= 2; // flag button-less dragging if we are dragging
7553 MarkTargetSquares(1);
7554 if(x == killX && y == killY) killX = killY = -1; else {
7555 killX = x; killY = y; //remeber this square as intermediate
7556 ReportClick("put", x, y); // and inform engine
7557 ReportClick("lift", x, y);
7558 MarkTargetSquares(0);
7562 DragPieceEnd(xPix, yPix); dragging = 0;
7563 /* Don't animate move and drag both */
7564 appData.animate = FALSE;
7567 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7568 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7569 ChessSquare piece = boards[currentMove][fromY][fromX];
7570 if(gameMode == EditPosition && piece != EmptySquare &&
7571 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7574 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7575 n = PieceToNumber(piece - (int)BlackPawn);
7576 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7577 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7578 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7580 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7581 n = PieceToNumber(piece);
7582 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7583 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7584 boards[currentMove][n][BOARD_WIDTH-2]++;
7586 boards[currentMove][fromY][fromX] = EmptySquare;
7590 MarkTargetSquares(1);
7591 DrawPosition(TRUE, boards[currentMove]);
7595 // off-board moves should not be highlighted
7596 if(x < 0 || y < 0) ClearHighlights();
7597 else ReportClick("put", x, y);
7599 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7601 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7602 SetHighlights(fromX, fromY, toX, toY);
7603 MarkTargetSquares(1);
7604 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7605 // [HGM] super: promotion to captured piece selected from holdings
7606 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7607 promotionChoice = TRUE;
7608 // kludge follows to temporarily execute move on display, without promoting yet
7609 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7610 boards[currentMove][toY][toX] = p;
7611 DrawPosition(FALSE, boards[currentMove]);
7612 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7613 boards[currentMove][toY][toX] = q;
7614 DisplayMessage("Click in holdings to choose piece", "");
7617 PromotionPopUp(promoChoice);
7619 int oldMove = currentMove;
7620 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7621 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7622 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7623 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7624 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7625 DrawPosition(TRUE, boards[currentMove]);
7626 MarkTargetSquares(1);
7629 appData.animate = saveAnimate;
7630 if (appData.animate || appData.animateDragging) {
7631 /* Undo animation damage if needed */
7632 DrawPosition(FALSE, NULL);
7637 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7638 { // front-end-free part taken out of PieceMenuPopup
7639 int whichMenu; int xSqr, ySqr;
7641 if(seekGraphUp) { // [HGM] seekgraph
7642 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7643 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7647 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7648 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7649 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7650 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7651 if(action == Press) {
7652 originalFlip = flipView;
7653 flipView = !flipView; // temporarily flip board to see game from partners perspective
7654 DrawPosition(TRUE, partnerBoard);
7655 DisplayMessage(partnerStatus, "");
7657 } else if(action == Release) {
7658 flipView = originalFlip;
7659 DrawPosition(TRUE, boards[currentMove]);
7665 xSqr = EventToSquare(x, BOARD_WIDTH);
7666 ySqr = EventToSquare(y, BOARD_HEIGHT);
7667 if (action == Release) {
7668 if(pieceSweep != EmptySquare) {
7669 EditPositionMenuEvent(pieceSweep, toX, toY);
7670 pieceSweep = EmptySquare;
7671 } else UnLoadPV(); // [HGM] pv
7673 if (action != Press) return -2; // return code to be ignored
7676 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7678 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7679 if (xSqr < 0 || ySqr < 0) return -1;
7680 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7681 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7682 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7683 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7687 if(!appData.icsEngineAnalyze) return -1;
7688 case IcsPlayingWhite:
7689 case IcsPlayingBlack:
7690 if(!appData.zippyPlay) goto noZip;
7693 case MachinePlaysWhite:
7694 case MachinePlaysBlack:
7695 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7696 if (!appData.dropMenu) {
7698 return 2; // flag front-end to grab mouse events
7700 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7701 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7704 if (xSqr < 0 || ySqr < 0) return -1;
7705 if (!appData.dropMenu || appData.testLegality &&
7706 gameInfo.variant != VariantBughouse &&
7707 gameInfo.variant != VariantCrazyhouse) return -1;
7708 whichMenu = 1; // drop menu
7714 if (((*fromX = xSqr) < 0) ||
7715 ((*fromY = ySqr) < 0)) {
7716 *fromX = *fromY = -1;
7720 *fromX = BOARD_WIDTH - 1 - *fromX;
7722 *fromY = BOARD_HEIGHT - 1 - *fromY;
7728 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7730 // char * hint = lastHint;
7731 FrontEndProgramStats stats;
7733 stats.which = cps == &first ? 0 : 1;
7734 stats.depth = cpstats->depth;
7735 stats.nodes = cpstats->nodes;
7736 stats.score = cpstats->score;
7737 stats.time = cpstats->time;
7738 stats.pv = cpstats->movelist;
7739 stats.hint = lastHint;
7740 stats.an_move_index = 0;
7741 stats.an_move_count = 0;
7743 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7744 stats.hint = cpstats->move_name;
7745 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7746 stats.an_move_count = cpstats->nr_moves;
7749 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
7751 SetProgramStats( &stats );
7755 ClearEngineOutputPane (int which)
7757 static FrontEndProgramStats dummyStats;
7758 dummyStats.which = which;
7759 dummyStats.pv = "#";
7760 SetProgramStats( &dummyStats );
7763 #define MAXPLAYERS 500
7766 TourneyStandings (int display)
7768 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7769 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7770 char result, *p, *names[MAXPLAYERS];
7772 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7773 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7774 names[0] = p = strdup(appData.participants);
7775 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7777 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7779 while(result = appData.results[nr]) {
7780 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7781 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7782 wScore = bScore = 0;
7784 case '+': wScore = 2; break;
7785 case '-': bScore = 2; break;
7786 case '=': wScore = bScore = 1; break;
7788 case '*': return strdup("busy"); // tourney not finished
7796 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7797 for(w=0; w<nPlayers; w++) {
7799 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7800 ranking[w] = b; points[w] = bScore; score[b] = -2;
7802 p = malloc(nPlayers*34+1);
7803 for(w=0; w<nPlayers && w<display; w++)
7804 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7810 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7811 { // count all piece types
7813 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7814 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7815 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7818 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7819 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7820 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7821 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7822 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7823 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7828 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7830 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7831 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7833 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7834 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7835 if(myPawns == 2 && nMine == 3) // KPP
7836 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7837 if(myPawns == 1 && nMine == 2) // KP
7838 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7839 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7840 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7841 if(myPawns) return FALSE;
7842 if(pCnt[WhiteRook+side])
7843 return pCnt[BlackRook-side] ||
7844 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7845 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7846 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7847 if(pCnt[WhiteCannon+side]) {
7848 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7849 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7851 if(pCnt[WhiteKnight+side])
7852 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7857 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7859 VariantClass v = gameInfo.variant;
7861 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7862 if(v == VariantShatranj) return TRUE; // always winnable through baring
7863 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7864 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7866 if(v == VariantXiangqi) {
7867 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7869 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7870 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7871 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7872 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7873 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7874 if(stale) // we have at least one last-rank P plus perhaps C
7875 return majors // KPKX
7876 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7878 return pCnt[WhiteFerz+side] // KCAK
7879 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7880 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7881 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7883 } else if(v == VariantKnightmate) {
7884 if(nMine == 1) return FALSE;
7885 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7886 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7887 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7889 if(nMine == 1) return FALSE; // bare King
7890 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
7891 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7892 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7893 // by now we have King + 1 piece (or multiple Bishops on the same color)
7894 if(pCnt[WhiteKnight+side])
7895 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7896 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7897 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7899 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7900 if(pCnt[WhiteAlfil+side])
7901 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7902 if(pCnt[WhiteWazir+side])
7903 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7910 CompareWithRights (Board b1, Board b2)
7913 if(!CompareBoards(b1, b2)) return FALSE;
7914 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7915 /* compare castling rights */
7916 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7917 rights++; /* King lost rights, while rook still had them */
7918 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7919 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7920 rights++; /* but at least one rook lost them */
7922 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7924 if( b1[CASTLING][5] != NoRights ) {
7925 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7932 Adjudicate (ChessProgramState *cps)
7933 { // [HGM] some adjudications useful with buggy engines
7934 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7935 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7936 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7937 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7938 int k, drop, count = 0; static int bare = 1;
7939 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7940 Boolean canAdjudicate = !appData.icsActive;
7942 // most tests only when we understand the game, i.e. legality-checking on
7943 if( appData.testLegality )
7944 { /* [HGM] Some more adjudications for obstinate engines */
7945 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7946 static int moveCount = 6;
7948 char *reason = NULL;
7950 /* Count what is on board. */
7951 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7953 /* Some material-based adjudications that have to be made before stalemate test */
7954 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7955 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7956 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7957 if(canAdjudicate && appData.checkMates) {
7959 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7960 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7961 "Xboard adjudication: King destroyed", GE_XBOARD );
7966 /* Bare King in Shatranj (loses) or Losers (wins) */
7967 if( nrW == 1 || nrB == 1) {
7968 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7969 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7970 if(canAdjudicate && appData.checkMates) {
7972 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7973 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7974 "Xboard adjudication: Bare king", GE_XBOARD );
7978 if( gameInfo.variant == VariantShatranj && --bare < 0)
7980 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7981 if(canAdjudicate && appData.checkMates) {
7982 /* but only adjudicate if adjudication enabled */
7984 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7985 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7986 "Xboard adjudication: Bare king", GE_XBOARD );
7993 // don't wait for engine to announce game end if we can judge ourselves
7994 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7996 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7997 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7998 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7999 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8002 reason = "Xboard adjudication: 3rd check";
8003 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8013 reason = "Xboard adjudication: Stalemate";
8014 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8015 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8016 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8017 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8018 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8019 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8020 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8021 EP_CHECKMATE : EP_WINS);
8022 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8023 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8027 reason = "Xboard adjudication: Checkmate";
8028 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8029 if(gameInfo.variant == VariantShogi) {
8030 if(forwardMostMove > backwardMostMove
8031 && moveList[forwardMostMove-1][1] == '@'
8032 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8033 reason = "XBoard adjudication: pawn-drop mate";
8034 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8040 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8042 result = GameIsDrawn; break;
8044 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8046 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8050 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8052 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8053 GameEnds( result, reason, GE_XBOARD );
8057 /* Next absolutely insufficient mating material. */
8058 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8059 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8060 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8062 /* always flag draws, for judging claims */
8063 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8065 if(canAdjudicate && appData.materialDraws) {
8066 /* but only adjudicate them if adjudication enabled */
8067 if(engineOpponent) {
8068 SendToProgram("force\n", engineOpponent); // suppress reply
8069 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8071 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8076 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8077 if(gameInfo.variant == VariantXiangqi ?
8078 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8080 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8081 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8082 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8083 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8085 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8086 { /* if the first 3 moves do not show a tactical win, declare draw */
8087 if(engineOpponent) {
8088 SendToProgram("force\n", engineOpponent); // suppress reply
8089 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8091 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8094 } else moveCount = 6;
8097 // Repetition draws and 50-move rule can be applied independently of legality testing
8099 /* Check for rep-draws */
8101 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8102 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8103 for(k = forwardMostMove-2;
8104 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8105 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8106 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8109 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8110 /* compare castling rights */
8111 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8112 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8113 rights++; /* King lost rights, while rook still had them */
8114 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8115 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8116 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8117 rights++; /* but at least one rook lost them */
8119 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8120 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8122 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8123 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8124 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8127 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8128 && appData.drawRepeats > 1) {
8129 /* adjudicate after user-specified nr of repeats */
8130 int result = GameIsDrawn;
8131 char *details = "XBoard adjudication: repetition draw";
8132 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8133 // [HGM] xiangqi: check for forbidden perpetuals
8134 int m, ourPerpetual = 1, hisPerpetual = 1;
8135 for(m=forwardMostMove; m>k; m-=2) {
8136 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8137 ourPerpetual = 0; // the current mover did not always check
8138 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8139 hisPerpetual = 0; // the opponent did not always check
8141 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8142 ourPerpetual, hisPerpetual);
8143 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8144 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8145 details = "Xboard adjudication: perpetual checking";
8147 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8148 break; // (or we would have caught him before). Abort repetition-checking loop.
8150 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8151 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8153 details = "Xboard adjudication: repetition";
8155 } else // it must be XQ
8156 // Now check for perpetual chases
8157 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8158 hisPerpetual = PerpetualChase(k, forwardMostMove);
8159 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8160 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8161 static char resdet[MSG_SIZ];
8162 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8164 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8166 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8167 break; // Abort repetition-checking loop.
8169 // if neither of us is checking or chasing all the time, or both are, it is draw
8171 if(engineOpponent) {
8172 SendToProgram("force\n", engineOpponent); // suppress reply
8173 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8175 GameEnds( result, details, GE_XBOARD );
8178 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8179 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8183 /* Now we test for 50-move draws. Determine ply count */
8184 count = forwardMostMove;
8185 /* look for last irreversble move */
8186 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8188 /* if we hit starting position, add initial plies */
8189 if( count == backwardMostMove )
8190 count -= initialRulePlies;
8191 count = forwardMostMove - count;
8192 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8193 // adjust reversible move counter for checks in Xiangqi
8194 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8195 if(i < backwardMostMove) i = backwardMostMove;
8196 while(i <= forwardMostMove) {
8197 lastCheck = inCheck; // check evasion does not count
8198 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8199 if(inCheck || lastCheck) count--; // check does not count
8204 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8205 /* this is used to judge if draw claims are legal */
8206 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8207 if(engineOpponent) {
8208 SendToProgram("force\n", engineOpponent); // suppress reply
8209 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8211 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8215 /* if draw offer is pending, treat it as a draw claim
8216 * when draw condition present, to allow engines a way to
8217 * claim draws before making their move to avoid a race
8218 * condition occurring after their move
8220 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8222 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8223 p = "Draw claim: 50-move rule";
8224 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8225 p = "Draw claim: 3-fold repetition";
8226 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8227 p = "Draw claim: insufficient mating material";
8228 if( p != NULL && canAdjudicate) {
8229 if(engineOpponent) {
8230 SendToProgram("force\n", engineOpponent); // suppress reply
8231 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8233 GameEnds( GameIsDrawn, p, GE_XBOARD );
8238 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8239 if(engineOpponent) {
8240 SendToProgram("force\n", engineOpponent); // suppress reply
8241 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8243 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8250 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8251 { // [HGM] book: this routine intercepts moves to simulate book replies
8252 char *bookHit = NULL;
8254 //first determine if the incoming move brings opponent into his book
8255 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8256 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8257 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8258 if(bookHit != NULL && !cps->bookSuspend) {
8259 // make sure opponent is not going to reply after receiving move to book position
8260 SendToProgram("force\n", cps);
8261 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8263 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8264 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8265 // now arrange restart after book miss
8267 // after a book hit we never send 'go', and the code after the call to this routine
8268 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8269 char buf[MSG_SIZ], *move = bookHit;
8271 int fromX, fromY, toX, toY;
8275 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8276 &fromX, &fromY, &toX, &toY, &promoChar)) {
8277 (void) CoordsToAlgebraic(boards[forwardMostMove],
8278 PosFlags(forwardMostMove),
8279 fromY, fromX, toY, toX, promoChar, move);
8281 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8285 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8286 SendToProgram(buf, cps);
8287 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8288 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8289 SendToProgram("go\n", cps);
8290 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8291 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8292 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8293 SendToProgram("go\n", cps);
8294 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8296 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8300 LoadError (char *errmess, ChessProgramState *cps)
8301 { // unloads engine and switches back to -ncp mode if it was first
8302 if(cps->initDone) return FALSE;
8303 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8304 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8307 appData.noChessProgram = TRUE;
8308 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8309 gameMode = BeginningOfGame; ModeHighlight();
8312 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8313 DisplayMessage("", ""); // erase waiting message
8314 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8319 ChessProgramState *savedState;
8321 DeferredBookMove (void)
8323 if(savedState->lastPing != savedState->lastPong)
8324 ScheduleDelayedEvent(DeferredBookMove, 10);
8326 HandleMachineMove(savedMessage, savedState);
8329 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8330 static ChessProgramState *stalledEngine;
8331 static char stashedInputMove[MSG_SIZ];
8334 HandleMachineMove (char *message, ChessProgramState *cps)
8336 static char firstLeg[20];
8337 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8338 char realname[MSG_SIZ];
8339 int fromX, fromY, toX, toY;
8341 char promoChar, roar;
8343 int machineWhite, oldError;
8346 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8347 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8348 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8349 DisplayError(_("Invalid pairing from pairing engine"), 0);
8352 pairingReceived = 1;
8354 return; // Skim the pairing messages here.
8357 oldError = cps->userError; cps->userError = 0;
8359 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8361 * Kludge to ignore BEL characters
8363 while (*message == '\007') message++;
8366 * [HGM] engine debug message: ignore lines starting with '#' character
8368 if(cps->debug && *message == '#') return;
8371 * Look for book output
8373 if (cps == &first && bookRequested) {
8374 if (message[0] == '\t' || message[0] == ' ') {
8375 /* Part of the book output is here; append it */
8376 strcat(bookOutput, message);
8377 strcat(bookOutput, " \n");
8379 } else if (bookOutput[0] != NULLCHAR) {
8380 /* All of book output has arrived; display it */
8381 char *p = bookOutput;
8382 while (*p != NULLCHAR) {
8383 if (*p == '\t') *p = ' ';
8386 DisplayInformation(bookOutput);
8387 bookRequested = FALSE;
8388 /* Fall through to parse the current output */
8393 * Look for machine move.
8395 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8396 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8398 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8399 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8400 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8401 stalledEngine = cps;
8402 if(appData.ponderNextMove) { // bring opponent out of ponder
8403 if(gameMode == TwoMachinesPlay) {
8404 if(cps->other->pause)
8405 PauseEngine(cps->other);
8407 SendToProgram("easy\n", cps->other);
8414 /* This method is only useful on engines that support ping */
8415 if (cps->lastPing != cps->lastPong) {
8416 if (gameMode == BeginningOfGame) {
8417 /* Extra move from before last new; ignore */
8418 if (appData.debugMode) {
8419 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8422 if (appData.debugMode) {
8423 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8424 cps->which, gameMode);
8427 SendToProgram("undo\n", cps);
8433 case BeginningOfGame:
8434 /* Extra move from before last reset; ignore */
8435 if (appData.debugMode) {
8436 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8443 /* Extra move after we tried to stop. The mode test is
8444 not a reliable way of detecting this problem, but it's
8445 the best we can do on engines that don't support ping.
8447 if (appData.debugMode) {
8448 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8449 cps->which, gameMode);
8451 SendToProgram("undo\n", cps);
8454 case MachinePlaysWhite:
8455 case IcsPlayingWhite:
8456 machineWhite = TRUE;
8459 case MachinePlaysBlack:
8460 case IcsPlayingBlack:
8461 machineWhite = FALSE;
8464 case TwoMachinesPlay:
8465 machineWhite = (cps->twoMachinesColor[0] == 'w');
8468 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8469 if (appData.debugMode) {
8471 "Ignoring move out of turn by %s, gameMode %d"
8472 ", forwardMost %d\n",
8473 cps->which, gameMode, forwardMostMove);
8478 if(cps->alphaRank) AlphaRank(machineMove, 4);
8480 // [HGM] lion: (some very limited) support for Alien protocol
8482 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8483 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8485 } else if(firstLeg[0]) { // there was a previous leg;
8486 // only support case where same piece makes two step (and don't even test that!)
8487 char buf[20], *p = machineMove+1, *q = buf+1, f;
8488 safeStrCpy(buf, machineMove, 20);
8489 while(isdigit(*q)) q++; // find start of to-square
8490 safeStrCpy(machineMove, firstLeg, 20);
8491 while(isdigit(*p)) p++;
8492 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8493 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8494 firstLeg[0] = NULLCHAR;
8497 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8498 &fromX, &fromY, &toX, &toY, &promoChar)) {
8499 /* Machine move could not be parsed; ignore it. */
8500 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8501 machineMove, _(cps->which));
8502 DisplayMoveError(buf1);
8503 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8504 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8505 if (gameMode == TwoMachinesPlay) {
8506 GameEnds(machineWhite ? BlackWins : WhiteWins,
8512 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8513 /* So we have to redo legality test with true e.p. status here, */
8514 /* to make sure an illegal e.p. capture does not slip through, */
8515 /* to cause a forfeit on a justified illegal-move complaint */
8516 /* of the opponent. */
8517 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8519 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8520 fromY, fromX, toY, toX, promoChar);
8521 if(moveType == IllegalMove) {
8522 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8523 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8524 GameEnds(machineWhite ? BlackWins : WhiteWins,
8527 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8528 /* [HGM] Kludge to handle engines that send FRC-style castling
8529 when they shouldn't (like TSCP-Gothic) */
8531 case WhiteASideCastleFR:
8532 case BlackASideCastleFR:
8534 currentMoveString[2]++;
8536 case WhiteHSideCastleFR:
8537 case BlackHSideCastleFR:
8539 currentMoveString[2]--;
8541 default: ; // nothing to do, but suppresses warning of pedantic compilers
8544 hintRequested = FALSE;
8545 lastHint[0] = NULLCHAR;
8546 bookRequested = FALSE;
8547 /* Program may be pondering now */
8548 cps->maybeThinking = TRUE;
8549 if (cps->sendTime == 2) cps->sendTime = 1;
8550 if (cps->offeredDraw) cps->offeredDraw--;
8552 /* [AS] Save move info*/
8553 pvInfoList[ forwardMostMove ].score = programStats.score;
8554 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8555 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8557 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8559 /* Test suites abort the 'game' after one move */
8560 if(*appData.finger) {
8562 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8563 if(!f) f = fopen(appData.finger, "w");
8564 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8565 else { DisplayFatalError("Bad output file", errno, 0); return; }
8567 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8570 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8571 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8574 while( count < adjudicateLossPlies ) {
8575 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8578 score = -score; /* Flip score for winning side */
8581 if( score > adjudicateLossThreshold ) {
8588 if( count >= adjudicateLossPlies ) {
8589 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8591 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8592 "Xboard adjudication",
8599 if(Adjudicate(cps)) {
8600 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8601 return; // [HGM] adjudicate: for all automatic game ends
8605 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8607 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8608 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8610 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8612 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8614 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8615 char buf[3*MSG_SIZ];
8617 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8618 programStats.score / 100.,
8620 programStats.time / 100.,
8621 (unsigned int)programStats.nodes,
8622 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8623 programStats.movelist);
8625 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8630 /* [AS] Clear stats for next move */
8631 ClearProgramStats();
8632 thinkOutput[0] = NULLCHAR;
8633 hiddenThinkOutputState = 0;
8636 if (gameMode == TwoMachinesPlay) {
8637 /* [HGM] relaying draw offers moved to after reception of move */
8638 /* and interpreting offer as claim if it brings draw condition */
8639 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8640 SendToProgram("draw\n", cps->other);
8642 if (cps->other->sendTime) {
8643 SendTimeRemaining(cps->other,
8644 cps->other->twoMachinesColor[0] == 'w');
8646 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8647 if (firstMove && !bookHit) {
8649 if (cps->other->useColors) {
8650 SendToProgram(cps->other->twoMachinesColor, cps->other);
8652 SendToProgram("go\n", cps->other);
8654 cps->other->maybeThinking = TRUE;
8657 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8659 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8661 if (!pausing && appData.ringBellAfterMoves) {
8662 if(!roar) RingBell();
8666 * Reenable menu items that were disabled while
8667 * machine was thinking
8669 if (gameMode != TwoMachinesPlay)
8670 SetUserThinkingEnables();
8672 // [HGM] book: after book hit opponent has received move and is now in force mode
8673 // force the book reply into it, and then fake that it outputted this move by jumping
8674 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8676 static char bookMove[MSG_SIZ]; // a bit generous?
8678 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8679 strcat(bookMove, bookHit);
8682 programStats.nodes = programStats.depth = programStats.time =
8683 programStats.score = programStats.got_only_move = 0;
8684 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8686 if(cps->lastPing != cps->lastPong) {
8687 savedMessage = message; // args for deferred call
8689 ScheduleDelayedEvent(DeferredBookMove, 10);
8698 /* Set special modes for chess engines. Later something general
8699 * could be added here; for now there is just one kludge feature,
8700 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8701 * when "xboard" is given as an interactive command.
8703 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8704 cps->useSigint = FALSE;
8705 cps->useSigterm = FALSE;
8707 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8708 ParseFeatures(message+8, cps);
8709 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8712 if (!strncmp(message, "setup ", 6) &&
8713 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8714 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8715 ) { // [HGM] allow first engine to define opening position
8716 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8717 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8719 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8720 if(startedFromSetupPosition) return;
8721 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8723 while(message[s] && message[s++] != ' ');
8724 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8725 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8726 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8727 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8728 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8729 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8732 ParseFEN(boards[0], &dummy, message+s, FALSE);
8733 DrawPosition(TRUE, boards[0]);
8734 startedFromSetupPosition = TRUE;
8737 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8738 * want this, I was asked to put it in, and obliged.
8740 if (!strncmp(message, "setboard ", 9)) {
8741 Board initial_position;
8743 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8745 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8746 DisplayError(_("Bad FEN received from engine"), 0);
8750 CopyBoard(boards[0], initial_position);
8751 initialRulePlies = FENrulePlies;
8752 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8753 else gameMode = MachinePlaysBlack;
8754 DrawPosition(FALSE, boards[currentMove]);
8760 * Look for communication commands
8762 if (!strncmp(message, "telluser ", 9)) {
8763 if(message[9] == '\\' && message[10] == '\\')
8764 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8766 DisplayNote(message + 9);
8769 if (!strncmp(message, "tellusererror ", 14)) {
8771 if(message[14] == '\\' && message[15] == '\\')
8772 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8774 DisplayError(message + 14, 0);
8777 if (!strncmp(message, "tellopponent ", 13)) {
8778 if (appData.icsActive) {
8780 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8784 DisplayNote(message + 13);
8788 if (!strncmp(message, "tellothers ", 11)) {
8789 if (appData.icsActive) {
8791 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8794 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8797 if (!strncmp(message, "tellall ", 8)) {
8798 if (appData.icsActive) {
8800 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8804 DisplayNote(message + 8);
8808 if (strncmp(message, "warning", 7) == 0) {
8809 /* Undocumented feature, use tellusererror in new code */
8810 DisplayError(message, 0);
8813 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8814 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8815 strcat(realname, " query");
8816 AskQuestion(realname, buf2, buf1, cps->pr);
8819 /* Commands from the engine directly to ICS. We don't allow these to be
8820 * sent until we are logged on. Crafty kibitzes have been known to
8821 * interfere with the login process.
8824 if (!strncmp(message, "tellics ", 8)) {
8825 SendToICS(message + 8);
8829 if (!strncmp(message, "tellicsnoalias ", 15)) {
8830 SendToICS(ics_prefix);
8831 SendToICS(message + 15);
8835 /* The following are for backward compatibility only */
8836 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8837 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8838 SendToICS(ics_prefix);
8844 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8845 if(initPing == cps->lastPong) {
8846 if(gameInfo.variant == VariantUnknown) {
8847 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8848 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8849 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8855 if(!strncmp(message, "highlight ", 10)) {
8856 if(appData.testLegality && appData.markers) return;
8857 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8860 if(!strncmp(message, "click ", 6)) {
8861 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8862 if(appData.testLegality || !appData.oneClick) return;
8863 sscanf(message+6, "%c%d%c", &f, &y, &c);
8864 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8865 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8866 x = x*squareSize + (x+1)*lineGap + squareSize/2;
8867 y = y*squareSize + (y+1)*lineGap + squareSize/2;
8868 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8869 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8870 LeftClick(Release, lastLeftX, lastLeftY);
8871 controlKey = (c == ',');
8872 LeftClick(Press, x, y);
8873 LeftClick(Release, x, y);
8874 first.highlight = f;
8878 * If the move is illegal, cancel it and redraw the board.
8879 * Also deal with other error cases. Matching is rather loose
8880 * here to accommodate engines written before the spec.
8882 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8883 strncmp(message, "Error", 5) == 0) {
8884 if (StrStr(message, "name") ||
8885 StrStr(message, "rating") || StrStr(message, "?") ||
8886 StrStr(message, "result") || StrStr(message, "board") ||
8887 StrStr(message, "bk") || StrStr(message, "computer") ||
8888 StrStr(message, "variant") || StrStr(message, "hint") ||
8889 StrStr(message, "random") || StrStr(message, "depth") ||
8890 StrStr(message, "accepted")) {
8893 if (StrStr(message, "protover")) {
8894 /* Program is responding to input, so it's apparently done
8895 initializing, and this error message indicates it is
8896 protocol version 1. So we don't need to wait any longer
8897 for it to initialize and send feature commands. */
8898 FeatureDone(cps, 1);
8899 cps->protocolVersion = 1;
8902 cps->maybeThinking = FALSE;
8904 if (StrStr(message, "draw")) {
8905 /* Program doesn't have "draw" command */
8906 cps->sendDrawOffers = 0;
8909 if (cps->sendTime != 1 &&
8910 (StrStr(message, "time") || StrStr(message, "otim"))) {
8911 /* Program apparently doesn't have "time" or "otim" command */
8915 if (StrStr(message, "analyze")) {
8916 cps->analysisSupport = FALSE;
8917 cps->analyzing = FALSE;
8918 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8919 EditGameEvent(); // [HGM] try to preserve loaded game
8920 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8921 DisplayError(buf2, 0);
8924 if (StrStr(message, "(no matching move)st")) {
8925 /* Special kludge for GNU Chess 4 only */
8926 cps->stKludge = TRUE;
8927 SendTimeControl(cps, movesPerSession, timeControl,
8928 timeIncrement, appData.searchDepth,
8932 if (StrStr(message, "(no matching move)sd")) {
8933 /* Special kludge for GNU Chess 4 only */
8934 cps->sdKludge = TRUE;
8935 SendTimeControl(cps, movesPerSession, timeControl,
8936 timeIncrement, appData.searchDepth,
8940 if (!StrStr(message, "llegal")) {
8943 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8944 gameMode == IcsIdle) return;
8945 if (forwardMostMove <= backwardMostMove) return;
8946 if (pausing) PauseEvent();
8947 if(appData.forceIllegal) {
8948 // [HGM] illegal: machine refused move; force position after move into it
8949 SendToProgram("force\n", cps);
8950 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8951 // we have a real problem now, as SendBoard will use the a2a3 kludge
8952 // when black is to move, while there might be nothing on a2 or black
8953 // might already have the move. So send the board as if white has the move.
8954 // But first we must change the stm of the engine, as it refused the last move
8955 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8956 if(WhiteOnMove(forwardMostMove)) {
8957 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8958 SendBoard(cps, forwardMostMove); // kludgeless board
8960 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8961 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8962 SendBoard(cps, forwardMostMove+1); // kludgeless board
8964 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8965 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8966 gameMode == TwoMachinesPlay)
8967 SendToProgram("go\n", cps);
8970 if (gameMode == PlayFromGameFile) {
8971 /* Stop reading this game file */
8972 gameMode = EditGame;
8975 /* [HGM] illegal-move claim should forfeit game when Xboard */
8976 /* only passes fully legal moves */
8977 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8978 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8979 "False illegal-move claim", GE_XBOARD );
8980 return; // do not take back move we tested as valid
8982 currentMove = forwardMostMove-1;
8983 DisplayMove(currentMove-1); /* before DisplayMoveError */
8984 SwitchClocks(forwardMostMove-1); // [HGM] race
8985 DisplayBothClocks();
8986 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8987 parseList[currentMove], _(cps->which));
8988 DisplayMoveError(buf1);
8989 DrawPosition(FALSE, boards[currentMove]);
8991 SetUserThinkingEnables();
8994 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8995 /* Program has a broken "time" command that
8996 outputs a string not ending in newline.
9002 * If chess program startup fails, exit with an error message.
9003 * Attempts to recover here are futile. [HGM] Well, we try anyway
9005 if ((StrStr(message, "unknown host") != NULL)
9006 || (StrStr(message, "No remote directory") != NULL)
9007 || (StrStr(message, "not found") != NULL)
9008 || (StrStr(message, "No such file") != NULL)
9009 || (StrStr(message, "can't alloc") != NULL)
9010 || (StrStr(message, "Permission denied") != NULL)) {
9012 cps->maybeThinking = FALSE;
9013 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9014 _(cps->which), cps->program, cps->host, message);
9015 RemoveInputSource(cps->isr);
9016 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9017 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9018 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9024 * Look for hint output
9026 if (sscanf(message, "Hint: %s", buf1) == 1) {
9027 if (cps == &first && hintRequested) {
9028 hintRequested = FALSE;
9029 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9030 &fromX, &fromY, &toX, &toY, &promoChar)) {
9031 (void) CoordsToAlgebraic(boards[forwardMostMove],
9032 PosFlags(forwardMostMove),
9033 fromY, fromX, toY, toX, promoChar, buf1);
9034 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9035 DisplayInformation(buf2);
9037 /* Hint move could not be parsed!? */
9038 snprintf(buf2, sizeof(buf2),
9039 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9040 buf1, _(cps->which));
9041 DisplayError(buf2, 0);
9044 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9050 * Ignore other messages if game is not in progress
9052 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9053 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9056 * look for win, lose, draw, or draw offer
9058 if (strncmp(message, "1-0", 3) == 0) {
9059 char *p, *q, *r = "";
9060 p = strchr(message, '{');
9068 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9070 } else if (strncmp(message, "0-1", 3) == 0) {
9071 char *p, *q, *r = "";
9072 p = strchr(message, '{');
9080 /* Kludge for Arasan 4.1 bug */
9081 if (strcmp(r, "Black resigns") == 0) {
9082 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9085 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9087 } else if (strncmp(message, "1/2", 3) == 0) {
9088 char *p, *q, *r = "";
9089 p = strchr(message, '{');
9098 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9101 } else if (strncmp(message, "White resign", 12) == 0) {
9102 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9104 } else if (strncmp(message, "Black resign", 12) == 0) {
9105 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9107 } else if (strncmp(message, "White matches", 13) == 0 ||
9108 strncmp(message, "Black matches", 13) == 0 ) {
9109 /* [HGM] ignore GNUShogi noises */
9111 } else if (strncmp(message, "White", 5) == 0 &&
9112 message[5] != '(' &&
9113 StrStr(message, "Black") == NULL) {
9114 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9116 } else if (strncmp(message, "Black", 5) == 0 &&
9117 message[5] != '(') {
9118 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9120 } else if (strcmp(message, "resign") == 0 ||
9121 strcmp(message, "computer resigns") == 0) {
9123 case MachinePlaysBlack:
9124 case IcsPlayingBlack:
9125 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9127 case MachinePlaysWhite:
9128 case IcsPlayingWhite:
9129 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9131 case TwoMachinesPlay:
9132 if (cps->twoMachinesColor[0] == 'w')
9133 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9135 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9142 } else if (strncmp(message, "opponent mates", 14) == 0) {
9144 case MachinePlaysBlack:
9145 case IcsPlayingBlack:
9146 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9148 case MachinePlaysWhite:
9149 case IcsPlayingWhite:
9150 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9152 case TwoMachinesPlay:
9153 if (cps->twoMachinesColor[0] == 'w')
9154 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9156 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9163 } else if (strncmp(message, "computer mates", 14) == 0) {
9165 case MachinePlaysBlack:
9166 case IcsPlayingBlack:
9167 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9169 case MachinePlaysWhite:
9170 case IcsPlayingWhite:
9171 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9173 case TwoMachinesPlay:
9174 if (cps->twoMachinesColor[0] == 'w')
9175 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9177 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9184 } else if (strncmp(message, "checkmate", 9) == 0) {
9185 if (WhiteOnMove(forwardMostMove)) {
9186 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9188 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9191 } else if (strstr(message, "Draw") != NULL ||
9192 strstr(message, "game is a draw") != NULL) {
9193 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9195 } else if (strstr(message, "offer") != NULL &&
9196 strstr(message, "draw") != NULL) {
9198 if (appData.zippyPlay && first.initDone) {
9199 /* Relay offer to ICS */
9200 SendToICS(ics_prefix);
9201 SendToICS("draw\n");
9204 cps->offeredDraw = 2; /* valid until this engine moves twice */
9205 if (gameMode == TwoMachinesPlay) {
9206 if (cps->other->offeredDraw) {
9207 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9208 /* [HGM] in two-machine mode we delay relaying draw offer */
9209 /* until after we also have move, to see if it is really claim */
9211 } else if (gameMode == MachinePlaysWhite ||
9212 gameMode == MachinePlaysBlack) {
9213 if (userOfferedDraw) {
9214 DisplayInformation(_("Machine accepts your draw offer"));
9215 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9217 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9224 * Look for thinking output
9226 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9227 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9229 int plylev, mvleft, mvtot, curscore, time;
9230 char mvname[MOVE_LEN];
9234 int prefixHint = FALSE;
9235 mvname[0] = NULLCHAR;
9238 case MachinePlaysBlack:
9239 case IcsPlayingBlack:
9240 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9242 case MachinePlaysWhite:
9243 case IcsPlayingWhite:
9244 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9249 case IcsObserving: /* [DM] icsEngineAnalyze */
9250 if (!appData.icsEngineAnalyze) ignore = TRUE;
9252 case TwoMachinesPlay:
9253 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9263 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9265 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9266 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9268 if (plyext != ' ' && plyext != '\t') {
9272 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9273 if( cps->scoreIsAbsolute &&
9274 ( gameMode == MachinePlaysBlack ||
9275 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9276 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9277 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9278 !WhiteOnMove(currentMove)
9281 curscore = -curscore;
9284 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9286 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9289 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9290 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9291 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9292 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9293 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9294 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9298 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9299 DisplayError(_("failed writing PV"), 0);
9302 tempStats.depth = plylev;
9303 tempStats.nodes = nodes;
9304 tempStats.time = time;
9305 tempStats.score = curscore;
9306 tempStats.got_only_move = 0;
9308 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9311 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9312 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9313 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9314 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9315 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9316 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9317 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9318 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9321 /* Buffer overflow protection */
9322 if (pv[0] != NULLCHAR) {
9323 if (strlen(pv) >= sizeof(tempStats.movelist)
9324 && appData.debugMode) {
9326 "PV is too long; using the first %u bytes.\n",
9327 (unsigned) sizeof(tempStats.movelist) - 1);
9330 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9332 sprintf(tempStats.movelist, " no PV\n");
9335 if (tempStats.seen_stat) {
9336 tempStats.ok_to_send = 1;
9339 if (strchr(tempStats.movelist, '(') != NULL) {
9340 tempStats.line_is_book = 1;
9341 tempStats.nr_moves = 0;
9342 tempStats.moves_left = 0;
9344 tempStats.line_is_book = 0;
9347 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9348 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9350 SendProgramStatsToFrontend( cps, &tempStats );
9353 [AS] Protect the thinkOutput buffer from overflow... this
9354 is only useful if buf1 hasn't overflowed first!
9356 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9358 (gameMode == TwoMachinesPlay ?
9359 ToUpper(cps->twoMachinesColor[0]) : ' '),
9360 ((double) curscore) / 100.0,
9361 prefixHint ? lastHint : "",
9362 prefixHint ? " " : "" );
9364 if( buf1[0] != NULLCHAR ) {
9365 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9367 if( strlen(pv) > max_len ) {
9368 if( appData.debugMode) {
9369 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9371 pv[max_len+1] = '\0';
9374 strcat( thinkOutput, pv);
9377 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9378 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9379 DisplayMove(currentMove - 1);
9383 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9384 /* crafty (9.25+) says "(only move) <move>"
9385 * if there is only 1 legal move
9387 sscanf(p, "(only move) %s", buf1);
9388 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9389 sprintf(programStats.movelist, "%s (only move)", buf1);
9390 programStats.depth = 1;
9391 programStats.nr_moves = 1;
9392 programStats.moves_left = 1;
9393 programStats.nodes = 1;
9394 programStats.time = 1;
9395 programStats.got_only_move = 1;
9397 /* Not really, but we also use this member to
9398 mean "line isn't going to change" (Crafty
9399 isn't searching, so stats won't change) */
9400 programStats.line_is_book = 1;
9402 SendProgramStatsToFrontend( cps, &programStats );
9404 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9405 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9406 DisplayMove(currentMove - 1);
9409 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9410 &time, &nodes, &plylev, &mvleft,
9411 &mvtot, mvname) >= 5) {
9412 /* The stat01: line is from Crafty (9.29+) in response
9413 to the "." command */
9414 programStats.seen_stat = 1;
9415 cps->maybeThinking = TRUE;
9417 if (programStats.got_only_move || !appData.periodicUpdates)
9420 programStats.depth = plylev;
9421 programStats.time = time;
9422 programStats.nodes = nodes;
9423 programStats.moves_left = mvleft;
9424 programStats.nr_moves = mvtot;
9425 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9426 programStats.ok_to_send = 1;
9427 programStats.movelist[0] = '\0';
9429 SendProgramStatsToFrontend( cps, &programStats );
9433 } else if (strncmp(message,"++",2) == 0) {
9434 /* Crafty 9.29+ outputs this */
9435 programStats.got_fail = 2;
9438 } else if (strncmp(message,"--",2) == 0) {
9439 /* Crafty 9.29+ outputs this */
9440 programStats.got_fail = 1;
9443 } else if (thinkOutput[0] != NULLCHAR &&
9444 strncmp(message, " ", 4) == 0) {
9445 unsigned message_len;
9448 while (*p && *p == ' ') p++;
9450 message_len = strlen( p );
9452 /* [AS] Avoid buffer overflow */
9453 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9454 strcat(thinkOutput, " ");
9455 strcat(thinkOutput, p);
9458 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9459 strcat(programStats.movelist, " ");
9460 strcat(programStats.movelist, p);
9463 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9464 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9465 DisplayMove(currentMove - 1);
9473 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9474 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9476 ChessProgramStats cpstats;
9478 if (plyext != ' ' && plyext != '\t') {
9482 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9483 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9484 curscore = -curscore;
9487 cpstats.depth = plylev;
9488 cpstats.nodes = nodes;
9489 cpstats.time = time;
9490 cpstats.score = curscore;
9491 cpstats.got_only_move = 0;
9492 cpstats.movelist[0] = '\0';
9494 if (buf1[0] != NULLCHAR) {
9495 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9498 cpstats.ok_to_send = 0;
9499 cpstats.line_is_book = 0;
9500 cpstats.nr_moves = 0;
9501 cpstats.moves_left = 0;
9503 SendProgramStatsToFrontend( cps, &cpstats );
9510 /* Parse a game score from the character string "game", and
9511 record it as the history of the current game. The game
9512 score is NOT assumed to start from the standard position.
9513 The display is not updated in any way.
9516 ParseGameHistory (char *game)
9519 int fromX, fromY, toX, toY, boardIndex;
9524 if (appData.debugMode)
9525 fprintf(debugFP, "Parsing game history: %s\n", game);
9527 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9528 gameInfo.site = StrSave(appData.icsHost);
9529 gameInfo.date = PGNDate();
9530 gameInfo.round = StrSave("-");
9532 /* Parse out names of players */
9533 while (*game == ' ') game++;
9535 while (*game != ' ') *p++ = *game++;
9537 gameInfo.white = StrSave(buf);
9538 while (*game == ' ') game++;
9540 while (*game != ' ' && *game != '\n') *p++ = *game++;
9542 gameInfo.black = StrSave(buf);
9545 boardIndex = blackPlaysFirst ? 1 : 0;
9548 yyboardindex = boardIndex;
9549 moveType = (ChessMove) Myylex();
9551 case IllegalMove: /* maybe suicide chess, etc. */
9552 if (appData.debugMode) {
9553 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9554 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9555 setbuf(debugFP, NULL);
9557 case WhitePromotion:
9558 case BlackPromotion:
9559 case WhiteNonPromotion:
9560 case BlackNonPromotion:
9563 case WhiteCapturesEnPassant:
9564 case BlackCapturesEnPassant:
9565 case WhiteKingSideCastle:
9566 case WhiteQueenSideCastle:
9567 case BlackKingSideCastle:
9568 case BlackQueenSideCastle:
9569 case WhiteKingSideCastleWild:
9570 case WhiteQueenSideCastleWild:
9571 case BlackKingSideCastleWild:
9572 case BlackQueenSideCastleWild:
9574 case WhiteHSideCastleFR:
9575 case WhiteASideCastleFR:
9576 case BlackHSideCastleFR:
9577 case BlackASideCastleFR:
9579 fromX = currentMoveString[0] - AAA;
9580 fromY = currentMoveString[1] - ONE;
9581 toX = currentMoveString[2] - AAA;
9582 toY = currentMoveString[3] - ONE;
9583 promoChar = currentMoveString[4];
9587 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9588 fromX = moveType == WhiteDrop ?
9589 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9590 (int) CharToPiece(ToLower(currentMoveString[0]));
9592 toX = currentMoveString[2] - AAA;
9593 toY = currentMoveString[3] - ONE;
9594 promoChar = NULLCHAR;
9598 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9599 if (appData.debugMode) {
9600 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9601 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9602 setbuf(debugFP, NULL);
9604 DisplayError(buf, 0);
9606 case ImpossibleMove:
9608 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9609 if (appData.debugMode) {
9610 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9611 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9612 setbuf(debugFP, NULL);
9614 DisplayError(buf, 0);
9617 if (boardIndex < backwardMostMove) {
9618 /* Oops, gap. How did that happen? */
9619 DisplayError(_("Gap in move list"), 0);
9622 backwardMostMove = blackPlaysFirst ? 1 : 0;
9623 if (boardIndex > forwardMostMove) {
9624 forwardMostMove = boardIndex;
9628 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9629 strcat(parseList[boardIndex-1], " ");
9630 strcat(parseList[boardIndex-1], yy_text);
9642 case GameUnfinished:
9643 if (gameMode == IcsExamining) {
9644 if (boardIndex < backwardMostMove) {
9645 /* Oops, gap. How did that happen? */
9648 backwardMostMove = blackPlaysFirst ? 1 : 0;
9651 gameInfo.result = moveType;
9652 p = strchr(yy_text, '{');
9653 if (p == NULL) p = strchr(yy_text, '(');
9656 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9658 q = strchr(p, *p == '{' ? '}' : ')');
9659 if (q != NULL) *q = NULLCHAR;
9662 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9663 gameInfo.resultDetails = StrSave(p);
9666 if (boardIndex >= forwardMostMove &&
9667 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9668 backwardMostMove = blackPlaysFirst ? 1 : 0;
9671 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9672 fromY, fromX, toY, toX, promoChar,
9673 parseList[boardIndex]);
9674 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9675 /* currentMoveString is set as a side-effect of yylex */
9676 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9677 strcat(moveList[boardIndex], "\n");
9679 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9680 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9686 if(gameInfo.variant != VariantShogi)
9687 strcat(parseList[boardIndex - 1], "+");
9691 strcat(parseList[boardIndex - 1], "#");
9698 /* Apply a move to the given board */
9700 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9702 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9703 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9705 /* [HGM] compute & store e.p. status and castling rights for new position */
9706 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9708 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9709 oldEP = (signed char)board[EP_STATUS];
9710 board[EP_STATUS] = EP_NONE;
9712 if (fromY == DROP_RANK) {
9714 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9715 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9718 piece = board[toY][toX] = (ChessSquare) fromX;
9723 if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9724 victim = board[killY][killX],
9725 board[killY][killX] = EmptySquare,
9726 board[EP_STATUS] = EP_CAPTURE;
9728 if( board[toY][toX] != EmptySquare ) {
9729 board[EP_STATUS] = EP_CAPTURE;
9730 if( (fromX != toX || fromY != toY) && // not igui!
9731 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9732 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
9733 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9737 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9738 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9739 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9741 if( board[fromY][fromX] == WhitePawn ) {
9742 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9743 board[EP_STATUS] = EP_PAWN_MOVE;
9745 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9746 gameInfo.variant != VariantBerolina || toX < fromX)
9747 board[EP_STATUS] = toX | berolina;
9748 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9749 gameInfo.variant != VariantBerolina || toX > fromX)
9750 board[EP_STATUS] = toX;
9753 if( board[fromY][fromX] == BlackPawn ) {
9754 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9755 board[EP_STATUS] = EP_PAWN_MOVE;
9756 if( toY-fromY== -2) {
9757 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9758 gameInfo.variant != VariantBerolina || toX < fromX)
9759 board[EP_STATUS] = toX | berolina;
9760 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9761 gameInfo.variant != VariantBerolina || toX > fromX)
9762 board[EP_STATUS] = toX;
9766 for(i=0; i<nrCastlingRights; i++) {
9767 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9768 board[CASTLING][i] == toX && castlingRank[i] == toY
9769 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9772 if(gameInfo.variant == VariantSChess) { // update virginity
9773 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9774 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9775 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9776 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9779 if (fromX == toX && fromY == toY) return;
9781 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9782 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9783 if(gameInfo.variant == VariantKnightmate)
9784 king += (int) WhiteUnicorn - (int) WhiteKing;
9786 /* Code added by Tord: */
9787 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9788 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9789 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9790 board[fromY][fromX] = EmptySquare;
9791 board[toY][toX] = EmptySquare;
9792 if((toX > fromX) != (piece == WhiteRook)) {
9793 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9795 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9797 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9798 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9799 board[fromY][fromX] = EmptySquare;
9800 board[toY][toX] = EmptySquare;
9801 if((toX > fromX) != (piece == BlackRook)) {
9802 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9804 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9806 /* End of code added by Tord */
9808 } else if (board[fromY][fromX] == king
9809 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9810 && toY == fromY && toX > fromX+1) {
9811 board[fromY][fromX] = EmptySquare;
9812 board[toY][toX] = king;
9813 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9814 board[fromY][BOARD_RGHT-1] = EmptySquare;
9815 } else if (board[fromY][fromX] == king
9816 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9817 && toY == fromY && toX < fromX-1) {
9818 board[fromY][fromX] = EmptySquare;
9819 board[toY][toX] = king;
9820 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9821 board[fromY][BOARD_LEFT] = EmptySquare;
9822 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9823 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9824 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9826 /* white pawn promotion */
9827 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9828 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9829 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9830 board[fromY][fromX] = EmptySquare;
9831 } else if ((fromY >= BOARD_HEIGHT>>1)
9832 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9834 && gameInfo.variant != VariantXiangqi
9835 && gameInfo.variant != VariantBerolina
9836 && (board[fromY][fromX] == WhitePawn)
9837 && (board[toY][toX] == EmptySquare)) {
9838 board[fromY][fromX] = EmptySquare;
9839 board[toY][toX] = WhitePawn;
9840 captured = board[toY - 1][toX];
9841 board[toY - 1][toX] = EmptySquare;
9842 } else if ((fromY == BOARD_HEIGHT-4)
9844 && gameInfo.variant == VariantBerolina
9845 && (board[fromY][fromX] == WhitePawn)
9846 && (board[toY][toX] == EmptySquare)) {
9847 board[fromY][fromX] = EmptySquare;
9848 board[toY][toX] = WhitePawn;
9849 if(oldEP & EP_BEROLIN_A) {
9850 captured = board[fromY][fromX-1];
9851 board[fromY][fromX-1] = EmptySquare;
9852 }else{ captured = board[fromY][fromX+1];
9853 board[fromY][fromX+1] = EmptySquare;
9855 } else if (board[fromY][fromX] == king
9856 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9857 && toY == fromY && toX > fromX+1) {
9858 board[fromY][fromX] = EmptySquare;
9859 board[toY][toX] = king;
9860 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9861 board[fromY][BOARD_RGHT-1] = EmptySquare;
9862 } else if (board[fromY][fromX] == king
9863 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9864 && toY == fromY && toX < fromX-1) {
9865 board[fromY][fromX] = EmptySquare;
9866 board[toY][toX] = king;
9867 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9868 board[fromY][BOARD_LEFT] = EmptySquare;
9869 } else if (fromY == 7 && fromX == 3
9870 && board[fromY][fromX] == BlackKing
9871 && toY == 7 && toX == 5) {
9872 board[fromY][fromX] = EmptySquare;
9873 board[toY][toX] = BlackKing;
9874 board[fromY][7] = EmptySquare;
9875 board[toY][4] = BlackRook;
9876 } else if (fromY == 7 && fromX == 3
9877 && board[fromY][fromX] == BlackKing
9878 && toY == 7 && toX == 1) {
9879 board[fromY][fromX] = EmptySquare;
9880 board[toY][toX] = BlackKing;
9881 board[fromY][0] = EmptySquare;
9882 board[toY][2] = BlackRook;
9883 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9884 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9885 && toY < promoRank && promoChar
9887 /* black pawn promotion */
9888 board[toY][toX] = CharToPiece(ToLower(promoChar));
9889 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9890 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9891 board[fromY][fromX] = EmptySquare;
9892 } else if ((fromY < BOARD_HEIGHT>>1)
9893 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9895 && gameInfo.variant != VariantXiangqi
9896 && gameInfo.variant != VariantBerolina
9897 && (board[fromY][fromX] == BlackPawn)
9898 && (board[toY][toX] == EmptySquare)) {
9899 board[fromY][fromX] = EmptySquare;
9900 board[toY][toX] = BlackPawn;
9901 captured = board[toY + 1][toX];
9902 board[toY + 1][toX] = EmptySquare;
9903 } else if ((fromY == 3)
9905 && gameInfo.variant == VariantBerolina
9906 && (board[fromY][fromX] == BlackPawn)
9907 && (board[toY][toX] == EmptySquare)) {
9908 board[fromY][fromX] = EmptySquare;
9909 board[toY][toX] = BlackPawn;
9910 if(oldEP & EP_BEROLIN_A) {
9911 captured = board[fromY][fromX-1];
9912 board[fromY][fromX-1] = EmptySquare;
9913 }else{ captured = board[fromY][fromX+1];
9914 board[fromY][fromX+1] = EmptySquare;
9917 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9918 board[fromY][fromX] = EmptySquare;
9919 board[toY][toX] = piece;
9923 if (gameInfo.holdingsWidth != 0) {
9925 /* !!A lot more code needs to be written to support holdings */
9926 /* [HGM] OK, so I have written it. Holdings are stored in the */
9927 /* penultimate board files, so they are automaticlly stored */
9928 /* in the game history. */
9929 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9930 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9931 /* Delete from holdings, by decreasing count */
9932 /* and erasing image if necessary */
9933 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9934 if(p < (int) BlackPawn) { /* white drop */
9935 p -= (int)WhitePawn;
9936 p = PieceToNumber((ChessSquare)p);
9937 if(p >= gameInfo.holdingsSize) p = 0;
9938 if(--board[p][BOARD_WIDTH-2] <= 0)
9939 board[p][BOARD_WIDTH-1] = EmptySquare;
9940 if((int)board[p][BOARD_WIDTH-2] < 0)
9941 board[p][BOARD_WIDTH-2] = 0;
9942 } else { /* black drop */
9943 p -= (int)BlackPawn;
9944 p = PieceToNumber((ChessSquare)p);
9945 if(p >= gameInfo.holdingsSize) p = 0;
9946 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9947 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9948 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9949 board[BOARD_HEIGHT-1-p][1] = 0;
9952 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9953 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9954 /* [HGM] holdings: Add to holdings, if holdings exist */
9955 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9956 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9957 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9960 if (p >= (int) BlackPawn) {
9961 p -= (int)BlackPawn;
9962 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9963 /* in Shogi restore piece to its original first */
9964 captured = (ChessSquare) (DEMOTED captured);
9967 p = PieceToNumber((ChessSquare)p);
9968 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9969 board[p][BOARD_WIDTH-2]++;
9970 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9972 p -= (int)WhitePawn;
9973 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9974 captured = (ChessSquare) (DEMOTED captured);
9977 p = PieceToNumber((ChessSquare)p);
9978 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9979 board[BOARD_HEIGHT-1-p][1]++;
9980 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9983 } else if (gameInfo.variant == VariantAtomic) {
9984 if (captured != EmptySquare) {
9986 for (y = toY-1; y <= toY+1; y++) {
9987 for (x = toX-1; x <= toX+1; x++) {
9988 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9989 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9990 board[y][x] = EmptySquare;
9994 board[toY][toX] = EmptySquare;
9997 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9998 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10000 if(promoChar == '+') {
10001 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10002 board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10003 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10004 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10005 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10006 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10007 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10008 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10009 board[toY][toX] = newPiece;
10011 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10012 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10013 // [HGM] superchess: take promotion piece out of holdings
10014 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10015 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10016 if(!--board[k][BOARD_WIDTH-2])
10017 board[k][BOARD_WIDTH-1] = EmptySquare;
10019 if(!--board[BOARD_HEIGHT-1-k][1])
10020 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10025 /* Updates forwardMostMove */
10027 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10029 int x = toX, y = toY;
10030 char *s = parseList[forwardMostMove];
10031 ChessSquare p = boards[forwardMostMove][toY][toX];
10032 // forwardMostMove++; // [HGM] bare: moved downstream
10034 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10035 (void) CoordsToAlgebraic(boards[forwardMostMove],
10036 PosFlags(forwardMostMove),
10037 fromY, fromX, y, x, promoChar,
10039 if(killX >= 0 && killY >= 0)
10040 sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10042 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10043 int timeLeft; static int lastLoadFlag=0; int king, piece;
10044 piece = boards[forwardMostMove][fromY][fromX];
10045 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10046 if(gameInfo.variant == VariantKnightmate)
10047 king += (int) WhiteUnicorn - (int) WhiteKing;
10048 if(forwardMostMove == 0) {
10049 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10050 fprintf(serverMoves, "%s;", UserName());
10051 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10052 fprintf(serverMoves, "%s;", second.tidy);
10053 fprintf(serverMoves, "%s;", first.tidy);
10054 if(gameMode == MachinePlaysWhite)
10055 fprintf(serverMoves, "%s;", UserName());
10056 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10057 fprintf(serverMoves, "%s;", second.tidy);
10058 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10059 lastLoadFlag = loadFlag;
10061 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10062 // print castling suffix
10063 if( toY == fromY && piece == king ) {
10065 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10067 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10070 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10071 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10072 boards[forwardMostMove][toY][toX] == EmptySquare
10073 && fromX != toX && fromY != toY)
10074 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10075 // promotion suffix
10076 if(promoChar != NULLCHAR) {
10077 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10078 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10079 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10080 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10083 char buf[MOVE_LEN*2], *p; int len;
10084 fprintf(serverMoves, "/%d/%d",
10085 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10086 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10087 else timeLeft = blackTimeRemaining/1000;
10088 fprintf(serverMoves, "/%d", timeLeft);
10089 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10090 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10091 if(p = strchr(buf, '=')) *p = NULLCHAR;
10092 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10093 fprintf(serverMoves, "/%s", buf);
10095 fflush(serverMoves);
10098 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10099 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10102 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10103 if (commentList[forwardMostMove+1] != NULL) {
10104 free(commentList[forwardMostMove+1]);
10105 commentList[forwardMostMove+1] = NULL;
10107 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10108 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10109 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10110 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10111 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10112 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10113 adjustedClock = FALSE;
10114 gameInfo.result = GameUnfinished;
10115 if (gameInfo.resultDetails != NULL) {
10116 free(gameInfo.resultDetails);
10117 gameInfo.resultDetails = NULL;
10119 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10120 moveList[forwardMostMove - 1]);
10121 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10127 if(gameInfo.variant != VariantShogi)
10128 strcat(parseList[forwardMostMove - 1], "+");
10132 strcat(parseList[forwardMostMove - 1], "#");
10137 /* Updates currentMove if not pausing */
10139 ShowMove (int fromX, int fromY, int toX, int toY)
10141 int instant = (gameMode == PlayFromGameFile) ?
10142 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10143 if(appData.noGUI) return;
10144 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10146 if (forwardMostMove == currentMove + 1) {
10147 AnimateMove(boards[forwardMostMove - 1],
10148 fromX, fromY, toX, toY);
10151 currentMove = forwardMostMove;
10154 killX = killY = -1; // [HGM] lion: used up
10156 if (instant) return;
10158 DisplayMove(currentMove - 1);
10159 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10160 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10161 SetHighlights(fromX, fromY, toX, toY);
10164 DrawPosition(FALSE, boards[currentMove]);
10165 DisplayBothClocks();
10166 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10170 SendEgtPath (ChessProgramState *cps)
10171 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10172 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10174 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10177 char c, *q = name+1, *r, *s;
10179 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10180 while(*p && *p != ',') *q++ = *p++;
10181 *q++ = ':'; *q = 0;
10182 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10183 strcmp(name, ",nalimov:") == 0 ) {
10184 // take nalimov path from the menu-changeable option first, if it is defined
10185 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10186 SendToProgram(buf,cps); // send egtbpath command for nalimov
10188 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10189 (s = StrStr(appData.egtFormats, name)) != NULL) {
10190 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10191 s = r = StrStr(s, ":") + 1; // beginning of path info
10192 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10193 c = *r; *r = 0; // temporarily null-terminate path info
10194 *--q = 0; // strip of trailig ':' from name
10195 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10197 SendToProgram(buf,cps); // send egtbpath command for this format
10199 if(*p == ',') p++; // read away comma to position for next format name
10204 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10206 int width = 8, height = 8, holdings = 0; // most common sizes
10207 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10208 // correct the deviations default for each variant
10209 if( v == VariantXiangqi ) width = 9, height = 10;
10210 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10211 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10212 if( v == VariantCapablanca || v == VariantCapaRandom ||
10213 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10215 if( v == VariantCourier ) width = 12;
10216 if( v == VariantSuper ) holdings = 8;
10217 if( v == VariantGreat ) width = 10, holdings = 8;
10218 if( v == VariantSChess ) holdings = 7;
10219 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10220 if( v == VariantChuChess) width = 10, height = 10;
10221 if( v == VariantChu ) width = 12, height = 12;
10222 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10223 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10224 holdingsSize >= 0 && holdingsSize != holdings;
10227 char variantError[MSG_SIZ];
10230 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10231 { // returns error message (recognizable by upper-case) if engine does not support the variant
10232 char *p, *variant = VariantName(v);
10233 static char b[MSG_SIZ];
10234 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10235 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10236 holdingsSize, variant); // cook up sized variant name
10237 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10238 if(StrStr(list, b) == NULL) {
10239 // specific sized variant not known, check if general sizing allowed
10240 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10241 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10242 boardWidth, boardHeight, holdingsSize, engine);
10245 /* [HGM] here we really should compare with the maximum supported board size */
10247 } else snprintf(b, MSG_SIZ,"%s", variant);
10248 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10249 p = StrStr(list, b);
10250 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10252 // occurs not at all in list, or only as sub-string
10253 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10254 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10255 int l = strlen(variantError);
10257 while(p != list && p[-1] != ',') p--;
10258 q = strchr(p, ',');
10259 if(q) *q = NULLCHAR;
10260 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10269 InitChessProgram (ChessProgramState *cps, int setup)
10270 /* setup needed to setup FRC opening position */
10272 char buf[MSG_SIZ], *b;
10273 if (appData.noChessProgram) return;
10274 hintRequested = FALSE;
10275 bookRequested = FALSE;
10277 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10278 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10279 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10280 if(cps->memSize) { /* [HGM] memory */
10281 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10282 SendToProgram(buf, cps);
10284 SendEgtPath(cps); /* [HGM] EGT */
10285 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10286 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10287 SendToProgram(buf, cps);
10290 SendToProgram(cps->initString, cps);
10291 if (gameInfo.variant != VariantNormal &&
10292 gameInfo.variant != VariantLoadable
10293 /* [HGM] also send variant if board size non-standard */
10294 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10296 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10297 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10299 DisplayFatalError(variantError, 0, 1);
10303 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10304 SendToProgram(buf, cps);
10306 currentlyInitializedVariant = gameInfo.variant;
10308 /* [HGM] send opening position in FRC to first engine */
10310 SendToProgram("force\n", cps);
10312 /* engine is now in force mode! Set flag to wake it up after first move. */
10313 setboardSpoiledMachineBlack = 1;
10316 if (cps->sendICS) {
10317 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10318 SendToProgram(buf, cps);
10320 cps->maybeThinking = FALSE;
10321 cps->offeredDraw = 0;
10322 if (!appData.icsActive) {
10323 SendTimeControl(cps, movesPerSession, timeControl,
10324 timeIncrement, appData.searchDepth,
10327 if (appData.showThinking
10328 // [HGM] thinking: four options require thinking output to be sent
10329 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10331 SendToProgram("post\n", cps);
10333 SendToProgram("hard\n", cps);
10334 if (!appData.ponderNextMove) {
10335 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10336 it without being sure what state we are in first. "hard"
10337 is not a toggle, so that one is OK.
10339 SendToProgram("easy\n", cps);
10341 if (cps->usePing) {
10342 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10343 SendToProgram(buf, cps);
10345 cps->initDone = TRUE;
10346 ClearEngineOutputPane(cps == &second);
10351 ResendOptions (ChessProgramState *cps)
10352 { // send the stored value of the options
10355 Option *opt = cps->option;
10356 for(i=0; i<cps->nrOptions; i++, opt++) {
10357 switch(opt->type) {
10361 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10364 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10367 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10373 SendToProgram(buf, cps);
10378 StartChessProgram (ChessProgramState *cps)
10383 if (appData.noChessProgram) return;
10384 cps->initDone = FALSE;
10386 if (strcmp(cps->host, "localhost") == 0) {
10387 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10388 } else if (*appData.remoteShell == NULLCHAR) {
10389 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10391 if (*appData.remoteUser == NULLCHAR) {
10392 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10395 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10396 cps->host, appData.remoteUser, cps->program);
10398 err = StartChildProcess(buf, "", &cps->pr);
10402 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10403 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10404 if(cps != &first) return;
10405 appData.noChessProgram = TRUE;
10408 // DisplayFatalError(buf, err, 1);
10409 // cps->pr = NoProc;
10410 // cps->isr = NULL;
10414 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10415 if (cps->protocolVersion > 1) {
10416 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10417 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10418 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10419 cps->comboCnt = 0; // and values of combo boxes
10421 SendToProgram(buf, cps);
10422 if(cps->reload) ResendOptions(cps);
10424 SendToProgram("xboard\n", cps);
10429 TwoMachinesEventIfReady P((void))
10431 static int curMess = 0;
10432 if (first.lastPing != first.lastPong) {
10433 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10434 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10437 if (second.lastPing != second.lastPong) {
10438 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10439 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10442 DisplayMessage("", ""); curMess = 0;
10443 TwoMachinesEvent();
10447 MakeName (char *template)
10451 static char buf[MSG_SIZ];
10455 clock = time((time_t *)NULL);
10456 tm = localtime(&clock);
10458 while(*p++ = *template++) if(p[-1] == '%') {
10459 switch(*template++) {
10460 case 0: *p = 0; return buf;
10461 case 'Y': i = tm->tm_year+1900; break;
10462 case 'y': i = tm->tm_year-100; break;
10463 case 'M': i = tm->tm_mon+1; break;
10464 case 'd': i = tm->tm_mday; break;
10465 case 'h': i = tm->tm_hour; break;
10466 case 'm': i = tm->tm_min; break;
10467 case 's': i = tm->tm_sec; break;
10470 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10476 CountPlayers (char *p)
10479 while(p = strchr(p, '\n')) p++, n++; // count participants
10484 WriteTourneyFile (char *results, FILE *f)
10485 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10486 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10487 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10488 // create a file with tournament description
10489 fprintf(f, "-participants {%s}\n", appData.participants);
10490 fprintf(f, "-seedBase %d\n", appData.seedBase);
10491 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10492 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10493 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10494 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10495 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10496 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10497 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10498 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10499 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10500 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10501 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10502 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10503 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10504 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10505 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10506 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10507 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10508 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10509 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10510 fprintf(f, "-smpCores %d\n", appData.smpCores);
10512 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10514 fprintf(f, "-mps %d\n", appData.movesPerSession);
10515 fprintf(f, "-tc %s\n", appData.timeControl);
10516 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10518 fprintf(f, "-results \"%s\"\n", results);
10523 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10526 Substitute (char *participants, int expunge)
10528 int i, changed, changes=0, nPlayers=0;
10529 char *p, *q, *r, buf[MSG_SIZ];
10530 if(participants == NULL) return;
10531 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10532 r = p = participants; q = appData.participants;
10533 while(*p && *p == *q) {
10534 if(*p == '\n') r = p+1, nPlayers++;
10537 if(*p) { // difference
10538 while(*p && *p++ != '\n');
10539 while(*q && *q++ != '\n');
10540 changed = nPlayers;
10541 changes = 1 + (strcmp(p, q) != 0);
10543 if(changes == 1) { // a single engine mnemonic was changed
10544 q = r; while(*q) nPlayers += (*q++ == '\n');
10545 p = buf; while(*r && (*p = *r++) != '\n') p++;
10547 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10548 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10549 if(mnemonic[i]) { // The substitute is valid
10551 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10552 flock(fileno(f), LOCK_EX);
10553 ParseArgsFromFile(f);
10554 fseek(f, 0, SEEK_SET);
10555 FREE(appData.participants); appData.participants = participants;
10556 if(expunge) { // erase results of replaced engine
10557 int len = strlen(appData.results), w, b, dummy;
10558 for(i=0; i<len; i++) {
10559 Pairing(i, nPlayers, &w, &b, &dummy);
10560 if((w == changed || b == changed) && appData.results[i] == '*') {
10561 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10566 for(i=0; i<len; i++) {
10567 Pairing(i, nPlayers, &w, &b, &dummy);
10568 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10571 WriteTourneyFile(appData.results, f);
10572 fclose(f); // release lock
10575 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10577 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10578 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10579 free(participants);
10584 CheckPlayers (char *participants)
10587 char buf[MSG_SIZ], *p;
10588 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10589 while(p = strchr(participants, '\n')) {
10591 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10593 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10595 DisplayError(buf, 0);
10599 participants = p + 1;
10605 CreateTourney (char *name)
10608 if(matchMode && strcmp(name, appData.tourneyFile)) {
10609 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10611 if(name[0] == NULLCHAR) {
10612 if(appData.participants[0])
10613 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10616 f = fopen(name, "r");
10617 if(f) { // file exists
10618 ASSIGN(appData.tourneyFile, name);
10619 ParseArgsFromFile(f); // parse it
10621 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10622 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10623 DisplayError(_("Not enough participants"), 0);
10626 if(CheckPlayers(appData.participants)) return 0;
10627 ASSIGN(appData.tourneyFile, name);
10628 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10629 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10632 appData.noChessProgram = FALSE;
10633 appData.clockMode = TRUE;
10639 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10641 char buf[MSG_SIZ], *p, *q;
10642 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10643 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10644 skip = !all && group[0]; // if group requested, we start in skip mode
10645 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10646 p = names; q = buf; header = 0;
10647 while(*p && *p != '\n') *q++ = *p++;
10649 if(*p == '\n') p++;
10650 if(buf[0] == '#') {
10651 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10652 depth++; // we must be entering a new group
10653 if(all) continue; // suppress printing group headers when complete list requested
10655 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10657 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10658 if(engineList[i]) free(engineList[i]);
10659 engineList[i] = strdup(buf);
10660 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10661 if(engineMnemonic[i]) free(engineMnemonic[i]);
10662 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10664 sscanf(q + 8, "%s", buf + strlen(buf));
10667 engineMnemonic[i] = strdup(buf);
10670 engineList[i] = engineMnemonic[i] = NULL;
10674 // following implemented as macro to avoid type limitations
10675 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10678 SwapEngines (int n)
10679 { // swap settings for first engine and other engine (so far only some selected options)
10684 SWAP(chessProgram, p)
10686 SWAP(hasOwnBookUCI, h)
10687 SWAP(protocolVersion, h)
10689 SWAP(scoreIsAbsolute, h)
10694 SWAP(engOptions, p)
10695 SWAP(engInitString, p)
10696 SWAP(computerString, p)
10698 SWAP(fenOverride, p)
10700 SWAP(accumulateTC, h)
10705 GetEngineLine (char *s, int n)
10709 extern char *icsNames;
10710 if(!s || !*s) return 0;
10711 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10712 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10713 if(!mnemonic[i]) return 0;
10714 if(n == 11) return 1; // just testing if there was a match
10715 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10716 if(n == 1) SwapEngines(n);
10717 ParseArgsFromString(buf);
10718 if(n == 1) SwapEngines(n);
10719 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10720 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10721 ParseArgsFromString(buf);
10727 SetPlayer (int player, char *p)
10728 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10730 char buf[MSG_SIZ], *engineName;
10731 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10732 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10733 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10735 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10736 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10737 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10738 ParseArgsFromString(buf);
10739 } else { // no engine with this nickname is installed!
10740 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10741 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10742 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10744 DisplayError(buf, 0);
10751 char *recentEngines;
10754 RecentEngineEvent (int nr)
10757 // SwapEngines(1); // bump first to second
10758 // ReplaceEngine(&second, 1); // and load it there
10759 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10760 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10761 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10762 ReplaceEngine(&first, 0);
10763 FloatToFront(&appData.recentEngineList, command[n]);
10768 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10769 { // determine players from game number
10770 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10772 if(appData.tourneyType == 0) {
10773 roundsPerCycle = (nPlayers - 1) | 1;
10774 pairingsPerRound = nPlayers / 2;
10775 } else if(appData.tourneyType > 0) {
10776 roundsPerCycle = nPlayers - appData.tourneyType;
10777 pairingsPerRound = appData.tourneyType;
10779 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10780 gamesPerCycle = gamesPerRound * roundsPerCycle;
10781 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10782 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10783 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10784 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10785 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10786 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10788 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10789 if(appData.roundSync) *syncInterval = gamesPerRound;
10791 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10793 if(appData.tourneyType == 0) {
10794 if(curPairing == (nPlayers-1)/2 ) {
10795 *whitePlayer = curRound;
10796 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10798 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10799 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10800 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10801 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10803 } else if(appData.tourneyType > 1) {
10804 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10805 *whitePlayer = curRound + appData.tourneyType;
10806 } else if(appData.tourneyType > 0) {
10807 *whitePlayer = curPairing;
10808 *blackPlayer = curRound + appData.tourneyType;
10811 // take care of white/black alternation per round.
10812 // For cycles and games this is already taken care of by default, derived from matchGame!
10813 return curRound & 1;
10817 NextTourneyGame (int nr, int *swapColors)
10818 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10820 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10822 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10823 tf = fopen(appData.tourneyFile, "r");
10824 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10825 ParseArgsFromFile(tf); fclose(tf);
10826 InitTimeControls(); // TC might be altered from tourney file
10828 nPlayers = CountPlayers(appData.participants); // count participants
10829 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10830 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10833 p = q = appData.results;
10834 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10835 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10836 DisplayMessage(_("Waiting for other game(s)"),"");
10837 waitingForGame = TRUE;
10838 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10841 waitingForGame = FALSE;
10844 if(appData.tourneyType < 0) {
10845 if(nr>=0 && !pairingReceived) {
10847 if(pairing.pr == NoProc) {
10848 if(!appData.pairingEngine[0]) {
10849 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10852 StartChessProgram(&pairing); // starts the pairing engine
10854 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10855 SendToProgram(buf, &pairing);
10856 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10857 SendToProgram(buf, &pairing);
10858 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10860 pairingReceived = 0; // ... so we continue here
10862 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10863 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10864 matchGame = 1; roundNr = nr / syncInterval + 1;
10867 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10869 // redefine engines, engine dir, etc.
10870 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10871 if(first.pr == NoProc) {
10872 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10873 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10875 if(second.pr == NoProc) {
10877 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10878 SwapEngines(1); // and make that valid for second engine by swapping
10879 InitEngine(&second, 1);
10881 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10882 UpdateLogos(FALSE); // leave display to ModeHiglight()
10888 { // performs game initialization that does not invoke engines, and then tries to start the game
10889 int res, firstWhite, swapColors = 0;
10890 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10891 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
10893 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10894 if(strcmp(buf, currentDebugFile)) { // name has changed
10895 FILE *f = fopen(buf, "w");
10896 if(f) { // if opening the new file failed, just keep using the old one
10897 ASSIGN(currentDebugFile, buf);
10901 if(appData.serverFileName) {
10902 if(serverFP) fclose(serverFP);
10903 serverFP = fopen(appData.serverFileName, "w");
10904 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10905 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10909 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10910 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10911 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10912 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10913 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10914 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10915 Reset(FALSE, first.pr != NoProc);
10916 res = LoadGameOrPosition(matchGame); // setup game
10917 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10918 if(!res) return; // abort when bad game/pos file
10919 TwoMachinesEvent();
10923 UserAdjudicationEvent (int result)
10925 ChessMove gameResult = GameIsDrawn;
10928 gameResult = WhiteWins;
10930 else if( result < 0 ) {
10931 gameResult = BlackWins;
10934 if( gameMode == TwoMachinesPlay ) {
10935 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10940 // [HGM] save: calculate checksum of game to make games easily identifiable
10942 StringCheckSum (char *s)
10945 if(s==NULL) return 0;
10946 while(*s) i = i*259 + *s++;
10954 for(i=backwardMostMove; i<forwardMostMove; i++) {
10955 sum += pvInfoList[i].depth;
10956 sum += StringCheckSum(parseList[i]);
10957 sum += StringCheckSum(commentList[i]);
10960 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10961 return sum + StringCheckSum(commentList[i]);
10962 } // end of save patch
10965 GameEnds (ChessMove result, char *resultDetails, int whosays)
10967 GameMode nextGameMode;
10969 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10971 if(endingGame) return; /* [HGM] crash: forbid recursion */
10973 if(twoBoards) { // [HGM] dual: switch back to one board
10974 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10975 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10977 if (appData.debugMode) {
10978 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10979 result, resultDetails ? resultDetails : "(null)", whosays);
10982 fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10984 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10986 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10987 /* If we are playing on ICS, the server decides when the
10988 game is over, but the engine can offer to draw, claim
10992 if (appData.zippyPlay && first.initDone) {
10993 if (result == GameIsDrawn) {
10994 /* In case draw still needs to be claimed */
10995 SendToICS(ics_prefix);
10996 SendToICS("draw\n");
10997 } else if (StrCaseStr(resultDetails, "resign")) {
10998 SendToICS(ics_prefix);
10999 SendToICS("resign\n");
11003 endingGame = 0; /* [HGM] crash */
11007 /* If we're loading the game from a file, stop */
11008 if (whosays == GE_FILE) {
11009 (void) StopLoadGameTimer();
11013 /* Cancel draw offers */
11014 first.offeredDraw = second.offeredDraw = 0;
11016 /* If this is an ICS game, only ICS can really say it's done;
11017 if not, anyone can. */
11018 isIcsGame = (gameMode == IcsPlayingWhite ||
11019 gameMode == IcsPlayingBlack ||
11020 gameMode == IcsObserving ||
11021 gameMode == IcsExamining);
11023 if (!isIcsGame || whosays == GE_ICS) {
11024 /* OK -- not an ICS game, or ICS said it was done */
11026 if (!isIcsGame && !appData.noChessProgram)
11027 SetUserThinkingEnables();
11029 /* [HGM] if a machine claims the game end we verify this claim */
11030 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11031 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11033 ChessMove trueResult = (ChessMove) -1;
11035 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11036 first.twoMachinesColor[0] :
11037 second.twoMachinesColor[0] ;
11039 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11040 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11041 /* [HGM] verify: engine mate claims accepted if they were flagged */
11042 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11044 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11045 /* [HGM] verify: engine mate claims accepted if they were flagged */
11046 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11048 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11049 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11052 // now verify win claims, but not in drop games, as we don't understand those yet
11053 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11054 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11055 (result == WhiteWins && claimer == 'w' ||
11056 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11057 if (appData.debugMode) {
11058 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11059 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11061 if(result != trueResult) {
11062 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11063 result = claimer == 'w' ? BlackWins : WhiteWins;
11064 resultDetails = buf;
11067 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11068 && (forwardMostMove <= backwardMostMove ||
11069 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11070 (claimer=='b')==(forwardMostMove&1))
11072 /* [HGM] verify: draws that were not flagged are false claims */
11073 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11074 result = claimer == 'w' ? BlackWins : WhiteWins;
11075 resultDetails = buf;
11077 /* (Claiming a loss is accepted no questions asked!) */
11078 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11079 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11080 result = GameUnfinished;
11081 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11083 /* [HGM] bare: don't allow bare King to win */
11084 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11085 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11086 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11087 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11088 && result != GameIsDrawn)
11089 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11090 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11091 int p = (signed char)boards[forwardMostMove][i][j] - color;
11092 if(p >= 0 && p <= (int)WhiteKing) k++;
11094 if (appData.debugMode) {
11095 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11096 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11099 result = GameIsDrawn;
11100 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11101 resultDetails = buf;
11107 if(serverMoves != NULL && !loadFlag) { char c = '=';
11108 if(result==WhiteWins) c = '+';
11109 if(result==BlackWins) c = '-';
11110 if(resultDetails != NULL)
11111 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11113 if (resultDetails != NULL) {
11114 gameInfo.result = result;
11115 gameInfo.resultDetails = StrSave(resultDetails);
11117 /* display last move only if game was not loaded from file */
11118 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11119 DisplayMove(currentMove - 1);
11121 if (forwardMostMove != 0) {
11122 if (gameMode != PlayFromGameFile && gameMode != EditGame
11123 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11125 if (*appData.saveGameFile != NULLCHAR) {
11126 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11127 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11129 SaveGameToFile(appData.saveGameFile, TRUE);
11130 } else if (appData.autoSaveGames) {
11131 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11133 if (*appData.savePositionFile != NULLCHAR) {
11134 SavePositionToFile(appData.savePositionFile);
11136 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11140 /* Tell program how game ended in case it is learning */
11141 /* [HGM] Moved this to after saving the PGN, just in case */
11142 /* engine died and we got here through time loss. In that */
11143 /* case we will get a fatal error writing the pipe, which */
11144 /* would otherwise lose us the PGN. */
11145 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11146 /* output during GameEnds should never be fatal anymore */
11147 if (gameMode == MachinePlaysWhite ||
11148 gameMode == MachinePlaysBlack ||
11149 gameMode == TwoMachinesPlay ||
11150 gameMode == IcsPlayingWhite ||
11151 gameMode == IcsPlayingBlack ||
11152 gameMode == BeginningOfGame) {
11154 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11156 if (first.pr != NoProc) {
11157 SendToProgram(buf, &first);
11159 if (second.pr != NoProc &&
11160 gameMode == TwoMachinesPlay) {
11161 SendToProgram(buf, &second);
11166 if (appData.icsActive) {
11167 if (appData.quietPlay &&
11168 (gameMode == IcsPlayingWhite ||
11169 gameMode == IcsPlayingBlack)) {
11170 SendToICS(ics_prefix);
11171 SendToICS("set shout 1\n");
11173 nextGameMode = IcsIdle;
11174 ics_user_moved = FALSE;
11175 /* clean up premove. It's ugly when the game has ended and the
11176 * premove highlights are still on the board.
11179 gotPremove = FALSE;
11180 ClearPremoveHighlights();
11181 DrawPosition(FALSE, boards[currentMove]);
11183 if (whosays == GE_ICS) {
11186 if (gameMode == IcsPlayingWhite)
11188 else if(gameMode == IcsPlayingBlack)
11189 PlayIcsLossSound();
11192 if (gameMode == IcsPlayingBlack)
11194 else if(gameMode == IcsPlayingWhite)
11195 PlayIcsLossSound();
11198 PlayIcsDrawSound();
11201 PlayIcsUnfinishedSound();
11204 if(appData.quitNext) { ExitEvent(0); return; }
11205 } else if (gameMode == EditGame ||
11206 gameMode == PlayFromGameFile ||
11207 gameMode == AnalyzeMode ||
11208 gameMode == AnalyzeFile) {
11209 nextGameMode = gameMode;
11211 nextGameMode = EndOfGame;
11216 nextGameMode = gameMode;
11219 if (appData.noChessProgram) {
11220 gameMode = nextGameMode;
11222 endingGame = 0; /* [HGM] crash */
11227 /* Put first chess program into idle state */
11228 if (first.pr != NoProc &&
11229 (gameMode == MachinePlaysWhite ||
11230 gameMode == MachinePlaysBlack ||
11231 gameMode == TwoMachinesPlay ||
11232 gameMode == IcsPlayingWhite ||
11233 gameMode == IcsPlayingBlack ||
11234 gameMode == BeginningOfGame)) {
11235 SendToProgram("force\n", &first);
11236 if (first.usePing) {
11238 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11239 SendToProgram(buf, &first);
11242 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11243 /* Kill off first chess program */
11244 if (first.isr != NULL)
11245 RemoveInputSource(first.isr);
11248 if (first.pr != NoProc) {
11250 DoSleep( appData.delayBeforeQuit );
11251 SendToProgram("quit\n", &first);
11252 DoSleep( appData.delayAfterQuit );
11253 DestroyChildProcess(first.pr, first.useSigterm);
11254 first.reload = TRUE;
11258 if (second.reuse) {
11259 /* Put second chess program into idle state */
11260 if (second.pr != NoProc &&
11261 gameMode == TwoMachinesPlay) {
11262 SendToProgram("force\n", &second);
11263 if (second.usePing) {
11265 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11266 SendToProgram(buf, &second);
11269 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11270 /* Kill off second chess program */
11271 if (second.isr != NULL)
11272 RemoveInputSource(second.isr);
11275 if (second.pr != NoProc) {
11276 DoSleep( appData.delayBeforeQuit );
11277 SendToProgram("quit\n", &second);
11278 DoSleep( appData.delayAfterQuit );
11279 DestroyChildProcess(second.pr, second.useSigterm);
11280 second.reload = TRUE;
11282 second.pr = NoProc;
11285 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11286 char resChar = '=';
11290 if (first.twoMachinesColor[0] == 'w') {
11293 second.matchWins++;
11298 if (first.twoMachinesColor[0] == 'b') {
11301 second.matchWins++;
11304 case GameUnfinished:
11310 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11311 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11312 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11313 ReserveGame(nextGame, resChar); // sets nextGame
11314 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11315 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11316 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11318 if (nextGame <= appData.matchGames && !abortMatch) {
11319 gameMode = nextGameMode;
11320 matchGame = nextGame; // this will be overruled in tourney mode!
11321 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11322 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11323 endingGame = 0; /* [HGM] crash */
11326 gameMode = nextGameMode;
11327 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11328 first.tidy, second.tidy,
11329 first.matchWins, second.matchWins,
11330 appData.matchGames - (first.matchWins + second.matchWins));
11331 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11332 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11333 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11334 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11335 first.twoMachinesColor = "black\n";
11336 second.twoMachinesColor = "white\n";
11338 first.twoMachinesColor = "white\n";
11339 second.twoMachinesColor = "black\n";
11343 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11344 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11346 gameMode = nextGameMode;
11348 endingGame = 0; /* [HGM] crash */
11349 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11350 if(matchMode == TRUE) { // match through command line: exit with or without popup
11352 ToNrEvent(forwardMostMove);
11353 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11355 } else DisplayFatalError(buf, 0, 0);
11356 } else { // match through menu; just stop, with or without popup
11357 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11360 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11361 } else DisplayNote(buf);
11363 if(ranking) free(ranking);
11367 /* Assumes program was just initialized (initString sent).
11368 Leaves program in force mode. */
11370 FeedMovesToProgram (ChessProgramState *cps, int upto)
11374 if (appData.debugMode)
11375 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11376 startedFromSetupPosition ? "position and " : "",
11377 backwardMostMove, upto, cps->which);
11378 if(currentlyInitializedVariant != gameInfo.variant) {
11380 // [HGM] variantswitch: make engine aware of new variant
11381 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11382 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11383 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11384 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11385 SendToProgram(buf, cps);
11386 currentlyInitializedVariant = gameInfo.variant;
11388 SendToProgram("force\n", cps);
11389 if (startedFromSetupPosition) {
11390 SendBoard(cps, backwardMostMove);
11391 if (appData.debugMode) {
11392 fprintf(debugFP, "feedMoves\n");
11395 for (i = backwardMostMove; i < upto; i++) {
11396 SendMoveToProgram(i, cps);
11402 ResurrectChessProgram ()
11404 /* The chess program may have exited.
11405 If so, restart it and feed it all the moves made so far. */
11406 static int doInit = 0;
11408 if (appData.noChessProgram) return 1;
11410 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11411 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11412 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11413 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11415 if (first.pr != NoProc) return 1;
11416 StartChessProgram(&first);
11418 InitChessProgram(&first, FALSE);
11419 FeedMovesToProgram(&first, currentMove);
11421 if (!first.sendTime) {
11422 /* can't tell gnuchess what its clock should read,
11423 so we bow to its notion. */
11425 timeRemaining[0][currentMove] = whiteTimeRemaining;
11426 timeRemaining[1][currentMove] = blackTimeRemaining;
11429 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11430 appData.icsEngineAnalyze) && first.analysisSupport) {
11431 SendToProgram("analyze\n", &first);
11432 first.analyzing = TRUE;
11438 * Button procedures
11441 Reset (int redraw, int init)
11445 if (appData.debugMode) {
11446 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11447 redraw, init, gameMode);
11449 CleanupTail(); // [HGM] vari: delete any stored variations
11450 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11451 pausing = pauseExamInvalid = FALSE;
11452 startedFromSetupPosition = blackPlaysFirst = FALSE;
11454 whiteFlag = blackFlag = FALSE;
11455 userOfferedDraw = FALSE;
11456 hintRequested = bookRequested = FALSE;
11457 first.maybeThinking = FALSE;
11458 second.maybeThinking = FALSE;
11459 first.bookSuspend = FALSE; // [HGM] book
11460 second.bookSuspend = FALSE;
11461 thinkOutput[0] = NULLCHAR;
11462 lastHint[0] = NULLCHAR;
11463 ClearGameInfo(&gameInfo);
11464 gameInfo.variant = StringToVariant(appData.variant);
11465 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11466 ics_user_moved = ics_clock_paused = FALSE;
11467 ics_getting_history = H_FALSE;
11469 white_holding[0] = black_holding[0] = NULLCHAR;
11470 ClearProgramStats();
11471 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11475 flipView = appData.flipView;
11476 ClearPremoveHighlights();
11477 gotPremove = FALSE;
11478 alarmSounded = FALSE;
11479 killX = killY = -1; // [HGM] lion
11481 GameEnds(EndOfFile, NULL, GE_PLAYER);
11482 if(appData.serverMovesName != NULL) {
11483 /* [HGM] prepare to make moves file for broadcasting */
11484 clock_t t = clock();
11485 if(serverMoves != NULL) fclose(serverMoves);
11486 serverMoves = fopen(appData.serverMovesName, "r");
11487 if(serverMoves != NULL) {
11488 fclose(serverMoves);
11489 /* delay 15 sec before overwriting, so all clients can see end */
11490 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11492 serverMoves = fopen(appData.serverMovesName, "w");
11496 gameMode = BeginningOfGame;
11498 if(appData.icsActive) gameInfo.variant = VariantNormal;
11499 currentMove = forwardMostMove = backwardMostMove = 0;
11500 MarkTargetSquares(1);
11501 InitPosition(redraw);
11502 for (i = 0; i < MAX_MOVES; i++) {
11503 if (commentList[i] != NULL) {
11504 free(commentList[i]);
11505 commentList[i] = NULL;
11509 timeRemaining[0][0] = whiteTimeRemaining;
11510 timeRemaining[1][0] = blackTimeRemaining;
11512 if (first.pr == NoProc) {
11513 StartChessProgram(&first);
11516 InitChessProgram(&first, startedFromSetupPosition);
11519 DisplayMessage("", "");
11520 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11521 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11522 ClearMap(); // [HGM] exclude: invalidate map
11526 AutoPlayGameLoop ()
11529 if (!AutoPlayOneMove())
11531 if (matchMode || appData.timeDelay == 0)
11533 if (appData.timeDelay < 0)
11535 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11543 ReloadGame(1); // next game
11549 int fromX, fromY, toX, toY;
11551 if (appData.debugMode) {
11552 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11555 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11558 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11559 pvInfoList[currentMove].depth = programStats.depth;
11560 pvInfoList[currentMove].score = programStats.score;
11561 pvInfoList[currentMove].time = 0;
11562 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11563 else { // append analysis of final position as comment
11565 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11566 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11568 programStats.depth = 0;
11571 if (currentMove >= forwardMostMove) {
11572 if(gameMode == AnalyzeFile) {
11573 if(appData.loadGameIndex == -1) {
11574 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11575 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11577 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11580 // gameMode = EndOfGame;
11581 // ModeHighlight();
11583 /* [AS] Clear current move marker at the end of a game */
11584 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11589 toX = moveList[currentMove][2] - AAA;
11590 toY = moveList[currentMove][3] - ONE;
11592 if (moveList[currentMove][1] == '@') {
11593 if (appData.highlightLastMove) {
11594 SetHighlights(-1, -1, toX, toY);
11597 fromX = moveList[currentMove][0] - AAA;
11598 fromY = moveList[currentMove][1] - ONE;
11600 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11602 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11604 if (appData.highlightLastMove) {
11605 SetHighlights(fromX, fromY, toX, toY);
11608 DisplayMove(currentMove);
11609 SendMoveToProgram(currentMove++, &first);
11610 DisplayBothClocks();
11611 DrawPosition(FALSE, boards[currentMove]);
11612 // [HGM] PV info: always display, routine tests if empty
11613 DisplayComment(currentMove - 1, commentList[currentMove]);
11619 LoadGameOneMove (ChessMove readAhead)
11621 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11622 char promoChar = NULLCHAR;
11623 ChessMove moveType;
11624 char move[MSG_SIZ];
11627 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11628 gameMode != AnalyzeMode && gameMode != Training) {
11633 yyboardindex = forwardMostMove;
11634 if (readAhead != EndOfFile) {
11635 moveType = readAhead;
11637 if (gameFileFP == NULL)
11639 moveType = (ChessMove) Myylex();
11643 switch (moveType) {
11645 if (appData.debugMode)
11646 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11649 /* append the comment but don't display it */
11650 AppendComment(currentMove, p, FALSE);
11653 case WhiteCapturesEnPassant:
11654 case BlackCapturesEnPassant:
11655 case WhitePromotion:
11656 case BlackPromotion:
11657 case WhiteNonPromotion:
11658 case BlackNonPromotion:
11661 case WhiteKingSideCastle:
11662 case WhiteQueenSideCastle:
11663 case BlackKingSideCastle:
11664 case BlackQueenSideCastle:
11665 case WhiteKingSideCastleWild:
11666 case WhiteQueenSideCastleWild:
11667 case BlackKingSideCastleWild:
11668 case BlackQueenSideCastleWild:
11670 case WhiteHSideCastleFR:
11671 case WhiteASideCastleFR:
11672 case BlackHSideCastleFR:
11673 case BlackASideCastleFR:
11675 if (appData.debugMode)
11676 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11677 fromX = currentMoveString[0] - AAA;
11678 fromY = currentMoveString[1] - ONE;
11679 toX = currentMoveString[2] - AAA;
11680 toY = currentMoveString[3] - ONE;
11681 promoChar = currentMoveString[4];
11682 if(promoChar == ';') promoChar = NULLCHAR;
11687 if (appData.debugMode)
11688 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11689 fromX = moveType == WhiteDrop ?
11690 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11691 (int) CharToPiece(ToLower(currentMoveString[0]));
11693 toX = currentMoveString[2] - AAA;
11694 toY = currentMoveString[3] - ONE;
11700 case GameUnfinished:
11701 if (appData.debugMode)
11702 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11703 p = strchr(yy_text, '{');
11704 if (p == NULL) p = strchr(yy_text, '(');
11707 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11709 q = strchr(p, *p == '{' ? '}' : ')');
11710 if (q != NULL) *q = NULLCHAR;
11713 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11714 GameEnds(moveType, p, GE_FILE);
11716 if (cmailMsgLoaded) {
11718 flipView = WhiteOnMove(currentMove);
11719 if (moveType == GameUnfinished) flipView = !flipView;
11720 if (appData.debugMode)
11721 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11726 if (appData.debugMode)
11727 fprintf(debugFP, "Parser hit end of file\n");
11728 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11734 if (WhiteOnMove(currentMove)) {
11735 GameEnds(BlackWins, "Black mates", GE_FILE);
11737 GameEnds(WhiteWins, "White mates", GE_FILE);
11741 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11747 case MoveNumberOne:
11748 if (lastLoadGameStart == GNUChessGame) {
11749 /* GNUChessGames have numbers, but they aren't move numbers */
11750 if (appData.debugMode)
11751 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11752 yy_text, (int) moveType);
11753 return LoadGameOneMove(EndOfFile); /* tail recursion */
11755 /* else fall thru */
11760 /* Reached start of next game in file */
11761 if (appData.debugMode)
11762 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11763 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11769 if (WhiteOnMove(currentMove)) {
11770 GameEnds(BlackWins, "Black mates", GE_FILE);
11772 GameEnds(WhiteWins, "White mates", GE_FILE);
11776 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11782 case PositionDiagram: /* should not happen; ignore */
11783 case ElapsedTime: /* ignore */
11784 case NAG: /* ignore */
11785 if (appData.debugMode)
11786 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11787 yy_text, (int) moveType);
11788 return LoadGameOneMove(EndOfFile); /* tail recursion */
11791 if (appData.testLegality) {
11792 if (appData.debugMode)
11793 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11794 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11795 (forwardMostMove / 2) + 1,
11796 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11797 DisplayError(move, 0);
11800 if (appData.debugMode)
11801 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11802 yy_text, currentMoveString);
11803 fromX = currentMoveString[0] - AAA;
11804 fromY = currentMoveString[1] - ONE;
11805 toX = currentMoveString[2] - AAA;
11806 toY = currentMoveString[3] - ONE;
11807 promoChar = currentMoveString[4];
11811 case AmbiguousMove:
11812 if (appData.debugMode)
11813 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11814 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11815 (forwardMostMove / 2) + 1,
11816 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11817 DisplayError(move, 0);
11822 case ImpossibleMove:
11823 if (appData.debugMode)
11824 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11825 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11826 (forwardMostMove / 2) + 1,
11827 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11828 DisplayError(move, 0);
11834 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11835 DrawPosition(FALSE, boards[currentMove]);
11836 DisplayBothClocks();
11837 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11838 DisplayComment(currentMove - 1, commentList[currentMove]);
11840 (void) StopLoadGameTimer();
11842 cmailOldMove = forwardMostMove;
11845 /* currentMoveString is set as a side-effect of yylex */
11847 thinkOutput[0] = NULLCHAR;
11848 MakeMove(fromX, fromY, toX, toY, promoChar);
11849 killX = killY = -1; // [HGM] lion: used up
11850 currentMove = forwardMostMove;
11855 /* Load the nth game from the given file */
11857 LoadGameFromFile (char *filename, int n, char *title, int useList)
11862 if (strcmp(filename, "-") == 0) {
11866 f = fopen(filename, "rb");
11868 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11869 DisplayError(buf, errno);
11873 if (fseek(f, 0, 0) == -1) {
11874 /* f is not seekable; probably a pipe */
11877 if (useList && n == 0) {
11878 int error = GameListBuild(f);
11880 DisplayError(_("Cannot build game list"), error);
11881 } else if (!ListEmpty(&gameList) &&
11882 ((ListGame *) gameList.tailPred)->number > 1) {
11883 GameListPopUp(f, title);
11890 return LoadGame(f, n, title, FALSE);
11895 MakeRegisteredMove ()
11897 int fromX, fromY, toX, toY;
11899 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11900 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11903 if (appData.debugMode)
11904 fprintf(debugFP, "Restoring %s for game %d\n",
11905 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11907 thinkOutput[0] = NULLCHAR;
11908 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11909 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11910 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11911 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11912 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11913 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11914 MakeMove(fromX, fromY, toX, toY, promoChar);
11915 ShowMove(fromX, fromY, toX, toY);
11917 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11924 if (WhiteOnMove(currentMove)) {
11925 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11927 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11932 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11939 if (WhiteOnMove(currentMove)) {
11940 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11942 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11947 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11958 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11960 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11964 if (gameNumber > nCmailGames) {
11965 DisplayError(_("No more games in this message"), 0);
11968 if (f == lastLoadGameFP) {
11969 int offset = gameNumber - lastLoadGameNumber;
11971 cmailMsg[0] = NULLCHAR;
11972 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11973 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11974 nCmailMovesRegistered--;
11976 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11977 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11978 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11981 if (! RegisterMove()) return FALSE;
11985 retVal = LoadGame(f, gameNumber, title, useList);
11987 /* Make move registered during previous look at this game, if any */
11988 MakeRegisteredMove();
11990 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11991 commentList[currentMove]
11992 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11993 DisplayComment(currentMove - 1, commentList[currentMove]);
11999 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12001 ReloadGame (int offset)
12003 int gameNumber = lastLoadGameNumber + offset;
12004 if (lastLoadGameFP == NULL) {
12005 DisplayError(_("No game has been loaded yet"), 0);
12008 if (gameNumber <= 0) {
12009 DisplayError(_("Can't back up any further"), 0);
12012 if (cmailMsgLoaded) {
12013 return CmailLoadGame(lastLoadGameFP, gameNumber,
12014 lastLoadGameTitle, lastLoadGameUseList);
12016 return LoadGame(lastLoadGameFP, gameNumber,
12017 lastLoadGameTitle, lastLoadGameUseList);
12021 int keys[EmptySquare+1];
12024 PositionMatches (Board b1, Board b2)
12027 switch(appData.searchMode) {
12028 case 1: return CompareWithRights(b1, b2);
12030 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12031 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12035 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12036 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12037 sum += keys[b1[r][f]] - keys[b2[r][f]];
12041 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12042 sum += keys[b1[r][f]] - keys[b2[r][f]];
12054 int pieceList[256], quickBoard[256];
12055 ChessSquare pieceType[256] = { EmptySquare };
12056 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12057 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12058 int soughtTotal, turn;
12059 Boolean epOK, flipSearch;
12062 unsigned char piece, to;
12065 #define DSIZE (250000)
12067 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12068 Move *moveDatabase = initialSpace;
12069 unsigned int movePtr, dataSize = DSIZE;
12072 MakePieceList (Board board, int *counts)
12074 int r, f, n=Q_PROMO, total=0;
12075 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12076 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12077 int sq = f + (r<<4);
12078 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12079 quickBoard[sq] = ++n;
12081 pieceType[n] = board[r][f];
12082 counts[board[r][f]]++;
12083 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12084 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12088 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12093 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12095 int sq = fromX + (fromY<<4);
12096 int piece = quickBoard[sq];
12097 quickBoard[sq] = 0;
12098 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12099 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12100 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12101 moveDatabase[movePtr++].piece = Q_WCASTL;
12102 quickBoard[sq] = piece;
12103 piece = quickBoard[from]; quickBoard[from] = 0;
12104 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12106 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12107 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12108 moveDatabase[movePtr++].piece = Q_BCASTL;
12109 quickBoard[sq] = piece;
12110 piece = quickBoard[from]; quickBoard[from] = 0;
12111 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12113 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12114 quickBoard[(fromY<<4)+toX] = 0;
12115 moveDatabase[movePtr].piece = Q_EP;
12116 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12117 moveDatabase[movePtr].to = sq;
12119 if(promoPiece != pieceType[piece]) {
12120 moveDatabase[movePtr++].piece = Q_PROMO;
12121 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12123 moveDatabase[movePtr].piece = piece;
12124 quickBoard[sq] = piece;
12129 PackGame (Board board)
12131 Move *newSpace = NULL;
12132 moveDatabase[movePtr].piece = 0; // terminate previous game
12133 if(movePtr > dataSize) {
12134 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12135 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12136 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12139 Move *p = moveDatabase, *q = newSpace;
12140 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12141 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12142 moveDatabase = newSpace;
12143 } else { // calloc failed, we must be out of memory. Too bad...
12144 dataSize = 0; // prevent calloc events for all subsequent games
12145 return 0; // and signal this one isn't cached
12149 MakePieceList(board, counts);
12154 QuickCompare (Board board, int *minCounts, int *maxCounts)
12155 { // compare according to search mode
12157 switch(appData.searchMode)
12159 case 1: // exact position match
12160 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12161 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12162 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12165 case 2: // can have extra material on empty squares
12166 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12167 if(board[r][f] == EmptySquare) continue;
12168 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12171 case 3: // material with exact Pawn structure
12172 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12173 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12174 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12175 } // fall through to material comparison
12176 case 4: // exact material
12177 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12179 case 6: // material range with given imbalance
12180 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12181 // fall through to range comparison
12182 case 5: // material range
12183 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12189 QuickScan (Board board, Move *move)
12190 { // reconstruct game,and compare all positions in it
12191 int cnt=0, stretch=0, total = MakePieceList(board, counts);
12193 int piece = move->piece;
12194 int to = move->to, from = pieceList[piece];
12195 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12196 if(!piece) return -1;
12197 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12198 piece = (++move)->piece;
12199 from = pieceList[piece];
12200 counts[pieceType[piece]]--;
12201 pieceType[piece] = (ChessSquare) move->to;
12202 counts[move->to]++;
12203 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12204 counts[pieceType[quickBoard[to]]]--;
12205 quickBoard[to] = 0; total--;
12208 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12209 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12210 from = pieceList[piece]; // so this must be King
12211 quickBoard[from] = 0;
12212 pieceList[piece] = to;
12213 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12214 quickBoard[from] = 0; // rook
12215 quickBoard[to] = piece;
12216 to = move->to; piece = move->piece;
12220 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12221 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12222 quickBoard[from] = 0;
12224 quickBoard[to] = piece;
12225 pieceList[piece] = to;
12227 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12228 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12229 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12230 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12232 static int lastCounts[EmptySquare+1];
12234 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12235 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12236 } else stretch = 0;
12237 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12246 flipSearch = FALSE;
12247 CopyBoard(soughtBoard, boards[currentMove]);
12248 soughtTotal = MakePieceList(soughtBoard, maxSought);
12249 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12250 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12251 CopyBoard(reverseBoard, boards[currentMove]);
12252 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12253 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12254 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12255 reverseBoard[r][f] = piece;
12257 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12258 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12259 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12260 || (boards[currentMove][CASTLING][2] == NoRights ||
12261 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12262 && (boards[currentMove][CASTLING][5] == NoRights ||
12263 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12266 CopyBoard(flipBoard, soughtBoard);
12267 CopyBoard(rotateBoard, reverseBoard);
12268 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12269 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12270 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12273 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12274 if(appData.searchMode >= 5) {
12275 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12276 MakePieceList(soughtBoard, minSought);
12277 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12279 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12280 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12283 GameInfo dummyInfo;
12284 static int creatingBook;
12287 GameContainsPosition (FILE *f, ListGame *lg)
12289 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12290 int fromX, fromY, toX, toY;
12292 static int initDone=FALSE;
12294 // weed out games based on numerical tag comparison
12295 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12296 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12297 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12298 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12300 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12303 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12304 else CopyBoard(boards[scratch], initialPosition); // default start position
12307 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12308 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12311 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12312 fseek(f, lg->offset, 0);
12315 yyboardindex = scratch;
12316 quickFlag = plyNr+1;
12321 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12327 if(plyNr) return -1; // after we have seen moves, this is for new game
12330 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12331 case ImpossibleMove:
12332 case WhiteWins: // game ends here with these four
12335 case GameUnfinished:
12339 if(appData.testLegality) return -1;
12340 case WhiteCapturesEnPassant:
12341 case BlackCapturesEnPassant:
12342 case WhitePromotion:
12343 case BlackPromotion:
12344 case WhiteNonPromotion:
12345 case BlackNonPromotion:
12348 case WhiteKingSideCastle:
12349 case WhiteQueenSideCastle:
12350 case BlackKingSideCastle:
12351 case BlackQueenSideCastle:
12352 case WhiteKingSideCastleWild:
12353 case WhiteQueenSideCastleWild:
12354 case BlackKingSideCastleWild:
12355 case BlackQueenSideCastleWild:
12356 case WhiteHSideCastleFR:
12357 case WhiteASideCastleFR:
12358 case BlackHSideCastleFR:
12359 case BlackASideCastleFR:
12360 fromX = currentMoveString[0] - AAA;
12361 fromY = currentMoveString[1] - ONE;
12362 toX = currentMoveString[2] - AAA;
12363 toY = currentMoveString[3] - ONE;
12364 promoChar = currentMoveString[4];
12368 fromX = next == WhiteDrop ?
12369 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12370 (int) CharToPiece(ToLower(currentMoveString[0]));
12372 toX = currentMoveString[2] - AAA;
12373 toY = currentMoveString[3] - ONE;
12377 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12379 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12380 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12381 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12382 if(appData.findMirror) {
12383 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12384 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12389 /* Load the nth game from open file f */
12391 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12395 int gn = gameNumber;
12396 ListGame *lg = NULL;
12397 int numPGNTags = 0;
12399 GameMode oldGameMode;
12400 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12402 if (appData.debugMode)
12403 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12405 if (gameMode == Training )
12406 SetTrainingModeOff();
12408 oldGameMode = gameMode;
12409 if (gameMode != BeginningOfGame) {
12410 Reset(FALSE, TRUE);
12412 killX = killY = -1; // [HGM] lion: in case we did not Reset
12415 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12416 fclose(lastLoadGameFP);
12420 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12423 fseek(f, lg->offset, 0);
12424 GameListHighlight(gameNumber);
12425 pos = lg->position;
12429 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12430 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12432 DisplayError(_("Game number out of range"), 0);
12437 if (fseek(f, 0, 0) == -1) {
12438 if (f == lastLoadGameFP ?
12439 gameNumber == lastLoadGameNumber + 1 :
12443 DisplayError(_("Can't seek on game file"), 0);
12448 lastLoadGameFP = f;
12449 lastLoadGameNumber = gameNumber;
12450 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12451 lastLoadGameUseList = useList;
12455 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12456 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12457 lg->gameInfo.black);
12459 } else if (*title != NULLCHAR) {
12460 if (gameNumber > 1) {
12461 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12464 DisplayTitle(title);
12468 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12469 gameMode = PlayFromGameFile;
12473 currentMove = forwardMostMove = backwardMostMove = 0;
12474 CopyBoard(boards[0], initialPosition);
12478 * Skip the first gn-1 games in the file.
12479 * Also skip over anything that precedes an identifiable
12480 * start of game marker, to avoid being confused by
12481 * garbage at the start of the file. Currently
12482 * recognized start of game markers are the move number "1",
12483 * the pattern "gnuchess .* game", the pattern
12484 * "^[#;%] [^ ]* game file", and a PGN tag block.
12485 * A game that starts with one of the latter two patterns
12486 * will also have a move number 1, possibly
12487 * following a position diagram.
12488 * 5-4-02: Let's try being more lenient and allowing a game to
12489 * start with an unnumbered move. Does that break anything?
12491 cm = lastLoadGameStart = EndOfFile;
12493 yyboardindex = forwardMostMove;
12494 cm = (ChessMove) Myylex();
12497 if (cmailMsgLoaded) {
12498 nCmailGames = CMAIL_MAX_GAMES - gn;
12501 DisplayError(_("Game not found in file"), 0);
12508 lastLoadGameStart = cm;
12511 case MoveNumberOne:
12512 switch (lastLoadGameStart) {
12517 case MoveNumberOne:
12519 gn--; /* count this game */
12520 lastLoadGameStart = cm;
12529 switch (lastLoadGameStart) {
12532 case MoveNumberOne:
12534 gn--; /* count this game */
12535 lastLoadGameStart = cm;
12538 lastLoadGameStart = cm; /* game counted already */
12546 yyboardindex = forwardMostMove;
12547 cm = (ChessMove) Myylex();
12548 } while (cm == PGNTag || cm == Comment);
12555 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12556 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12557 != CMAIL_OLD_RESULT) {
12559 cmailResult[ CMAIL_MAX_GAMES
12560 - gn - 1] = CMAIL_OLD_RESULT;
12567 /* Only a NormalMove can be at the start of a game
12568 * without a position diagram. */
12569 if (lastLoadGameStart == EndOfFile ) {
12571 lastLoadGameStart = MoveNumberOne;
12580 if (appData.debugMode)
12581 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12583 if (cm == XBoardGame) {
12584 /* Skip any header junk before position diagram and/or move 1 */
12586 yyboardindex = forwardMostMove;
12587 cm = (ChessMove) Myylex();
12589 if (cm == EndOfFile ||
12590 cm == GNUChessGame || cm == XBoardGame) {
12591 /* Empty game; pretend end-of-file and handle later */
12596 if (cm == MoveNumberOne || cm == PositionDiagram ||
12597 cm == PGNTag || cm == Comment)
12600 } else if (cm == GNUChessGame) {
12601 if (gameInfo.event != NULL) {
12602 free(gameInfo.event);
12604 gameInfo.event = StrSave(yy_text);
12607 startedFromSetupPosition = FALSE;
12608 while (cm == PGNTag) {
12609 if (appData.debugMode)
12610 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12611 err = ParsePGNTag(yy_text, &gameInfo);
12612 if (!err) numPGNTags++;
12614 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12615 if(gameInfo.variant != oldVariant) {
12616 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12617 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12618 InitPosition(TRUE);
12619 oldVariant = gameInfo.variant;
12620 if (appData.debugMode)
12621 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12625 if (gameInfo.fen != NULL) {
12626 Board initial_position;
12627 startedFromSetupPosition = TRUE;
12628 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12630 DisplayError(_("Bad FEN position in file"), 0);
12633 CopyBoard(boards[0], initial_position);
12634 if (blackPlaysFirst) {
12635 currentMove = forwardMostMove = backwardMostMove = 1;
12636 CopyBoard(boards[1], initial_position);
12637 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12638 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12639 timeRemaining[0][1] = whiteTimeRemaining;
12640 timeRemaining[1][1] = blackTimeRemaining;
12641 if (commentList[0] != NULL) {
12642 commentList[1] = commentList[0];
12643 commentList[0] = NULL;
12646 currentMove = forwardMostMove = backwardMostMove = 0;
12648 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12650 initialRulePlies = FENrulePlies;
12651 for( i=0; i< nrCastlingRights; i++ )
12652 initialRights[i] = initial_position[CASTLING][i];
12654 yyboardindex = forwardMostMove;
12655 free(gameInfo.fen);
12656 gameInfo.fen = NULL;
12659 yyboardindex = forwardMostMove;
12660 cm = (ChessMove) Myylex();
12662 /* Handle comments interspersed among the tags */
12663 while (cm == Comment) {
12665 if (appData.debugMode)
12666 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12668 AppendComment(currentMove, p, FALSE);
12669 yyboardindex = forwardMostMove;
12670 cm = (ChessMove) Myylex();
12674 /* don't rely on existence of Event tag since if game was
12675 * pasted from clipboard the Event tag may not exist
12677 if (numPGNTags > 0){
12679 if (gameInfo.variant == VariantNormal) {
12680 VariantClass v = StringToVariant(gameInfo.event);
12681 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12682 if(v < VariantShogi) gameInfo.variant = v;
12685 if( appData.autoDisplayTags ) {
12686 tags = PGNTags(&gameInfo);
12687 TagsPopUp(tags, CmailMsg());
12692 /* Make something up, but don't display it now */
12697 if (cm == PositionDiagram) {
12700 Board initial_position;
12702 if (appData.debugMode)
12703 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12705 if (!startedFromSetupPosition) {
12707 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12708 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12719 initial_position[i][j++] = CharToPiece(*p);
12722 while (*p == ' ' || *p == '\t' ||
12723 *p == '\n' || *p == '\r') p++;
12725 if (strncmp(p, "black", strlen("black"))==0)
12726 blackPlaysFirst = TRUE;
12728 blackPlaysFirst = FALSE;
12729 startedFromSetupPosition = TRUE;
12731 CopyBoard(boards[0], initial_position);
12732 if (blackPlaysFirst) {
12733 currentMove = forwardMostMove = backwardMostMove = 1;
12734 CopyBoard(boards[1], initial_position);
12735 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12736 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12737 timeRemaining[0][1] = whiteTimeRemaining;
12738 timeRemaining[1][1] = blackTimeRemaining;
12739 if (commentList[0] != NULL) {
12740 commentList[1] = commentList[0];
12741 commentList[0] = NULL;
12744 currentMove = forwardMostMove = backwardMostMove = 0;
12747 yyboardindex = forwardMostMove;
12748 cm = (ChessMove) Myylex();
12751 if(!creatingBook) {
12752 if (first.pr == NoProc) {
12753 StartChessProgram(&first);
12755 InitChessProgram(&first, FALSE);
12756 SendToProgram("force\n", &first);
12757 if (startedFromSetupPosition) {
12758 SendBoard(&first, forwardMostMove);
12759 if (appData.debugMode) {
12760 fprintf(debugFP, "Load Game\n");
12762 DisplayBothClocks();
12766 /* [HGM] server: flag to write setup moves in broadcast file as one */
12767 loadFlag = appData.suppressLoadMoves;
12769 while (cm == Comment) {
12771 if (appData.debugMode)
12772 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12774 AppendComment(currentMove, p, FALSE);
12775 yyboardindex = forwardMostMove;
12776 cm = (ChessMove) Myylex();
12779 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12780 cm == WhiteWins || cm == BlackWins ||
12781 cm == GameIsDrawn || cm == GameUnfinished) {
12782 DisplayMessage("", _("No moves in game"));
12783 if (cmailMsgLoaded) {
12784 if (appData.debugMode)
12785 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12789 DrawPosition(FALSE, boards[currentMove]);
12790 DisplayBothClocks();
12791 gameMode = EditGame;
12798 // [HGM] PV info: routine tests if comment empty
12799 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12800 DisplayComment(currentMove - 1, commentList[currentMove]);
12802 if (!matchMode && appData.timeDelay != 0)
12803 DrawPosition(FALSE, boards[currentMove]);
12805 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12806 programStats.ok_to_send = 1;
12809 /* if the first token after the PGN tags is a move
12810 * and not move number 1, retrieve it from the parser
12812 if (cm != MoveNumberOne)
12813 LoadGameOneMove(cm);
12815 /* load the remaining moves from the file */
12816 while (LoadGameOneMove(EndOfFile)) {
12817 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12818 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12821 /* rewind to the start of the game */
12822 currentMove = backwardMostMove;
12824 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12826 if (oldGameMode == AnalyzeFile) {
12827 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12828 AnalyzeFileEvent();
12830 if (oldGameMode == AnalyzeMode) {
12831 AnalyzeFileEvent();
12834 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12835 long int w, b; // [HGM] adjourn: restore saved clock times
12836 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12837 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12838 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12839 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12843 if(creatingBook) return TRUE;
12844 if (!matchMode && pos > 0) {
12845 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12847 if (matchMode || appData.timeDelay == 0) {
12849 } else if (appData.timeDelay > 0) {
12850 AutoPlayGameLoop();
12853 if (appData.debugMode)
12854 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12856 loadFlag = 0; /* [HGM] true game starts */
12860 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12862 ReloadPosition (int offset)
12864 int positionNumber = lastLoadPositionNumber + offset;
12865 if (lastLoadPositionFP == NULL) {
12866 DisplayError(_("No position has been loaded yet"), 0);
12869 if (positionNumber <= 0) {
12870 DisplayError(_("Can't back up any further"), 0);
12873 return LoadPosition(lastLoadPositionFP, positionNumber,
12874 lastLoadPositionTitle);
12877 /* Load the nth position from the given file */
12879 LoadPositionFromFile (char *filename, int n, char *title)
12884 if (strcmp(filename, "-") == 0) {
12885 return LoadPosition(stdin, n, "stdin");
12887 f = fopen(filename, "rb");
12889 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12890 DisplayError(buf, errno);
12893 return LoadPosition(f, n, title);
12898 /* Load the nth position from the given open file, and close it */
12900 LoadPosition (FILE *f, int positionNumber, char *title)
12902 char *p, line[MSG_SIZ];
12903 Board initial_position;
12904 int i, j, fenMode, pn;
12906 if (gameMode == Training )
12907 SetTrainingModeOff();
12909 if (gameMode != BeginningOfGame) {
12910 Reset(FALSE, TRUE);
12912 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12913 fclose(lastLoadPositionFP);
12915 if (positionNumber == 0) positionNumber = 1;
12916 lastLoadPositionFP = f;
12917 lastLoadPositionNumber = positionNumber;
12918 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12919 if (first.pr == NoProc && !appData.noChessProgram) {
12920 StartChessProgram(&first);
12921 InitChessProgram(&first, FALSE);
12923 pn = positionNumber;
12924 if (positionNumber < 0) {
12925 /* Negative position number means to seek to that byte offset */
12926 if (fseek(f, -positionNumber, 0) == -1) {
12927 DisplayError(_("Can't seek on position file"), 0);
12932 if (fseek(f, 0, 0) == -1) {
12933 if (f == lastLoadPositionFP ?
12934 positionNumber == lastLoadPositionNumber + 1 :
12935 positionNumber == 1) {
12938 DisplayError(_("Can't seek on position file"), 0);
12943 /* See if this file is FEN or old-style xboard */
12944 if (fgets(line, MSG_SIZ, f) == NULL) {
12945 DisplayError(_("Position not found in file"), 0);
12948 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12949 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12952 if (fenMode || line[0] == '#') pn--;
12954 /* skip positions before number pn */
12955 if (fgets(line, MSG_SIZ, f) == NULL) {
12957 DisplayError(_("Position not found in file"), 0);
12960 if (fenMode || line[0] == '#') pn--;
12965 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12966 DisplayError(_("Bad FEN position in file"), 0);
12970 (void) fgets(line, MSG_SIZ, f);
12971 (void) fgets(line, MSG_SIZ, f);
12973 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12974 (void) fgets(line, MSG_SIZ, f);
12975 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12978 initial_position[i][j++] = CharToPiece(*p);
12982 blackPlaysFirst = FALSE;
12984 (void) fgets(line, MSG_SIZ, f);
12985 if (strncmp(line, "black", strlen("black"))==0)
12986 blackPlaysFirst = TRUE;
12989 startedFromSetupPosition = TRUE;
12991 CopyBoard(boards[0], initial_position);
12992 if (blackPlaysFirst) {
12993 currentMove = forwardMostMove = backwardMostMove = 1;
12994 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12995 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12996 CopyBoard(boards[1], initial_position);
12997 DisplayMessage("", _("Black to play"));
12999 currentMove = forwardMostMove = backwardMostMove = 0;
13000 DisplayMessage("", _("White to play"));
13002 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13003 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13004 SendToProgram("force\n", &first);
13005 SendBoard(&first, forwardMostMove);
13007 if (appData.debugMode) {
13009 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13010 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13011 fprintf(debugFP, "Load Position\n");
13014 if (positionNumber > 1) {
13015 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13016 DisplayTitle(line);
13018 DisplayTitle(title);
13020 gameMode = EditGame;
13023 timeRemaining[0][1] = whiteTimeRemaining;
13024 timeRemaining[1][1] = blackTimeRemaining;
13025 DrawPosition(FALSE, boards[currentMove]);
13032 CopyPlayerNameIntoFileName (char **dest, char *src)
13034 while (*src != NULLCHAR && *src != ',') {
13039 *(*dest)++ = *src++;
13045 DefaultFileName (char *ext)
13047 static char def[MSG_SIZ];
13050 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13052 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13054 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13056 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13063 /* Save the current game to the given file */
13065 SaveGameToFile (char *filename, int append)
13069 int result, i, t,tot=0;
13071 if (strcmp(filename, "-") == 0) {
13072 return SaveGame(stdout, 0, NULL);
13074 for(i=0; i<10; i++) { // upto 10 tries
13075 f = fopen(filename, append ? "a" : "w");
13076 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13077 if(f || errno != 13) break;
13078 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13082 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13083 DisplayError(buf, errno);
13086 safeStrCpy(buf, lastMsg, MSG_SIZ);
13087 DisplayMessage(_("Waiting for access to save file"), "");
13088 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13089 DisplayMessage(_("Saving game"), "");
13090 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13091 result = SaveGame(f, 0, NULL);
13092 DisplayMessage(buf, "");
13099 SavePart (char *str)
13101 static char buf[MSG_SIZ];
13104 p = strchr(str, ' ');
13105 if (p == NULL) return str;
13106 strncpy(buf, str, p - str);
13107 buf[p - str] = NULLCHAR;
13111 #define PGN_MAX_LINE 75
13113 #define PGN_SIDE_WHITE 0
13114 #define PGN_SIDE_BLACK 1
13117 FindFirstMoveOutOfBook (int side)
13121 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13122 int index = backwardMostMove;
13123 int has_book_hit = 0;
13125 if( (index % 2) != side ) {
13129 while( index < forwardMostMove ) {
13130 /* Check to see if engine is in book */
13131 int depth = pvInfoList[index].depth;
13132 int score = pvInfoList[index].score;
13138 else if( score == 0 && depth == 63 ) {
13139 in_book = 1; /* Zappa */
13141 else if( score == 2 && depth == 99 ) {
13142 in_book = 1; /* Abrok */
13145 has_book_hit += in_book;
13161 GetOutOfBookInfo (char * buf)
13165 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13167 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13168 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13172 if( oob[0] >= 0 || oob[1] >= 0 ) {
13173 for( i=0; i<2; i++ ) {
13177 if( i > 0 && oob[0] >= 0 ) {
13178 strcat( buf, " " );
13181 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13182 sprintf( buf+strlen(buf), "%s%.2f",
13183 pvInfoList[idx].score >= 0 ? "+" : "",
13184 pvInfoList[idx].score / 100.0 );
13190 /* Save game in PGN style and close the file */
13192 SaveGamePGN (FILE *f)
13194 int i, offset, linelen, newblock;
13197 int movelen, numlen, blank;
13198 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13200 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13202 PrintPGNTags(f, &gameInfo);
13204 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13206 if (backwardMostMove > 0 || startedFromSetupPosition) {
13207 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13208 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13209 fprintf(f, "\n{--------------\n");
13210 PrintPosition(f, backwardMostMove);
13211 fprintf(f, "--------------}\n");
13215 /* [AS] Out of book annotation */
13216 if( appData.saveOutOfBookInfo ) {
13219 GetOutOfBookInfo( buf );
13221 if( buf[0] != '\0' ) {
13222 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13229 i = backwardMostMove;
13233 while (i < forwardMostMove) {
13234 /* Print comments preceding this move */
13235 if (commentList[i] != NULL) {
13236 if (linelen > 0) fprintf(f, "\n");
13237 fprintf(f, "%s", commentList[i]);
13242 /* Format move number */
13244 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13247 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13249 numtext[0] = NULLCHAR;
13251 numlen = strlen(numtext);
13254 /* Print move number */
13255 blank = linelen > 0 && numlen > 0;
13256 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13265 fprintf(f, "%s", numtext);
13269 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13270 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13273 blank = linelen > 0 && movelen > 0;
13274 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13283 fprintf(f, "%s", move_buffer);
13284 linelen += movelen;
13286 /* [AS] Add PV info if present */
13287 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13288 /* [HGM] add time */
13289 char buf[MSG_SIZ]; int seconds;
13291 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13297 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13300 seconds = (seconds + 4)/10; // round to full seconds
13302 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13304 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13307 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13308 pvInfoList[i].score >= 0 ? "+" : "",
13309 pvInfoList[i].score / 100.0,
13310 pvInfoList[i].depth,
13313 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13315 /* Print score/depth */
13316 blank = linelen > 0 && movelen > 0;
13317 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13326 fprintf(f, "%s", move_buffer);
13327 linelen += movelen;
13333 /* Start a new line */
13334 if (linelen > 0) fprintf(f, "\n");
13336 /* Print comments after last move */
13337 if (commentList[i] != NULL) {
13338 fprintf(f, "%s\n", commentList[i]);
13342 if (gameInfo.resultDetails != NULL &&
13343 gameInfo.resultDetails[0] != NULLCHAR) {
13344 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13345 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13346 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13347 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13348 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13350 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13354 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13358 /* Save game in old style and close the file */
13360 SaveGameOldStyle (FILE *f)
13365 tm = time((time_t *) NULL);
13367 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13370 if (backwardMostMove > 0 || startedFromSetupPosition) {
13371 fprintf(f, "\n[--------------\n");
13372 PrintPosition(f, backwardMostMove);
13373 fprintf(f, "--------------]\n");
13378 i = backwardMostMove;
13379 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13381 while (i < forwardMostMove) {
13382 if (commentList[i] != NULL) {
13383 fprintf(f, "[%s]\n", commentList[i]);
13386 if ((i % 2) == 1) {
13387 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13390 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13392 if (commentList[i] != NULL) {
13396 if (i >= forwardMostMove) {
13400 fprintf(f, "%s\n", parseList[i]);
13405 if (commentList[i] != NULL) {
13406 fprintf(f, "[%s]\n", commentList[i]);
13409 /* This isn't really the old style, but it's close enough */
13410 if (gameInfo.resultDetails != NULL &&
13411 gameInfo.resultDetails[0] != NULLCHAR) {
13412 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13413 gameInfo.resultDetails);
13415 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13422 /* Save the current game to open file f and close the file */
13424 SaveGame (FILE *f, int dummy, char *dummy2)
13426 if (gameMode == EditPosition) EditPositionDone(TRUE);
13427 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13428 if (appData.oldSaveStyle)
13429 return SaveGameOldStyle(f);
13431 return SaveGamePGN(f);
13434 /* Save the current position to the given file */
13436 SavePositionToFile (char *filename)
13441 if (strcmp(filename, "-") == 0) {
13442 return SavePosition(stdout, 0, NULL);
13444 f = fopen(filename, "a");
13446 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13447 DisplayError(buf, errno);
13450 safeStrCpy(buf, lastMsg, MSG_SIZ);
13451 DisplayMessage(_("Waiting for access to save file"), "");
13452 flock(fileno(f), LOCK_EX); // [HGM] lock
13453 DisplayMessage(_("Saving position"), "");
13454 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13455 SavePosition(f, 0, NULL);
13456 DisplayMessage(buf, "");
13462 /* Save the current position to the given open file and close the file */
13464 SavePosition (FILE *f, int dummy, char *dummy2)
13469 if (gameMode == EditPosition) EditPositionDone(TRUE);
13470 if (appData.oldSaveStyle) {
13471 tm = time((time_t *) NULL);
13473 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13475 fprintf(f, "[--------------\n");
13476 PrintPosition(f, currentMove);
13477 fprintf(f, "--------------]\n");
13479 fen = PositionToFEN(currentMove, NULL, 1);
13480 fprintf(f, "%s\n", fen);
13488 ReloadCmailMsgEvent (int unregister)
13491 static char *inFilename = NULL;
13492 static char *outFilename;
13494 struct stat inbuf, outbuf;
13497 /* Any registered moves are unregistered if unregister is set, */
13498 /* i.e. invoked by the signal handler */
13500 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13501 cmailMoveRegistered[i] = FALSE;
13502 if (cmailCommentList[i] != NULL) {
13503 free(cmailCommentList[i]);
13504 cmailCommentList[i] = NULL;
13507 nCmailMovesRegistered = 0;
13510 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13511 cmailResult[i] = CMAIL_NOT_RESULT;
13515 if (inFilename == NULL) {
13516 /* Because the filenames are static they only get malloced once */
13517 /* and they never get freed */
13518 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13519 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13521 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13522 sprintf(outFilename, "%s.out", appData.cmailGameName);
13525 status = stat(outFilename, &outbuf);
13527 cmailMailedMove = FALSE;
13529 status = stat(inFilename, &inbuf);
13530 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13533 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13534 counts the games, notes how each one terminated, etc.
13536 It would be nice to remove this kludge and instead gather all
13537 the information while building the game list. (And to keep it
13538 in the game list nodes instead of having a bunch of fixed-size
13539 parallel arrays.) Note this will require getting each game's
13540 termination from the PGN tags, as the game list builder does
13541 not process the game moves. --mann
13543 cmailMsgLoaded = TRUE;
13544 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13546 /* Load first game in the file or popup game menu */
13547 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13549 #endif /* !WIN32 */
13557 char string[MSG_SIZ];
13559 if ( cmailMailedMove
13560 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13561 return TRUE; /* Allow free viewing */
13564 /* Unregister move to ensure that we don't leave RegisterMove */
13565 /* with the move registered when the conditions for registering no */
13567 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13568 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13569 nCmailMovesRegistered --;
13571 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13573 free(cmailCommentList[lastLoadGameNumber - 1]);
13574 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13578 if (cmailOldMove == -1) {
13579 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13583 if (currentMove > cmailOldMove + 1) {
13584 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13588 if (currentMove < cmailOldMove) {
13589 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13593 if (forwardMostMove > currentMove) {
13594 /* Silently truncate extra moves */
13598 if ( (currentMove == cmailOldMove + 1)
13599 || ( (currentMove == cmailOldMove)
13600 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13601 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13602 if (gameInfo.result != GameUnfinished) {
13603 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13606 if (commentList[currentMove] != NULL) {
13607 cmailCommentList[lastLoadGameNumber - 1]
13608 = StrSave(commentList[currentMove]);
13610 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13612 if (appData.debugMode)
13613 fprintf(debugFP, "Saving %s for game %d\n",
13614 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13616 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13618 f = fopen(string, "w");
13619 if (appData.oldSaveStyle) {
13620 SaveGameOldStyle(f); /* also closes the file */
13622 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13623 f = fopen(string, "w");
13624 SavePosition(f, 0, NULL); /* also closes the file */
13626 fprintf(f, "{--------------\n");
13627 PrintPosition(f, currentMove);
13628 fprintf(f, "--------------}\n\n");
13630 SaveGame(f, 0, NULL); /* also closes the file*/
13633 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13634 nCmailMovesRegistered ++;
13635 } else if (nCmailGames == 1) {
13636 DisplayError(_("You have not made a move yet"), 0);
13647 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13648 FILE *commandOutput;
13649 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13650 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13656 if (! cmailMsgLoaded) {
13657 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13661 if (nCmailGames == nCmailResults) {
13662 DisplayError(_("No unfinished games"), 0);
13666 #if CMAIL_PROHIBIT_REMAIL
13667 if (cmailMailedMove) {
13668 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);
13669 DisplayError(msg, 0);
13674 if (! (cmailMailedMove || RegisterMove())) return;
13676 if ( cmailMailedMove
13677 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13678 snprintf(string, MSG_SIZ, partCommandString,
13679 appData.debugMode ? " -v" : "", appData.cmailGameName);
13680 commandOutput = popen(string, "r");
13682 if (commandOutput == NULL) {
13683 DisplayError(_("Failed to invoke cmail"), 0);
13685 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13686 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13688 if (nBuffers > 1) {
13689 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13690 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13691 nBytes = MSG_SIZ - 1;
13693 (void) memcpy(msg, buffer, nBytes);
13695 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13697 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13698 cmailMailedMove = TRUE; /* Prevent >1 moves */
13701 for (i = 0; i < nCmailGames; i ++) {
13702 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13707 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13709 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13711 appData.cmailGameName,
13713 LoadGameFromFile(buffer, 1, buffer, FALSE);
13714 cmailMsgLoaded = FALSE;
13718 DisplayInformation(msg);
13719 pclose(commandOutput);
13722 if ((*cmailMsg) != '\0') {
13723 DisplayInformation(cmailMsg);
13728 #endif /* !WIN32 */
13737 int prependComma = 0;
13739 char string[MSG_SIZ]; /* Space for game-list */
13742 if (!cmailMsgLoaded) return "";
13744 if (cmailMailedMove) {
13745 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13747 /* Create a list of games left */
13748 snprintf(string, MSG_SIZ, "[");
13749 for (i = 0; i < nCmailGames; i ++) {
13750 if (! ( cmailMoveRegistered[i]
13751 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13752 if (prependComma) {
13753 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13755 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13759 strcat(string, number);
13762 strcat(string, "]");
13764 if (nCmailMovesRegistered + nCmailResults == 0) {
13765 switch (nCmailGames) {
13767 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13771 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13775 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13780 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13782 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13787 if (nCmailResults == nCmailGames) {
13788 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13790 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13795 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13807 if (gameMode == Training)
13808 SetTrainingModeOff();
13811 cmailMsgLoaded = FALSE;
13812 if (appData.icsActive) {
13813 SendToICS(ics_prefix);
13814 SendToICS("refresh\n");
13819 ExitEvent (int status)
13823 /* Give up on clean exit */
13827 /* Keep trying for clean exit */
13831 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13833 if (telnetISR != NULL) {
13834 RemoveInputSource(telnetISR);
13836 if (icsPR != NoProc) {
13837 DestroyChildProcess(icsPR, TRUE);
13840 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13841 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13843 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13844 /* make sure this other one finishes before killing it! */
13845 if(endingGame) { int count = 0;
13846 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13847 while(endingGame && count++ < 10) DoSleep(1);
13848 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13851 /* Kill off chess programs */
13852 if (first.pr != NoProc) {
13855 DoSleep( appData.delayBeforeQuit );
13856 SendToProgram("quit\n", &first);
13857 DoSleep( appData.delayAfterQuit );
13858 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13860 if (second.pr != NoProc) {
13861 DoSleep( appData.delayBeforeQuit );
13862 SendToProgram("quit\n", &second);
13863 DoSleep( appData.delayAfterQuit );
13864 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13866 if (first.isr != NULL) {
13867 RemoveInputSource(first.isr);
13869 if (second.isr != NULL) {
13870 RemoveInputSource(second.isr);
13873 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13874 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13876 ShutDownFrontEnd();
13881 PauseEngine (ChessProgramState *cps)
13883 SendToProgram("pause\n", cps);
13888 UnPauseEngine (ChessProgramState *cps)
13890 SendToProgram("resume\n", cps);
13897 if (appData.debugMode)
13898 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13902 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13904 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13905 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13906 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13908 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13909 HandleMachineMove(stashedInputMove, stalledEngine);
13910 stalledEngine = NULL;
13913 if (gameMode == MachinePlaysWhite ||
13914 gameMode == TwoMachinesPlay ||
13915 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13916 if(first.pause) UnPauseEngine(&first);
13917 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13918 if(second.pause) UnPauseEngine(&second);
13919 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13922 DisplayBothClocks();
13924 if (gameMode == PlayFromGameFile) {
13925 if (appData.timeDelay >= 0)
13926 AutoPlayGameLoop();
13927 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13928 Reset(FALSE, TRUE);
13929 SendToICS(ics_prefix);
13930 SendToICS("refresh\n");
13931 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13932 ForwardInner(forwardMostMove);
13934 pauseExamInvalid = FALSE;
13936 switch (gameMode) {
13940 pauseExamForwardMostMove = forwardMostMove;
13941 pauseExamInvalid = FALSE;
13944 case IcsPlayingWhite:
13945 case IcsPlayingBlack:
13949 case PlayFromGameFile:
13950 (void) StopLoadGameTimer();
13954 case BeginningOfGame:
13955 if (appData.icsActive) return;
13956 /* else fall through */
13957 case MachinePlaysWhite:
13958 case MachinePlaysBlack:
13959 case TwoMachinesPlay:
13960 if (forwardMostMove == 0)
13961 return; /* don't pause if no one has moved */
13962 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13963 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13964 if(onMove->pause) { // thinking engine can be paused
13965 PauseEngine(onMove); // do it
13966 if(onMove->other->pause) // pondering opponent can always be paused immediately
13967 PauseEngine(onMove->other);
13969 SendToProgram("easy\n", onMove->other);
13971 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13972 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13974 PauseEngine(&first);
13976 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13977 } else { // human on move, pause pondering by either method
13979 PauseEngine(&first);
13980 else if(appData.ponderNextMove)
13981 SendToProgram("easy\n", &first);
13984 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13994 EditCommentEvent ()
13996 char title[MSG_SIZ];
13998 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13999 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14001 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14002 WhiteOnMove(currentMove - 1) ? " " : ".. ",
14003 parseList[currentMove - 1]);
14006 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14013 char *tags = PGNTags(&gameInfo);
14015 EditTagsPopUp(tags, NULL);
14022 if(second.analyzing) {
14023 SendToProgram("exit\n", &second);
14024 second.analyzing = FALSE;
14026 if (second.pr == NoProc) StartChessProgram(&second);
14027 InitChessProgram(&second, FALSE);
14028 FeedMovesToProgram(&second, currentMove);
14030 SendToProgram("analyze\n", &second);
14031 second.analyzing = TRUE;
14035 /* Toggle ShowThinking */
14037 ToggleShowThinking()
14039 appData.showThinking = !appData.showThinking;
14040 ShowThinkingEvent();
14044 AnalyzeModeEvent ()
14048 if (!first.analysisSupport) {
14049 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14050 DisplayError(buf, 0);
14053 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14054 if (appData.icsActive) {
14055 if (gameMode != IcsObserving) {
14056 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14057 DisplayError(buf, 0);
14059 if (appData.icsEngineAnalyze) {
14060 if (appData.debugMode)
14061 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14067 /* if enable, user wants to disable icsEngineAnalyze */
14068 if (appData.icsEngineAnalyze) {
14073 appData.icsEngineAnalyze = TRUE;
14074 if (appData.debugMode)
14075 fprintf(debugFP, "ICS engine analyze starting... \n");
14078 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14079 if (appData.noChessProgram || gameMode == AnalyzeMode)
14082 if (gameMode != AnalyzeFile) {
14083 if (!appData.icsEngineAnalyze) {
14085 if (gameMode != EditGame) return 0;
14087 if (!appData.showThinking) ToggleShowThinking();
14088 ResurrectChessProgram();
14089 SendToProgram("analyze\n", &first);
14090 first.analyzing = TRUE;
14091 /*first.maybeThinking = TRUE;*/
14092 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14093 EngineOutputPopUp();
14095 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14100 StartAnalysisClock();
14101 GetTimeMark(&lastNodeCountTime);
14107 AnalyzeFileEvent ()
14109 if (appData.noChessProgram || gameMode == AnalyzeFile)
14112 if (!first.analysisSupport) {
14114 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14115 DisplayError(buf, 0);
14119 if (gameMode != AnalyzeMode) {
14120 keepInfo = 1; // mere annotating should not alter PGN tags
14123 if (gameMode != EditGame) return;
14124 if (!appData.showThinking) ToggleShowThinking();
14125 ResurrectChessProgram();
14126 SendToProgram("analyze\n", &first);
14127 first.analyzing = TRUE;
14128 /*first.maybeThinking = TRUE;*/
14129 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14130 EngineOutputPopUp();
14132 gameMode = AnalyzeFile;
14136 StartAnalysisClock();
14137 GetTimeMark(&lastNodeCountTime);
14139 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14140 AnalysisPeriodicEvent(1);
14144 MachineWhiteEvent ()
14147 char *bookHit = NULL;
14149 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14153 if (gameMode == PlayFromGameFile ||
14154 gameMode == TwoMachinesPlay ||
14155 gameMode == Training ||
14156 gameMode == AnalyzeMode ||
14157 gameMode == EndOfGame)
14160 if (gameMode == EditPosition)
14161 EditPositionDone(TRUE);
14163 if (!WhiteOnMove(currentMove)) {
14164 DisplayError(_("It is not White's turn"), 0);
14168 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14171 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14172 gameMode == AnalyzeFile)
14175 ResurrectChessProgram(); /* in case it isn't running */
14176 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14177 gameMode = MachinePlaysWhite;
14180 gameMode = MachinePlaysWhite;
14184 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14186 if (first.sendName) {
14187 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14188 SendToProgram(buf, &first);
14190 if (first.sendTime) {
14191 if (first.useColors) {
14192 SendToProgram("black\n", &first); /*gnu kludge*/
14194 SendTimeRemaining(&first, TRUE);
14196 if (first.useColors) {
14197 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14199 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14200 SetMachineThinkingEnables();
14201 first.maybeThinking = TRUE;
14205 if (appData.autoFlipView && !flipView) {
14206 flipView = !flipView;
14207 DrawPosition(FALSE, NULL);
14208 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14211 if(bookHit) { // [HGM] book: simulate book reply
14212 static char bookMove[MSG_SIZ]; // a bit generous?
14214 programStats.nodes = programStats.depth = programStats.time =
14215 programStats.score = programStats.got_only_move = 0;
14216 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14218 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14219 strcat(bookMove, bookHit);
14220 HandleMachineMove(bookMove, &first);
14225 MachineBlackEvent ()
14228 char *bookHit = NULL;
14230 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14234 if (gameMode == PlayFromGameFile ||
14235 gameMode == TwoMachinesPlay ||
14236 gameMode == Training ||
14237 gameMode == AnalyzeMode ||
14238 gameMode == EndOfGame)
14241 if (gameMode == EditPosition)
14242 EditPositionDone(TRUE);
14244 if (WhiteOnMove(currentMove)) {
14245 DisplayError(_("It is not Black's turn"), 0);
14249 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14252 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14253 gameMode == AnalyzeFile)
14256 ResurrectChessProgram(); /* in case it isn't running */
14257 gameMode = MachinePlaysBlack;
14261 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14263 if (first.sendName) {
14264 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14265 SendToProgram(buf, &first);
14267 if (first.sendTime) {
14268 if (first.useColors) {
14269 SendToProgram("white\n", &first); /*gnu kludge*/
14271 SendTimeRemaining(&first, FALSE);
14273 if (first.useColors) {
14274 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14276 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14277 SetMachineThinkingEnables();
14278 first.maybeThinking = TRUE;
14281 if (appData.autoFlipView && flipView) {
14282 flipView = !flipView;
14283 DrawPosition(FALSE, NULL);
14284 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14286 if(bookHit) { // [HGM] book: simulate book reply
14287 static char bookMove[MSG_SIZ]; // a bit generous?
14289 programStats.nodes = programStats.depth = programStats.time =
14290 programStats.score = programStats.got_only_move = 0;
14291 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14293 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14294 strcat(bookMove, bookHit);
14295 HandleMachineMove(bookMove, &first);
14301 DisplayTwoMachinesTitle ()
14304 if (appData.matchGames > 0) {
14305 if(appData.tourneyFile[0]) {
14306 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14307 gameInfo.white, _("vs."), gameInfo.black,
14308 nextGame+1, appData.matchGames+1,
14309 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14311 if (first.twoMachinesColor[0] == 'w') {
14312 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14313 gameInfo.white, _("vs."), gameInfo.black,
14314 first.matchWins, second.matchWins,
14315 matchGame - 1 - (first.matchWins + second.matchWins));
14317 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14318 gameInfo.white, _("vs."), gameInfo.black,
14319 second.matchWins, first.matchWins,
14320 matchGame - 1 - (first.matchWins + second.matchWins));
14323 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14329 SettingsMenuIfReady ()
14331 if (second.lastPing != second.lastPong) {
14332 DisplayMessage("", _("Waiting for second chess program"));
14333 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14337 DisplayMessage("", "");
14338 SettingsPopUp(&second);
14342 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14345 if (cps->pr == NoProc) {
14346 StartChessProgram(cps);
14347 if (cps->protocolVersion == 1) {
14349 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14351 /* kludge: allow timeout for initial "feature" command */
14352 if(retry != TwoMachinesEventIfReady) FreezeUI();
14353 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14354 DisplayMessage("", buf);
14355 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14363 TwoMachinesEvent P((void))
14367 ChessProgramState *onmove;
14368 char *bookHit = NULL;
14369 static int stalling = 0;
14373 if (appData.noChessProgram) return;
14375 switch (gameMode) {
14376 case TwoMachinesPlay:
14378 case MachinePlaysWhite:
14379 case MachinePlaysBlack:
14380 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14381 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14385 case BeginningOfGame:
14386 case PlayFromGameFile:
14389 if (gameMode != EditGame) return;
14392 EditPositionDone(TRUE);
14403 // forwardMostMove = currentMove;
14404 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14405 startingEngine = TRUE;
14407 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14409 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14410 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14411 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14414 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14416 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14417 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14418 startingEngine = FALSE;
14419 DisplayError("second engine does not play this", 0);
14424 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14425 SendToProgram("force\n", &second);
14427 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14430 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14431 if(appData.matchPause>10000 || appData.matchPause<10)
14432 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14433 wait = SubtractTimeMarks(&now, &pauseStart);
14434 if(wait < appData.matchPause) {
14435 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14438 // we are now committed to starting the game
14440 DisplayMessage("", "");
14441 if (startedFromSetupPosition) {
14442 SendBoard(&second, backwardMostMove);
14443 if (appData.debugMode) {
14444 fprintf(debugFP, "Two Machines\n");
14447 for (i = backwardMostMove; i < forwardMostMove; i++) {
14448 SendMoveToProgram(i, &second);
14451 gameMode = TwoMachinesPlay;
14452 pausing = startingEngine = FALSE;
14453 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14455 DisplayTwoMachinesTitle();
14457 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14462 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14463 SendToProgram(first.computerString, &first);
14464 if (first.sendName) {
14465 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14466 SendToProgram(buf, &first);
14468 SendToProgram(second.computerString, &second);
14469 if (second.sendName) {
14470 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14471 SendToProgram(buf, &second);
14475 if (!first.sendTime || !second.sendTime) {
14476 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14477 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14479 if (onmove->sendTime) {
14480 if (onmove->useColors) {
14481 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14483 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14485 if (onmove->useColors) {
14486 SendToProgram(onmove->twoMachinesColor, onmove);
14488 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14489 // SendToProgram("go\n", onmove);
14490 onmove->maybeThinking = TRUE;
14491 SetMachineThinkingEnables();
14495 if(bookHit) { // [HGM] book: simulate book reply
14496 static char bookMove[MSG_SIZ]; // a bit generous?
14498 programStats.nodes = programStats.depth = programStats.time =
14499 programStats.score = programStats.got_only_move = 0;
14500 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14502 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14503 strcat(bookMove, bookHit);
14504 savedMessage = bookMove; // args for deferred call
14505 savedState = onmove;
14506 ScheduleDelayedEvent(DeferredBookMove, 1);
14513 if (gameMode == Training) {
14514 SetTrainingModeOff();
14515 gameMode = PlayFromGameFile;
14516 DisplayMessage("", _("Training mode off"));
14518 gameMode = Training;
14519 animateTraining = appData.animate;
14521 /* make sure we are not already at the end of the game */
14522 if (currentMove < forwardMostMove) {
14523 SetTrainingModeOn();
14524 DisplayMessage("", _("Training mode on"));
14526 gameMode = PlayFromGameFile;
14527 DisplayError(_("Already at end of game"), 0);
14536 if (!appData.icsActive) return;
14537 switch (gameMode) {
14538 case IcsPlayingWhite:
14539 case IcsPlayingBlack:
14542 case BeginningOfGame:
14550 EditPositionDone(TRUE);
14563 gameMode = IcsIdle;
14573 switch (gameMode) {
14575 SetTrainingModeOff();
14577 case MachinePlaysWhite:
14578 case MachinePlaysBlack:
14579 case BeginningOfGame:
14580 SendToProgram("force\n", &first);
14581 SetUserThinkingEnables();
14583 case PlayFromGameFile:
14584 (void) StopLoadGameTimer();
14585 if (gameFileFP != NULL) {
14590 EditPositionDone(TRUE);
14595 SendToProgram("force\n", &first);
14597 case TwoMachinesPlay:
14598 GameEnds(EndOfFile, NULL, GE_PLAYER);
14599 ResurrectChessProgram();
14600 SetUserThinkingEnables();
14603 ResurrectChessProgram();
14605 case IcsPlayingBlack:
14606 case IcsPlayingWhite:
14607 DisplayError(_("Warning: You are still playing a game"), 0);
14610 DisplayError(_("Warning: You are still observing a game"), 0);
14613 DisplayError(_("Warning: You are still examining a game"), 0);
14624 first.offeredDraw = second.offeredDraw = 0;
14626 if (gameMode == PlayFromGameFile) {
14627 whiteTimeRemaining = timeRemaining[0][currentMove];
14628 blackTimeRemaining = timeRemaining[1][currentMove];
14632 if (gameMode == MachinePlaysWhite ||
14633 gameMode == MachinePlaysBlack ||
14634 gameMode == TwoMachinesPlay ||
14635 gameMode == EndOfGame) {
14636 i = forwardMostMove;
14637 while (i > currentMove) {
14638 SendToProgram("undo\n", &first);
14641 if(!adjustedClock) {
14642 whiteTimeRemaining = timeRemaining[0][currentMove];
14643 blackTimeRemaining = timeRemaining[1][currentMove];
14644 DisplayBothClocks();
14646 if (whiteFlag || blackFlag) {
14647 whiteFlag = blackFlag = 0;
14652 gameMode = EditGame;
14659 EditPositionEvent ()
14661 if (gameMode == EditPosition) {
14667 if (gameMode != EditGame) return;
14669 gameMode = EditPosition;
14672 if (currentMove > 0)
14673 CopyBoard(boards[0], boards[currentMove]);
14675 blackPlaysFirst = !WhiteOnMove(currentMove);
14677 currentMove = forwardMostMove = backwardMostMove = 0;
14678 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14680 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14686 /* [DM] icsEngineAnalyze - possible call from other functions */
14687 if (appData.icsEngineAnalyze) {
14688 appData.icsEngineAnalyze = FALSE;
14690 DisplayMessage("",_("Close ICS engine analyze..."));
14692 if (first.analysisSupport && first.analyzing) {
14693 SendToBoth("exit\n");
14694 first.analyzing = second.analyzing = FALSE;
14696 thinkOutput[0] = NULLCHAR;
14700 EditPositionDone (Boolean fakeRights)
14702 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14704 startedFromSetupPosition = TRUE;
14705 InitChessProgram(&first, FALSE);
14706 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14707 boards[0][EP_STATUS] = EP_NONE;
14708 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14709 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14710 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14711 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14712 } else boards[0][CASTLING][2] = NoRights;
14713 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14714 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14715 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14716 } else boards[0][CASTLING][5] = NoRights;
14717 if(gameInfo.variant == VariantSChess) {
14719 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14720 boards[0][VIRGIN][i] = 0;
14721 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14722 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14726 SendToProgram("force\n", &first);
14727 if (blackPlaysFirst) {
14728 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14729 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14730 currentMove = forwardMostMove = backwardMostMove = 1;
14731 CopyBoard(boards[1], boards[0]);
14733 currentMove = forwardMostMove = backwardMostMove = 0;
14735 SendBoard(&first, forwardMostMove);
14736 if (appData.debugMode) {
14737 fprintf(debugFP, "EditPosDone\n");
14740 DisplayMessage("", "");
14741 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14742 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14743 gameMode = EditGame;
14745 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14746 ClearHighlights(); /* [AS] */
14749 /* Pause for `ms' milliseconds */
14750 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14752 TimeDelay (long ms)
14759 } while (SubtractTimeMarks(&m2, &m1) < ms);
14762 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14764 SendMultiLineToICS (char *buf)
14766 char temp[MSG_SIZ+1], *p;
14773 strncpy(temp, buf, len);
14778 if (*p == '\n' || *p == '\r')
14783 strcat(temp, "\n");
14785 SendToPlayer(temp, strlen(temp));
14789 SetWhiteToPlayEvent ()
14791 if (gameMode == EditPosition) {
14792 blackPlaysFirst = FALSE;
14793 DisplayBothClocks(); /* works because currentMove is 0 */
14794 } else if (gameMode == IcsExamining) {
14795 SendToICS(ics_prefix);
14796 SendToICS("tomove white\n");
14801 SetBlackToPlayEvent ()
14803 if (gameMode == EditPosition) {
14804 blackPlaysFirst = TRUE;
14805 currentMove = 1; /* kludge */
14806 DisplayBothClocks();
14808 } else if (gameMode == IcsExamining) {
14809 SendToICS(ics_prefix);
14810 SendToICS("tomove black\n");
14815 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14818 ChessSquare piece = boards[0][y][x];
14819 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14820 static int lastVariant;
14822 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14824 switch (selection) {
14826 CopyBoard(currentBoard, boards[0]);
14827 CopyBoard(menuBoard, initialPosition);
14828 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14829 SendToICS(ics_prefix);
14830 SendToICS("bsetup clear\n");
14831 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14832 SendToICS(ics_prefix);
14833 SendToICS("clearboard\n");
14836 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14837 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14838 for (y = 0; y < BOARD_HEIGHT; y++) {
14839 if (gameMode == IcsExamining) {
14840 if (boards[currentMove][y][x] != EmptySquare) {
14841 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14846 if(boards[0][y][x] != p) nonEmpty++;
14847 boards[0][y][x] = p;
14850 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14852 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14853 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
14854 ChessSquare p = menuBoard[0][x];
14855 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14856 p = menuBoard[BOARD_HEIGHT-1][x];
14857 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14859 DisplayMessage("Clicking clock again restores position", "");
14860 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14861 if(!nonEmpty) { // asked to clear an empty board
14862 CopyBoard(boards[0], menuBoard);
14864 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14865 CopyBoard(boards[0], initialPosition);
14867 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14868 && !CompareBoards(nullBoard, erasedBoard)) {
14869 CopyBoard(boards[0], erasedBoard);
14871 CopyBoard(erasedBoard, currentBoard);
14875 if (gameMode == EditPosition) {
14876 DrawPosition(FALSE, boards[0]);
14881 SetWhiteToPlayEvent();
14885 SetBlackToPlayEvent();
14889 if (gameMode == IcsExamining) {
14890 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14891 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14894 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14895 if(x == BOARD_LEFT-2) {
14896 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14897 boards[0][y][1] = 0;
14899 if(x == BOARD_RGHT+1) {
14900 if(y >= gameInfo.holdingsSize) break;
14901 boards[0][y][BOARD_WIDTH-2] = 0;
14904 boards[0][y][x] = EmptySquare;
14905 DrawPosition(FALSE, boards[0]);
14910 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14911 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14912 selection = (ChessSquare) (PROMOTED piece);
14913 } else if(piece == EmptySquare) selection = WhiteSilver;
14914 else selection = (ChessSquare)((int)piece - 1);
14918 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14919 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14920 selection = (ChessSquare) (DEMOTED piece);
14921 } else if(piece == EmptySquare) selection = BlackSilver;
14922 else selection = (ChessSquare)((int)piece + 1);
14927 if(gameInfo.variant == VariantShatranj ||
14928 gameInfo.variant == VariantXiangqi ||
14929 gameInfo.variant == VariantCourier ||
14930 gameInfo.variant == VariantASEAN ||
14931 gameInfo.variant == VariantMakruk )
14932 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14937 if(gameInfo.variant == VariantXiangqi)
14938 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14939 if(gameInfo.variant == VariantKnightmate)
14940 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14943 if (gameMode == IcsExamining) {
14944 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14945 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14946 PieceToChar(selection), AAA + x, ONE + y);
14949 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14951 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14952 n = PieceToNumber(selection - BlackPawn);
14953 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14954 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14955 boards[0][BOARD_HEIGHT-1-n][1]++;
14957 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14958 n = PieceToNumber(selection);
14959 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14960 boards[0][n][BOARD_WIDTH-1] = selection;
14961 boards[0][n][BOARD_WIDTH-2]++;
14964 boards[0][y][x] = selection;
14965 DrawPosition(TRUE, boards[0]);
14967 fromX = fromY = -1;
14975 DropMenuEvent (ChessSquare selection, int x, int y)
14977 ChessMove moveType;
14979 switch (gameMode) {
14980 case IcsPlayingWhite:
14981 case MachinePlaysBlack:
14982 if (!WhiteOnMove(currentMove)) {
14983 DisplayMoveError(_("It is Black's turn"));
14986 moveType = WhiteDrop;
14988 case IcsPlayingBlack:
14989 case MachinePlaysWhite:
14990 if (WhiteOnMove(currentMove)) {
14991 DisplayMoveError(_("It is White's turn"));
14994 moveType = BlackDrop;
14997 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15003 if (moveType == BlackDrop && selection < BlackPawn) {
15004 selection = (ChessSquare) ((int) selection
15005 + (int) BlackPawn - (int) WhitePawn);
15007 if (boards[currentMove][y][x] != EmptySquare) {
15008 DisplayMoveError(_("That square is occupied"));
15012 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15018 /* Accept a pending offer of any kind from opponent */
15020 if (appData.icsActive) {
15021 SendToICS(ics_prefix);
15022 SendToICS("accept\n");
15023 } else if (cmailMsgLoaded) {
15024 if (currentMove == cmailOldMove &&
15025 commentList[cmailOldMove] != NULL &&
15026 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15027 "Black offers a draw" : "White offers a draw")) {
15029 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15030 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15032 DisplayError(_("There is no pending offer on this move"), 0);
15033 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15036 /* Not used for offers from chess program */
15043 /* Decline a pending offer of any kind from opponent */
15045 if (appData.icsActive) {
15046 SendToICS(ics_prefix);
15047 SendToICS("decline\n");
15048 } else if (cmailMsgLoaded) {
15049 if (currentMove == cmailOldMove &&
15050 commentList[cmailOldMove] != NULL &&
15051 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15052 "Black offers a draw" : "White offers a draw")) {
15054 AppendComment(cmailOldMove, "Draw declined", TRUE);
15055 DisplayComment(cmailOldMove - 1, "Draw declined");
15058 DisplayError(_("There is no pending offer on this move"), 0);
15061 /* Not used for offers from chess program */
15068 /* Issue ICS rematch command */
15069 if (appData.icsActive) {
15070 SendToICS(ics_prefix);
15071 SendToICS("rematch\n");
15078 /* Call your opponent's flag (claim a win on time) */
15079 if (appData.icsActive) {
15080 SendToICS(ics_prefix);
15081 SendToICS("flag\n");
15083 switch (gameMode) {
15086 case MachinePlaysWhite:
15089 GameEnds(GameIsDrawn, "Both players ran out of time",
15092 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15094 DisplayError(_("Your opponent is not out of time"), 0);
15097 case MachinePlaysBlack:
15100 GameEnds(GameIsDrawn, "Both players ran out of time",
15103 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15105 DisplayError(_("Your opponent is not out of time"), 0);
15113 ClockClick (int which)
15114 { // [HGM] code moved to back-end from winboard.c
15115 if(which) { // black clock
15116 if (gameMode == EditPosition || gameMode == IcsExamining) {
15117 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15118 SetBlackToPlayEvent();
15119 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15120 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15121 } else if (shiftKey) {
15122 AdjustClock(which, -1);
15123 } else if (gameMode == IcsPlayingWhite ||
15124 gameMode == MachinePlaysBlack) {
15127 } else { // white clock
15128 if (gameMode == EditPosition || gameMode == IcsExamining) {
15129 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15130 SetWhiteToPlayEvent();
15131 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15132 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15133 } else if (shiftKey) {
15134 AdjustClock(which, -1);
15135 } else if (gameMode == IcsPlayingBlack ||
15136 gameMode == MachinePlaysWhite) {
15145 /* Offer draw or accept pending draw offer from opponent */
15147 if (appData.icsActive) {
15148 /* Note: tournament rules require draw offers to be
15149 made after you make your move but before you punch
15150 your clock. Currently ICS doesn't let you do that;
15151 instead, you immediately punch your clock after making
15152 a move, but you can offer a draw at any time. */
15154 SendToICS(ics_prefix);
15155 SendToICS("draw\n");
15156 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15157 } else if (cmailMsgLoaded) {
15158 if (currentMove == cmailOldMove &&
15159 commentList[cmailOldMove] != NULL &&
15160 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15161 "Black offers a draw" : "White offers a draw")) {
15162 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15163 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15164 } else if (currentMove == cmailOldMove + 1) {
15165 char *offer = WhiteOnMove(cmailOldMove) ?
15166 "White offers a draw" : "Black offers a draw";
15167 AppendComment(currentMove, offer, TRUE);
15168 DisplayComment(currentMove - 1, offer);
15169 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15171 DisplayError(_("You must make your move before offering a draw"), 0);
15172 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15174 } else if (first.offeredDraw) {
15175 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15177 if (first.sendDrawOffers) {
15178 SendToProgram("draw\n", &first);
15179 userOfferedDraw = TRUE;
15187 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15189 if (appData.icsActive) {
15190 SendToICS(ics_prefix);
15191 SendToICS("adjourn\n");
15193 /* Currently GNU Chess doesn't offer or accept Adjourns */
15201 /* Offer Abort or accept pending Abort offer from opponent */
15203 if (appData.icsActive) {
15204 SendToICS(ics_prefix);
15205 SendToICS("abort\n");
15207 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15214 /* Resign. You can do this even if it's not your turn. */
15216 if (appData.icsActive) {
15217 SendToICS(ics_prefix);
15218 SendToICS("resign\n");
15220 switch (gameMode) {
15221 case MachinePlaysWhite:
15222 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15224 case MachinePlaysBlack:
15225 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15228 if (cmailMsgLoaded) {
15230 if (WhiteOnMove(cmailOldMove)) {
15231 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15233 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15235 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15246 StopObservingEvent ()
15248 /* Stop observing current games */
15249 SendToICS(ics_prefix);
15250 SendToICS("unobserve\n");
15254 StopExaminingEvent ()
15256 /* Stop observing current game */
15257 SendToICS(ics_prefix);
15258 SendToICS("unexamine\n");
15262 ForwardInner (int target)
15264 int limit; int oldSeekGraphUp = seekGraphUp;
15266 if (appData.debugMode)
15267 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15268 target, currentMove, forwardMostMove);
15270 if (gameMode == EditPosition)
15273 seekGraphUp = FALSE;
15274 MarkTargetSquares(1);
15276 if (gameMode == PlayFromGameFile && !pausing)
15279 if (gameMode == IcsExamining && pausing)
15280 limit = pauseExamForwardMostMove;
15282 limit = forwardMostMove;
15284 if (target > limit) target = limit;
15286 if (target > 0 && moveList[target - 1][0]) {
15287 int fromX, fromY, toX, toY;
15288 toX = moveList[target - 1][2] - AAA;
15289 toY = moveList[target - 1][3] - ONE;
15290 if (moveList[target - 1][1] == '@') {
15291 if (appData.highlightLastMove) {
15292 SetHighlights(-1, -1, toX, toY);
15295 fromX = moveList[target - 1][0] - AAA;
15296 fromY = moveList[target - 1][1] - ONE;
15297 if (target == currentMove + 1) {
15298 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15300 if (appData.highlightLastMove) {
15301 SetHighlights(fromX, fromY, toX, toY);
15305 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15306 gameMode == Training || gameMode == PlayFromGameFile ||
15307 gameMode == AnalyzeFile) {
15308 while (currentMove < target) {
15309 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15310 SendMoveToProgram(currentMove++, &first);
15313 currentMove = target;
15316 if (gameMode == EditGame || gameMode == EndOfGame) {
15317 whiteTimeRemaining = timeRemaining[0][currentMove];
15318 blackTimeRemaining = timeRemaining[1][currentMove];
15320 DisplayBothClocks();
15321 DisplayMove(currentMove - 1);
15322 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15323 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15324 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15325 DisplayComment(currentMove - 1, commentList[currentMove]);
15327 ClearMap(); // [HGM] exclude: invalidate map
15334 if (gameMode == IcsExamining && !pausing) {
15335 SendToICS(ics_prefix);
15336 SendToICS("forward\n");
15338 ForwardInner(currentMove + 1);
15345 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15346 /* to optimze, we temporarily turn off analysis mode while we feed
15347 * the remaining moves to the engine. Otherwise we get analysis output
15350 if (first.analysisSupport) {
15351 SendToProgram("exit\nforce\n", &first);
15352 first.analyzing = FALSE;
15356 if (gameMode == IcsExamining && !pausing) {
15357 SendToICS(ics_prefix);
15358 SendToICS("forward 999999\n");
15360 ForwardInner(forwardMostMove);
15363 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15364 /* we have fed all the moves, so reactivate analysis mode */
15365 SendToProgram("analyze\n", &first);
15366 first.analyzing = TRUE;
15367 /*first.maybeThinking = TRUE;*/
15368 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15373 BackwardInner (int target)
15375 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15377 if (appData.debugMode)
15378 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15379 target, currentMove, forwardMostMove);
15381 if (gameMode == EditPosition) return;
15382 seekGraphUp = FALSE;
15383 MarkTargetSquares(1);
15384 if (currentMove <= backwardMostMove) {
15386 DrawPosition(full_redraw, boards[currentMove]);
15389 if (gameMode == PlayFromGameFile && !pausing)
15392 if (moveList[target][0]) {
15393 int fromX, fromY, toX, toY;
15394 toX = moveList[target][2] - AAA;
15395 toY = moveList[target][3] - ONE;
15396 if (moveList[target][1] == '@') {
15397 if (appData.highlightLastMove) {
15398 SetHighlights(-1, -1, toX, toY);
15401 fromX = moveList[target][0] - AAA;
15402 fromY = moveList[target][1] - ONE;
15403 if (target == currentMove - 1) {
15404 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15406 if (appData.highlightLastMove) {
15407 SetHighlights(fromX, fromY, toX, toY);
15411 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15412 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15413 while (currentMove > target) {
15414 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15415 // null move cannot be undone. Reload program with move history before it.
15417 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15418 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15420 SendBoard(&first, i);
15421 if(second.analyzing) SendBoard(&second, i);
15422 for(currentMove=i; currentMove<target; currentMove++) {
15423 SendMoveToProgram(currentMove, &first);
15424 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15428 SendToBoth("undo\n");
15432 currentMove = target;
15435 if (gameMode == EditGame || gameMode == EndOfGame) {
15436 whiteTimeRemaining = timeRemaining[0][currentMove];
15437 blackTimeRemaining = timeRemaining[1][currentMove];
15439 DisplayBothClocks();
15440 DisplayMove(currentMove - 1);
15441 DrawPosition(full_redraw, boards[currentMove]);
15442 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15443 // [HGM] PV info: routine tests if comment empty
15444 DisplayComment(currentMove - 1, commentList[currentMove]);
15445 ClearMap(); // [HGM] exclude: invalidate map
15451 if (gameMode == IcsExamining && !pausing) {
15452 SendToICS(ics_prefix);
15453 SendToICS("backward\n");
15455 BackwardInner(currentMove - 1);
15462 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15463 /* to optimize, we temporarily turn off analysis mode while we undo
15464 * all the moves. Otherwise we get analysis output after each undo.
15466 if (first.analysisSupport) {
15467 SendToProgram("exit\nforce\n", &first);
15468 first.analyzing = FALSE;
15472 if (gameMode == IcsExamining && !pausing) {
15473 SendToICS(ics_prefix);
15474 SendToICS("backward 999999\n");
15476 BackwardInner(backwardMostMove);
15479 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15480 /* we have fed all the moves, so reactivate analysis mode */
15481 SendToProgram("analyze\n", &first);
15482 first.analyzing = TRUE;
15483 /*first.maybeThinking = TRUE;*/
15484 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15491 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15492 if (to >= forwardMostMove) to = forwardMostMove;
15493 if (to <= backwardMostMove) to = backwardMostMove;
15494 if (to < currentMove) {
15502 RevertEvent (Boolean annotate)
15504 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15507 if (gameMode != IcsExamining) {
15508 DisplayError(_("You are not examining a game"), 0);
15512 DisplayError(_("You can't revert while pausing"), 0);
15515 SendToICS(ics_prefix);
15516 SendToICS("revert\n");
15520 RetractMoveEvent ()
15522 switch (gameMode) {
15523 case MachinePlaysWhite:
15524 case MachinePlaysBlack:
15525 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15526 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15529 if (forwardMostMove < 2) return;
15530 currentMove = forwardMostMove = forwardMostMove - 2;
15531 whiteTimeRemaining = timeRemaining[0][currentMove];
15532 blackTimeRemaining = timeRemaining[1][currentMove];
15533 DisplayBothClocks();
15534 DisplayMove(currentMove - 1);
15535 ClearHighlights();/*!! could figure this out*/
15536 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15537 SendToProgram("remove\n", &first);
15538 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15541 case BeginningOfGame:
15545 case IcsPlayingWhite:
15546 case IcsPlayingBlack:
15547 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15548 SendToICS(ics_prefix);
15549 SendToICS("takeback 2\n");
15551 SendToICS(ics_prefix);
15552 SendToICS("takeback 1\n");
15561 ChessProgramState *cps;
15563 switch (gameMode) {
15564 case MachinePlaysWhite:
15565 if (!WhiteOnMove(forwardMostMove)) {
15566 DisplayError(_("It is your turn"), 0);
15571 case MachinePlaysBlack:
15572 if (WhiteOnMove(forwardMostMove)) {
15573 DisplayError(_("It is your turn"), 0);
15578 case TwoMachinesPlay:
15579 if (WhiteOnMove(forwardMostMove) ==
15580 (first.twoMachinesColor[0] == 'w')) {
15586 case BeginningOfGame:
15590 SendToProgram("?\n", cps);
15594 TruncateGameEvent ()
15597 if (gameMode != EditGame) return;
15604 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15605 if (forwardMostMove > currentMove) {
15606 if (gameInfo.resultDetails != NULL) {
15607 free(gameInfo.resultDetails);
15608 gameInfo.resultDetails = NULL;
15609 gameInfo.result = GameUnfinished;
15611 forwardMostMove = currentMove;
15612 HistorySet(parseList, backwardMostMove, forwardMostMove,
15620 if (appData.noChessProgram) return;
15621 switch (gameMode) {
15622 case MachinePlaysWhite:
15623 if (WhiteOnMove(forwardMostMove)) {
15624 DisplayError(_("Wait until your turn."), 0);
15628 case BeginningOfGame:
15629 case MachinePlaysBlack:
15630 if (!WhiteOnMove(forwardMostMove)) {
15631 DisplayError(_("Wait until your turn."), 0);
15636 DisplayError(_("No hint available"), 0);
15639 SendToProgram("hint\n", &first);
15640 hintRequested = TRUE;
15646 ListGame * lg = (ListGame *) gameList.head;
15649 static int secondTime = FALSE;
15651 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15652 DisplayError(_("Game list not loaded or empty"), 0);
15656 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15659 DisplayNote(_("Book file exists! Try again for overwrite."));
15663 creatingBook = TRUE;
15664 secondTime = FALSE;
15666 /* Get list size */
15667 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15668 LoadGame(f, nItem, "", TRUE);
15669 AddGameToBook(TRUE);
15670 lg = (ListGame *) lg->node.succ;
15673 creatingBook = FALSE;
15680 if (appData.noChessProgram) return;
15681 switch (gameMode) {
15682 case MachinePlaysWhite:
15683 if (WhiteOnMove(forwardMostMove)) {
15684 DisplayError(_("Wait until your turn."), 0);
15688 case BeginningOfGame:
15689 case MachinePlaysBlack:
15690 if (!WhiteOnMove(forwardMostMove)) {
15691 DisplayError(_("Wait until your turn."), 0);
15696 EditPositionDone(TRUE);
15698 case TwoMachinesPlay:
15703 SendToProgram("bk\n", &first);
15704 bookOutput[0] = NULLCHAR;
15705 bookRequested = TRUE;
15711 char *tags = PGNTags(&gameInfo);
15712 TagsPopUp(tags, CmailMsg());
15716 /* end button procedures */
15719 PrintPosition (FILE *fp, int move)
15723 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15724 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15725 char c = PieceToChar(boards[move][i][j]);
15726 fputc(c == 'x' ? '.' : c, fp);
15727 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15730 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15731 fprintf(fp, "white to play\n");
15733 fprintf(fp, "black to play\n");
15737 PrintOpponents (FILE *fp)
15739 if (gameInfo.white != NULL) {
15740 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15746 /* Find last component of program's own name, using some heuristics */
15748 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15751 int local = (strcmp(host, "localhost") == 0);
15752 while (!local && (p = strchr(prog, ';')) != NULL) {
15754 while (*p == ' ') p++;
15757 if (*prog == '"' || *prog == '\'') {
15758 q = strchr(prog + 1, *prog);
15760 q = strchr(prog, ' ');
15762 if (q == NULL) q = prog + strlen(prog);
15764 while (p >= prog && *p != '/' && *p != '\\') p--;
15766 if(p == prog && *p == '"') p++;
15768 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15769 memcpy(buf, p, q - p);
15770 buf[q - p] = NULLCHAR;
15778 TimeControlTagValue ()
15781 if (!appData.clockMode) {
15782 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15783 } else if (movesPerSession > 0) {
15784 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15785 } else if (timeIncrement == 0) {
15786 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15788 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15790 return StrSave(buf);
15796 /* This routine is used only for certain modes */
15797 VariantClass v = gameInfo.variant;
15798 ChessMove r = GameUnfinished;
15801 if(keepInfo) return;
15803 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15804 r = gameInfo.result;
15805 p = gameInfo.resultDetails;
15806 gameInfo.resultDetails = NULL;
15808 ClearGameInfo(&gameInfo);
15809 gameInfo.variant = v;
15811 switch (gameMode) {
15812 case MachinePlaysWhite:
15813 gameInfo.event = StrSave( appData.pgnEventHeader );
15814 gameInfo.site = StrSave(HostName());
15815 gameInfo.date = PGNDate();
15816 gameInfo.round = StrSave("-");
15817 gameInfo.white = StrSave(first.tidy);
15818 gameInfo.black = StrSave(UserName());
15819 gameInfo.timeControl = TimeControlTagValue();
15822 case MachinePlaysBlack:
15823 gameInfo.event = StrSave( appData.pgnEventHeader );
15824 gameInfo.site = StrSave(HostName());
15825 gameInfo.date = PGNDate();
15826 gameInfo.round = StrSave("-");
15827 gameInfo.white = StrSave(UserName());
15828 gameInfo.black = StrSave(first.tidy);
15829 gameInfo.timeControl = TimeControlTagValue();
15832 case TwoMachinesPlay:
15833 gameInfo.event = StrSave( appData.pgnEventHeader );
15834 gameInfo.site = StrSave(HostName());
15835 gameInfo.date = PGNDate();
15838 snprintf(buf, MSG_SIZ, "%d", roundNr);
15839 gameInfo.round = StrSave(buf);
15841 gameInfo.round = StrSave("-");
15843 if (first.twoMachinesColor[0] == 'w') {
15844 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15845 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15847 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15848 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15850 gameInfo.timeControl = TimeControlTagValue();
15854 gameInfo.event = StrSave("Edited game");
15855 gameInfo.site = StrSave(HostName());
15856 gameInfo.date = PGNDate();
15857 gameInfo.round = StrSave("-");
15858 gameInfo.white = StrSave("-");
15859 gameInfo.black = StrSave("-");
15860 gameInfo.result = r;
15861 gameInfo.resultDetails = p;
15865 gameInfo.event = StrSave("Edited position");
15866 gameInfo.site = StrSave(HostName());
15867 gameInfo.date = PGNDate();
15868 gameInfo.round = StrSave("-");
15869 gameInfo.white = StrSave("-");
15870 gameInfo.black = StrSave("-");
15873 case IcsPlayingWhite:
15874 case IcsPlayingBlack:
15879 case PlayFromGameFile:
15880 gameInfo.event = StrSave("Game from non-PGN file");
15881 gameInfo.site = StrSave(HostName());
15882 gameInfo.date = PGNDate();
15883 gameInfo.round = StrSave("-");
15884 gameInfo.white = StrSave("?");
15885 gameInfo.black = StrSave("?");
15894 ReplaceComment (int index, char *text)
15900 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15901 pvInfoList[index-1].depth == len &&
15902 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15903 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15904 while (*text == '\n') text++;
15905 len = strlen(text);
15906 while (len > 0 && text[len - 1] == '\n') len--;
15908 if (commentList[index] != NULL)
15909 free(commentList[index]);
15912 commentList[index] = NULL;
15915 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15916 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15917 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15918 commentList[index] = (char *) malloc(len + 2);
15919 strncpy(commentList[index], text, len);
15920 commentList[index][len] = '\n';
15921 commentList[index][len + 1] = NULLCHAR;
15923 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15925 commentList[index] = (char *) malloc(len + 7);
15926 safeStrCpy(commentList[index], "{\n", 3);
15927 safeStrCpy(commentList[index]+2, text, len+1);
15928 commentList[index][len+2] = NULLCHAR;
15929 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15930 strcat(commentList[index], "\n}\n");
15935 CrushCRs (char *text)
15943 if (ch == '\r') continue;
15945 } while (ch != '\0');
15949 AppendComment (int index, char *text, Boolean addBraces)
15950 /* addBraces tells if we should add {} */
15955 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15956 if(addBraces == 3) addBraces = 0; else // force appending literally
15957 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15960 while (*text == '\n') text++;
15961 len = strlen(text);
15962 while (len > 0 && text[len - 1] == '\n') len--;
15963 text[len] = NULLCHAR;
15965 if (len == 0) return;
15967 if (commentList[index] != NULL) {
15968 Boolean addClosingBrace = addBraces;
15969 old = commentList[index];
15970 oldlen = strlen(old);
15971 while(commentList[index][oldlen-1] == '\n')
15972 commentList[index][--oldlen] = NULLCHAR;
15973 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15974 safeStrCpy(commentList[index], old, oldlen + len + 6);
15976 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15977 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15978 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15979 while (*text == '\n') { text++; len--; }
15980 commentList[index][--oldlen] = NULLCHAR;
15982 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15983 else strcat(commentList[index], "\n");
15984 strcat(commentList[index], text);
15985 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15986 else strcat(commentList[index], "\n");
15988 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15990 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15991 else commentList[index][0] = NULLCHAR;
15992 strcat(commentList[index], text);
15993 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15994 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15999 FindStr (char * text, char * sub_text)
16001 char * result = strstr( text, sub_text );
16003 if( result != NULL ) {
16004 result += strlen( sub_text );
16010 /* [AS] Try to extract PV info from PGN comment */
16011 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16013 GetInfoFromComment (int index, char * text)
16015 char * sep = text, *p;
16017 if( text != NULL && index > 0 ) {
16020 int time = -1, sec = 0, deci;
16021 char * s_eval = FindStr( text, "[%eval " );
16022 char * s_emt = FindStr( text, "[%emt " );
16024 if( s_eval != NULL || s_emt != NULL ) {
16026 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16031 if( s_eval != NULL ) {
16032 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16036 if( delim != ']' ) {
16041 if( s_emt != NULL ) {
16046 /* We expect something like: [+|-]nnn.nn/dd */
16049 if(*text != '{') return text; // [HGM] braces: must be normal comment
16051 sep = strchr( text, '/' );
16052 if( sep == NULL || sep < (text+4) ) {
16057 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16058 if(p[1] == '(') { // comment starts with PV
16059 p = strchr(p, ')'); // locate end of PV
16060 if(p == NULL || sep < p+5) return text;
16061 // at this point we have something like "{(.*) +0.23/6 ..."
16062 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16063 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16064 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16066 time = -1; sec = -1; deci = -1;
16067 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16068 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16069 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16070 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16074 if( score_lo < 0 || score_lo >= 100 ) {
16078 if(sec >= 0) time = 600*time + 10*sec; else
16079 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16081 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16083 /* [HGM] PV time: now locate end of PV info */
16084 while( *++sep >= '0' && *sep <= '9'); // strip depth
16086 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16088 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16090 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16091 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16102 pvInfoList[index-1].depth = depth;
16103 pvInfoList[index-1].score = score;
16104 pvInfoList[index-1].time = 10*time; // centi-sec
16105 if(*sep == '}') *sep = 0; else *--sep = '{';
16106 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16112 SendToProgram (char *message, ChessProgramState *cps)
16114 int count, outCount, error;
16117 if (cps->pr == NoProc) return;
16120 if (appData.debugMode) {
16123 fprintf(debugFP, "%ld >%-6s: %s",
16124 SubtractTimeMarks(&now, &programStartTime),
16125 cps->which, message);
16127 fprintf(serverFP, "%ld >%-6s: %s",
16128 SubtractTimeMarks(&now, &programStartTime),
16129 cps->which, message), fflush(serverFP);
16132 count = strlen(message);
16133 outCount = OutputToProcess(cps->pr, message, count, &error);
16134 if (outCount < count && !exiting
16135 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16136 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16137 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16138 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16139 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16140 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16141 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16142 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16144 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16145 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16146 gameInfo.result = res;
16148 gameInfo.resultDetails = StrSave(buf);
16150 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16151 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16156 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16160 ChessProgramState *cps = (ChessProgramState *)closure;
16162 if (isr != cps->isr) return; /* Killed intentionally */
16165 RemoveInputSource(cps->isr);
16166 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16167 _(cps->which), cps->program);
16168 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16169 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16170 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16171 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16172 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16173 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16175 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16176 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16177 gameInfo.result = res;
16179 gameInfo.resultDetails = StrSave(buf);
16181 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16182 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16184 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16185 _(cps->which), cps->program);
16186 RemoveInputSource(cps->isr);
16188 /* [AS] Program is misbehaving badly... kill it */
16189 if( count == -2 ) {
16190 DestroyChildProcess( cps->pr, 9 );
16194 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16199 if ((end_str = strchr(message, '\r')) != NULL)
16200 *end_str = NULLCHAR;
16201 if ((end_str = strchr(message, '\n')) != NULL)
16202 *end_str = NULLCHAR;
16204 if (appData.debugMode) {
16205 TimeMark now; int print = 1;
16206 char *quote = ""; char c; int i;
16208 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16209 char start = message[0];
16210 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16211 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16212 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16213 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16214 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16215 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16216 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16217 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16218 sscanf(message, "hint: %c", &c)!=1 &&
16219 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16220 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16221 print = (appData.engineComments >= 2);
16223 message[0] = start; // restore original message
16227 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16228 SubtractTimeMarks(&now, &programStartTime), cps->which,
16232 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16233 SubtractTimeMarks(&now, &programStartTime), cps->which,
16235 message), fflush(serverFP);
16239 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16240 if (appData.icsEngineAnalyze) {
16241 if (strstr(message, "whisper") != NULL ||
16242 strstr(message, "kibitz") != NULL ||
16243 strstr(message, "tellics") != NULL) return;
16246 HandleMachineMove(message, cps);
16251 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16256 if( timeControl_2 > 0 ) {
16257 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16258 tc = timeControl_2;
16261 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16262 inc /= cps->timeOdds;
16263 st /= cps->timeOdds;
16265 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16268 /* Set exact time per move, normally using st command */
16269 if (cps->stKludge) {
16270 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16272 if (seconds == 0) {
16273 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16275 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16278 snprintf(buf, MSG_SIZ, "st %d\n", st);
16281 /* Set conventional or incremental time control, using level command */
16282 if (seconds == 0) {
16283 /* Note old gnuchess bug -- minutes:seconds used to not work.
16284 Fixed in later versions, but still avoid :seconds
16285 when seconds is 0. */
16286 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16288 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16289 seconds, inc/1000.);
16292 SendToProgram(buf, cps);
16294 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16295 /* Orthogonally, limit search to given depth */
16297 if (cps->sdKludge) {
16298 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16300 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16302 SendToProgram(buf, cps);
16305 if(cps->nps >= 0) { /* [HGM] nps */
16306 if(cps->supportsNPS == FALSE)
16307 cps->nps = -1; // don't use if engine explicitly says not supported!
16309 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16310 SendToProgram(buf, cps);
16315 ChessProgramState *
16317 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16319 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16320 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16326 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16328 char message[MSG_SIZ];
16331 /* Note: this routine must be called when the clocks are stopped
16332 or when they have *just* been set or switched; otherwise
16333 it will be off by the time since the current tick started.
16335 if (machineWhite) {
16336 time = whiteTimeRemaining / 10;
16337 otime = blackTimeRemaining / 10;
16339 time = blackTimeRemaining / 10;
16340 otime = whiteTimeRemaining / 10;
16342 /* [HGM] translate opponent's time by time-odds factor */
16343 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16345 if (time <= 0) time = 1;
16346 if (otime <= 0) otime = 1;
16348 snprintf(message, MSG_SIZ, "time %ld\n", time);
16349 SendToProgram(message, cps);
16351 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16352 SendToProgram(message, cps);
16356 EngineDefinedVariant (ChessProgramState *cps, int n)
16357 { // return name of n-th unknown variant that engine supports
16358 static char buf[MSG_SIZ];
16359 char *p, *s = cps->variants;
16360 if(!s) return NULL;
16361 do { // parse string from variants feature
16363 p = strchr(s, ',');
16364 if(p) *p = NULLCHAR;
16365 v = StringToVariant(s);
16366 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16367 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16368 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16371 if(n < 0) return buf;
16377 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16380 int len = strlen(name);
16383 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16385 sscanf(*p, "%d", &val);
16387 while (**p && **p != ' ')
16389 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16390 SendToProgram(buf, cps);
16397 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16400 int len = strlen(name);
16401 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16403 sscanf(*p, "%d", loc);
16404 while (**p && **p != ' ') (*p)++;
16405 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16406 SendToProgram(buf, cps);
16413 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16416 int len = strlen(name);
16417 if (strncmp((*p), name, len) == 0
16418 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16420 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16421 sscanf(*p, "%[^\"]", *loc);
16422 while (**p && **p != '\"') (*p)++;
16423 if (**p == '\"') (*p)++;
16424 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16425 SendToProgram(buf, cps);
16432 ParseOption (Option *opt, ChessProgramState *cps)
16433 // [HGM] options: process the string that defines an engine option, and determine
16434 // name, type, default value, and allowed value range
16436 char *p, *q, buf[MSG_SIZ];
16437 int n, min = (-1)<<31, max = 1<<31, def;
16439 if(p = strstr(opt->name, " -spin ")) {
16440 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16441 if(max < min) max = min; // enforce consistency
16442 if(def < min) def = min;
16443 if(def > max) def = max;
16448 } else if((p = strstr(opt->name, " -slider "))) {
16449 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16450 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16451 if(max < min) max = min; // enforce consistency
16452 if(def < min) def = min;
16453 if(def > max) def = max;
16457 opt->type = Spin; // Slider;
16458 } else if((p = strstr(opt->name, " -string "))) {
16459 opt->textValue = p+9;
16460 opt->type = TextBox;
16461 } else if((p = strstr(opt->name, " -file "))) {
16462 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16463 opt->textValue = p+7;
16464 opt->type = FileName; // FileName;
16465 } else if((p = strstr(opt->name, " -path "))) {
16466 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16467 opt->textValue = p+7;
16468 opt->type = PathName; // PathName;
16469 } else if(p = strstr(opt->name, " -check ")) {
16470 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16471 opt->value = (def != 0);
16472 opt->type = CheckBox;
16473 } else if(p = strstr(opt->name, " -combo ")) {
16474 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16475 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16476 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16477 opt->value = n = 0;
16478 while(q = StrStr(q, " /// ")) {
16479 n++; *q = 0; // count choices, and null-terminate each of them
16481 if(*q == '*') { // remember default, which is marked with * prefix
16485 cps->comboList[cps->comboCnt++] = q;
16487 cps->comboList[cps->comboCnt++] = NULL;
16489 opt->type = ComboBox;
16490 } else if(p = strstr(opt->name, " -button")) {
16491 opt->type = Button;
16492 } else if(p = strstr(opt->name, " -save")) {
16493 opt->type = SaveButton;
16494 } else return FALSE;
16495 *p = 0; // terminate option name
16496 // now look if the command-line options define a setting for this engine option.
16497 if(cps->optionSettings && cps->optionSettings[0])
16498 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16499 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16500 snprintf(buf, MSG_SIZ, "option %s", p);
16501 if(p = strstr(buf, ",")) *p = 0;
16502 if(q = strchr(buf, '=')) switch(opt->type) {
16504 for(n=0; n<opt->max; n++)
16505 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16508 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16512 opt->value = atoi(q+1);
16517 SendToProgram(buf, cps);
16523 FeatureDone (ChessProgramState *cps, int val)
16525 DelayedEventCallback cb = GetDelayedEvent();
16526 if ((cb == InitBackEnd3 && cps == &first) ||
16527 (cb == SettingsMenuIfReady && cps == &second) ||
16528 (cb == LoadEngine) ||
16529 (cb == TwoMachinesEventIfReady)) {
16530 CancelDelayedEvent();
16531 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16533 cps->initDone = val;
16534 if(val) cps->reload = FALSE;
16537 /* Parse feature command from engine */
16539 ParseFeatures (char *args, ChessProgramState *cps)
16547 while (*p == ' ') p++;
16548 if (*p == NULLCHAR) return;
16550 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16551 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16552 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16553 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16554 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16555 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16556 if (BoolFeature(&p, "reuse", &val, cps)) {
16557 /* Engine can disable reuse, but can't enable it if user said no */
16558 if (!val) cps->reuse = FALSE;
16561 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16562 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16563 if (gameMode == TwoMachinesPlay) {
16564 DisplayTwoMachinesTitle();
16570 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16571 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16572 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16573 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16574 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16575 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16576 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16577 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16578 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16579 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16580 if (IntFeature(&p, "done", &val, cps)) {
16581 FeatureDone(cps, val);
16584 /* Added by Tord: */
16585 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16586 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16587 /* End of additions by Tord */
16589 /* [HGM] added features: */
16590 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16591 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16592 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16593 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16594 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16595 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16596 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16597 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16598 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16599 FREE(cps->option[cps->nrOptions].name);
16600 cps->option[cps->nrOptions].name = q; q = NULL;
16601 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16602 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16603 SendToProgram(buf, cps);
16606 if(cps->nrOptions >= MAX_OPTIONS) {
16608 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16609 DisplayError(buf, 0);
16613 /* End of additions by HGM */
16615 /* unknown feature: complain and skip */
16617 while (*q && *q != '=') q++;
16618 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16619 SendToProgram(buf, cps);
16625 while (*p && *p != '\"') p++;
16626 if (*p == '\"') p++;
16628 while (*p && *p != ' ') p++;
16636 PeriodicUpdatesEvent (int newState)
16638 if (newState == appData.periodicUpdates)
16641 appData.periodicUpdates=newState;
16643 /* Display type changes, so update it now */
16644 // DisplayAnalysis();
16646 /* Get the ball rolling again... */
16648 AnalysisPeriodicEvent(1);
16649 StartAnalysisClock();
16654 PonderNextMoveEvent (int newState)
16656 if (newState == appData.ponderNextMove) return;
16657 if (gameMode == EditPosition) EditPositionDone(TRUE);
16659 SendToProgram("hard\n", &first);
16660 if (gameMode == TwoMachinesPlay) {
16661 SendToProgram("hard\n", &second);
16664 SendToProgram("easy\n", &first);
16665 thinkOutput[0] = NULLCHAR;
16666 if (gameMode == TwoMachinesPlay) {
16667 SendToProgram("easy\n", &second);
16670 appData.ponderNextMove = newState;
16674 NewSettingEvent (int option, int *feature, char *command, int value)
16678 if (gameMode == EditPosition) EditPositionDone(TRUE);
16679 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16680 if(feature == NULL || *feature) SendToProgram(buf, &first);
16681 if (gameMode == TwoMachinesPlay) {
16682 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16687 ShowThinkingEvent ()
16688 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16690 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16691 int newState = appData.showThinking
16692 // [HGM] thinking: other features now need thinking output as well
16693 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16695 if (oldState == newState) return;
16696 oldState = newState;
16697 if (gameMode == EditPosition) EditPositionDone(TRUE);
16699 SendToProgram("post\n", &first);
16700 if (gameMode == TwoMachinesPlay) {
16701 SendToProgram("post\n", &second);
16704 SendToProgram("nopost\n", &first);
16705 thinkOutput[0] = NULLCHAR;
16706 if (gameMode == TwoMachinesPlay) {
16707 SendToProgram("nopost\n", &second);
16710 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16714 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16716 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16717 if (pr == NoProc) return;
16718 AskQuestion(title, question, replyPrefix, pr);
16722 TypeInEvent (char firstChar)
16724 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16725 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16726 gameMode == AnalyzeMode || gameMode == EditGame ||
16727 gameMode == EditPosition || gameMode == IcsExamining ||
16728 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16729 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16730 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16731 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16732 gameMode == Training) PopUpMoveDialog(firstChar);
16736 TypeInDoneEvent (char *move)
16739 int n, fromX, fromY, toX, toY;
16741 ChessMove moveType;
16744 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16745 EditPositionPasteFEN(move);
16748 // [HGM] movenum: allow move number to be typed in any mode
16749 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16753 // undocumented kludge: allow command-line option to be typed in!
16754 // (potentially fatal, and does not implement the effect of the option.)
16755 // should only be used for options that are values on which future decisions will be made,
16756 // and definitely not on options that would be used during initialization.
16757 if(strstr(move, "!!! -") == move) {
16758 ParseArgsFromString(move+4);
16762 if (gameMode != EditGame && currentMove != forwardMostMove &&
16763 gameMode != Training) {
16764 DisplayMoveError(_("Displayed move is not current"));
16766 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16767 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16768 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16769 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16770 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16771 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16773 DisplayMoveError(_("Could not parse move"));
16779 DisplayMove (int moveNumber)
16781 char message[MSG_SIZ];
16783 char cpThinkOutput[MSG_SIZ];
16785 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16787 if (moveNumber == forwardMostMove - 1 ||
16788 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16790 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16792 if (strchr(cpThinkOutput, '\n')) {
16793 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16796 *cpThinkOutput = NULLCHAR;
16799 /* [AS] Hide thinking from human user */
16800 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16801 *cpThinkOutput = NULLCHAR;
16802 if( thinkOutput[0] != NULLCHAR ) {
16805 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16806 cpThinkOutput[i] = '.';
16808 cpThinkOutput[i] = NULLCHAR;
16809 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16813 if (moveNumber == forwardMostMove - 1 &&
16814 gameInfo.resultDetails != NULL) {
16815 if (gameInfo.resultDetails[0] == NULLCHAR) {
16816 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16818 snprintf(res, MSG_SIZ, " {%s} %s",
16819 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16825 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16826 DisplayMessage(res, cpThinkOutput);
16828 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16829 WhiteOnMove(moveNumber) ? " " : ".. ",
16830 parseList[moveNumber], res);
16831 DisplayMessage(message, cpThinkOutput);
16836 DisplayComment (int moveNumber, char *text)
16838 char title[MSG_SIZ];
16840 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16841 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16843 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16844 WhiteOnMove(moveNumber) ? " " : ".. ",
16845 parseList[moveNumber]);
16847 if (text != NULL && (appData.autoDisplayComment || commentUp))
16848 CommentPopUp(title, text);
16851 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16852 * might be busy thinking or pondering. It can be omitted if your
16853 * gnuchess is configured to stop thinking immediately on any user
16854 * input. However, that gnuchess feature depends on the FIONREAD
16855 * ioctl, which does not work properly on some flavors of Unix.
16858 Attention (ChessProgramState *cps)
16861 if (!cps->useSigint) return;
16862 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16863 switch (gameMode) {
16864 case MachinePlaysWhite:
16865 case MachinePlaysBlack:
16866 case TwoMachinesPlay:
16867 case IcsPlayingWhite:
16868 case IcsPlayingBlack:
16871 /* Skip if we know it isn't thinking */
16872 if (!cps->maybeThinking) return;
16873 if (appData.debugMode)
16874 fprintf(debugFP, "Interrupting %s\n", cps->which);
16875 InterruptChildProcess(cps->pr);
16876 cps->maybeThinking = FALSE;
16881 #endif /*ATTENTION*/
16887 if (whiteTimeRemaining <= 0) {
16890 if (appData.icsActive) {
16891 if (appData.autoCallFlag &&
16892 gameMode == IcsPlayingBlack && !blackFlag) {
16893 SendToICS(ics_prefix);
16894 SendToICS("flag\n");
16898 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16900 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16901 if (appData.autoCallFlag) {
16902 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16909 if (blackTimeRemaining <= 0) {
16912 if (appData.icsActive) {
16913 if (appData.autoCallFlag &&
16914 gameMode == IcsPlayingWhite && !whiteFlag) {
16915 SendToICS(ics_prefix);
16916 SendToICS("flag\n");
16920 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16922 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16923 if (appData.autoCallFlag) {
16924 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16935 CheckTimeControl ()
16937 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16938 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16941 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16943 if ( !WhiteOnMove(forwardMostMove) ) {
16944 /* White made time control */
16945 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16946 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16947 /* [HGM] time odds: correct new time quota for time odds! */
16948 / WhitePlayer()->timeOdds;
16949 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16951 lastBlack -= blackTimeRemaining;
16952 /* Black made time control */
16953 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16954 / WhitePlayer()->other->timeOdds;
16955 lastWhite = whiteTimeRemaining;
16960 DisplayBothClocks ()
16962 int wom = gameMode == EditPosition ?
16963 !blackPlaysFirst : WhiteOnMove(currentMove);
16964 DisplayWhiteClock(whiteTimeRemaining, wom);
16965 DisplayBlackClock(blackTimeRemaining, !wom);
16969 /* Timekeeping seems to be a portability nightmare. I think everyone
16970 has ftime(), but I'm really not sure, so I'm including some ifdefs
16971 to use other calls if you don't. Clocks will be less accurate if
16972 you have neither ftime nor gettimeofday.
16975 /* VS 2008 requires the #include outside of the function */
16976 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16977 #include <sys/timeb.h>
16980 /* Get the current time as a TimeMark */
16982 GetTimeMark (TimeMark *tm)
16984 #if HAVE_GETTIMEOFDAY
16986 struct timeval timeVal;
16987 struct timezone timeZone;
16989 gettimeofday(&timeVal, &timeZone);
16990 tm->sec = (long) timeVal.tv_sec;
16991 tm->ms = (int) (timeVal.tv_usec / 1000L);
16993 #else /*!HAVE_GETTIMEOFDAY*/
16996 // include <sys/timeb.h> / moved to just above start of function
16997 struct timeb timeB;
17000 tm->sec = (long) timeB.time;
17001 tm->ms = (int) timeB.millitm;
17003 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17004 tm->sec = (long) time(NULL);
17010 /* Return the difference in milliseconds between two
17011 time marks. We assume the difference will fit in a long!
17014 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17016 return 1000L*(tm2->sec - tm1->sec) +
17017 (long) (tm2->ms - tm1->ms);
17022 * Code to manage the game clocks.
17024 * In tournament play, black starts the clock and then white makes a move.
17025 * We give the human user a slight advantage if he is playing white---the
17026 * clocks don't run until he makes his first move, so it takes zero time.
17027 * Also, we don't account for network lag, so we could get out of sync
17028 * with GNU Chess's clock -- but then, referees are always right.
17031 static TimeMark tickStartTM;
17032 static long intendedTickLength;
17035 NextTickLength (long timeRemaining)
17037 long nominalTickLength, nextTickLength;
17039 if (timeRemaining > 0L && timeRemaining <= 10000L)
17040 nominalTickLength = 100L;
17042 nominalTickLength = 1000L;
17043 nextTickLength = timeRemaining % nominalTickLength;
17044 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17046 return nextTickLength;
17049 /* Adjust clock one minute up or down */
17051 AdjustClock (Boolean which, int dir)
17053 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17054 if(which) blackTimeRemaining += 60000*dir;
17055 else whiteTimeRemaining += 60000*dir;
17056 DisplayBothClocks();
17057 adjustedClock = TRUE;
17060 /* Stop clocks and reset to a fresh time control */
17064 (void) StopClockTimer();
17065 if (appData.icsActive) {
17066 whiteTimeRemaining = blackTimeRemaining = 0;
17067 } else if (searchTime) {
17068 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17069 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17070 } else { /* [HGM] correct new time quote for time odds */
17071 whiteTC = blackTC = fullTimeControlString;
17072 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17073 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17075 if (whiteFlag || blackFlag) {
17077 whiteFlag = blackFlag = FALSE;
17079 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17080 DisplayBothClocks();
17081 adjustedClock = FALSE;
17084 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17086 /* Decrement running clock by amount of time that has passed */
17090 long timeRemaining;
17091 long lastTickLength, fudge;
17094 if (!appData.clockMode) return;
17095 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17099 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17101 /* Fudge if we woke up a little too soon */
17102 fudge = intendedTickLength - lastTickLength;
17103 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17105 if (WhiteOnMove(forwardMostMove)) {
17106 if(whiteNPS >= 0) lastTickLength = 0;
17107 timeRemaining = whiteTimeRemaining -= lastTickLength;
17108 if(timeRemaining < 0 && !appData.icsActive) {
17109 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17110 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17111 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17112 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17115 DisplayWhiteClock(whiteTimeRemaining - fudge,
17116 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17118 if(blackNPS >= 0) lastTickLength = 0;
17119 timeRemaining = blackTimeRemaining -= lastTickLength;
17120 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17121 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17123 blackStartMove = forwardMostMove;
17124 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17127 DisplayBlackClock(blackTimeRemaining - fudge,
17128 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17130 if (CheckFlags()) return;
17132 if(twoBoards) { // count down secondary board's clocks as well
17133 activePartnerTime -= lastTickLength;
17135 if(activePartner == 'W')
17136 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17138 DisplayBlackClock(activePartnerTime, TRUE);
17143 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17144 StartClockTimer(intendedTickLength);
17146 /* if the time remaining has fallen below the alarm threshold, sound the
17147 * alarm. if the alarm has sounded and (due to a takeback or time control
17148 * with increment) the time remaining has increased to a level above the
17149 * threshold, reset the alarm so it can sound again.
17152 if (appData.icsActive && appData.icsAlarm) {
17154 /* make sure we are dealing with the user's clock */
17155 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17156 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17159 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17160 alarmSounded = FALSE;
17161 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17163 alarmSounded = TRUE;
17169 /* A player has just moved, so stop the previously running
17170 clock and (if in clock mode) start the other one.
17171 We redisplay both clocks in case we're in ICS mode, because
17172 ICS gives us an update to both clocks after every move.
17173 Note that this routine is called *after* forwardMostMove
17174 is updated, so the last fractional tick must be subtracted
17175 from the color that is *not* on move now.
17178 SwitchClocks (int newMoveNr)
17180 long lastTickLength;
17182 int flagged = FALSE;
17186 if (StopClockTimer() && appData.clockMode) {
17187 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17188 if (!WhiteOnMove(forwardMostMove)) {
17189 if(blackNPS >= 0) lastTickLength = 0;
17190 blackTimeRemaining -= lastTickLength;
17191 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17192 // if(pvInfoList[forwardMostMove].time == -1)
17193 pvInfoList[forwardMostMove].time = // use GUI time
17194 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17196 if(whiteNPS >= 0) lastTickLength = 0;
17197 whiteTimeRemaining -= lastTickLength;
17198 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17199 // if(pvInfoList[forwardMostMove].time == -1)
17200 pvInfoList[forwardMostMove].time =
17201 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17203 flagged = CheckFlags();
17205 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17206 CheckTimeControl();
17208 if (flagged || !appData.clockMode) return;
17210 switch (gameMode) {
17211 case MachinePlaysBlack:
17212 case MachinePlaysWhite:
17213 case BeginningOfGame:
17214 if (pausing) return;
17218 case PlayFromGameFile:
17226 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17227 if(WhiteOnMove(forwardMostMove))
17228 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17229 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17233 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17234 whiteTimeRemaining : blackTimeRemaining);
17235 StartClockTimer(intendedTickLength);
17239 /* Stop both clocks */
17243 long lastTickLength;
17246 if (!StopClockTimer()) return;
17247 if (!appData.clockMode) return;
17251 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17252 if (WhiteOnMove(forwardMostMove)) {
17253 if(whiteNPS >= 0) lastTickLength = 0;
17254 whiteTimeRemaining -= lastTickLength;
17255 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17257 if(blackNPS >= 0) lastTickLength = 0;
17258 blackTimeRemaining -= lastTickLength;
17259 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17264 /* Start clock of player on move. Time may have been reset, so
17265 if clock is already running, stop and restart it. */
17269 (void) StopClockTimer(); /* in case it was running already */
17270 DisplayBothClocks();
17271 if (CheckFlags()) return;
17273 if (!appData.clockMode) return;
17274 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17276 GetTimeMark(&tickStartTM);
17277 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17278 whiteTimeRemaining : blackTimeRemaining);
17280 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17281 whiteNPS = blackNPS = -1;
17282 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17283 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17284 whiteNPS = first.nps;
17285 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17286 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17287 blackNPS = first.nps;
17288 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17289 whiteNPS = second.nps;
17290 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17291 blackNPS = second.nps;
17292 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17294 StartClockTimer(intendedTickLength);
17298 TimeString (long ms)
17300 long second, minute, hour, day;
17302 static char buf[32];
17304 if (ms > 0 && ms <= 9900) {
17305 /* convert milliseconds to tenths, rounding up */
17306 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17308 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17312 /* convert milliseconds to seconds, rounding up */
17313 /* use floating point to avoid strangeness of integer division
17314 with negative dividends on many machines */
17315 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17322 day = second / (60 * 60 * 24);
17323 second = second % (60 * 60 * 24);
17324 hour = second / (60 * 60);
17325 second = second % (60 * 60);
17326 minute = second / 60;
17327 second = second % 60;
17330 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17331 sign, day, hour, minute, second);
17333 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17335 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17342 * This is necessary because some C libraries aren't ANSI C compliant yet.
17345 StrStr (char *string, char *match)
17349 length = strlen(match);
17351 for (i = strlen(string) - length; i >= 0; i--, string++)
17352 if (!strncmp(match, string, length))
17359 StrCaseStr (char *string, char *match)
17363 length = strlen(match);
17365 for (i = strlen(string) - length; i >= 0; i--, string++) {
17366 for (j = 0; j < length; j++) {
17367 if (ToLower(match[j]) != ToLower(string[j]))
17370 if (j == length) return string;
17378 StrCaseCmp (char *s1, char *s2)
17383 c1 = ToLower(*s1++);
17384 c2 = ToLower(*s2++);
17385 if (c1 > c2) return 1;
17386 if (c1 < c2) return -1;
17387 if (c1 == NULLCHAR) return 0;
17395 return isupper(c) ? tolower(c) : c;
17402 return islower(c) ? toupper(c) : c;
17404 #endif /* !_amigados */
17411 if ((ret = (char *) malloc(strlen(s) + 1)))
17413 safeStrCpy(ret, s, strlen(s)+1);
17419 StrSavePtr (char *s, char **savePtr)
17424 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17425 safeStrCpy(*savePtr, s, strlen(s)+1);
17437 clock = time((time_t *)NULL);
17438 tm = localtime(&clock);
17439 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17440 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17441 return StrSave(buf);
17446 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17448 int i, j, fromX, fromY, toX, toY;
17455 whiteToPlay = (gameMode == EditPosition) ?
17456 !blackPlaysFirst : (move % 2 == 0);
17459 /* Piece placement data */
17460 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17461 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17463 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17464 if (boards[move][i][j] == EmptySquare) {
17466 } else { ChessSquare piece = boards[move][i][j];
17467 if (emptycount > 0) {
17468 if(emptycount<10) /* [HGM] can be >= 10 */
17469 *p++ = '0' + emptycount;
17470 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17473 if(PieceToChar(piece) == '+') {
17474 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17476 piece = (ChessSquare)(DEMOTED piece);
17478 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17480 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17481 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17486 if (emptycount > 0) {
17487 if(emptycount<10) /* [HGM] can be >= 10 */
17488 *p++ = '0' + emptycount;
17489 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17496 /* [HGM] print Crazyhouse or Shogi holdings */
17497 if( gameInfo.holdingsWidth ) {
17498 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17500 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17501 piece = boards[move][i][BOARD_WIDTH-1];
17502 if( piece != EmptySquare )
17503 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17504 *p++ = PieceToChar(piece);
17506 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17507 piece = boards[move][BOARD_HEIGHT-i-1][0];
17508 if( piece != EmptySquare )
17509 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17510 *p++ = PieceToChar(piece);
17513 if( q == p ) *p++ = '-';
17519 *p++ = whiteToPlay ? 'w' : 'b';
17522 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17523 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17525 if(nrCastlingRights) {
17527 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17528 /* [HGM] write directly from rights */
17529 if(boards[move][CASTLING][2] != NoRights &&
17530 boards[move][CASTLING][0] != NoRights )
17531 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17532 if(boards[move][CASTLING][2] != NoRights &&
17533 boards[move][CASTLING][1] != NoRights )
17534 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17535 if(boards[move][CASTLING][5] != NoRights &&
17536 boards[move][CASTLING][3] != NoRights )
17537 *p++ = boards[move][CASTLING][3] + AAA;
17538 if(boards[move][CASTLING][5] != NoRights &&
17539 boards[move][CASTLING][4] != NoRights )
17540 *p++ = boards[move][CASTLING][4] + AAA;
17543 /* [HGM] write true castling rights */
17544 if( nrCastlingRights == 6 ) {
17546 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17547 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17548 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17549 boards[move][CASTLING][2] != NoRights );
17550 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17551 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17552 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17553 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17554 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17558 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17559 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17560 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17561 boards[move][CASTLING][5] != NoRights );
17562 if(gameInfo.variant == VariantSChess) {
17563 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17564 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17565 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17566 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17571 if (q == p) *p++ = '-'; /* No castling rights */
17575 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17576 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17577 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17578 /* En passant target square */
17579 if (move > backwardMostMove) {
17580 fromX = moveList[move - 1][0] - AAA;
17581 fromY = moveList[move - 1][1] - ONE;
17582 toX = moveList[move - 1][2] - AAA;
17583 toY = moveList[move - 1][3] - ONE;
17584 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17585 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17586 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17588 /* 2-square pawn move just happened */
17590 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17594 } else if(move == backwardMostMove) {
17595 // [HGM] perhaps we should always do it like this, and forget the above?
17596 if((signed char)boards[move][EP_STATUS] >= 0) {
17597 *p++ = boards[move][EP_STATUS] + AAA;
17598 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17610 { int i = 0, j=move;
17612 /* [HGM] find reversible plies */
17613 if (appData.debugMode) { int k;
17614 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17615 for(k=backwardMostMove; k<=forwardMostMove; k++)
17616 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17620 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17621 if( j == backwardMostMove ) i += initialRulePlies;
17622 sprintf(p, "%d ", i);
17623 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17625 /* Fullmove number */
17626 sprintf(p, "%d", (move / 2) + 1);
17627 } else *--p = NULLCHAR;
17629 return StrSave(buf);
17633 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17637 int emptycount, virgin[BOARD_FILES];
17642 /* Piece placement data */
17643 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17646 if (*p == '/' || *p == ' ' || *p == '[' ) {
17648 emptycount = gameInfo.boardWidth - j;
17649 while (emptycount--)
17650 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17651 if (*p == '/') p++;
17652 else if(autoSize) { // we stumbled unexpectedly into end of board
17653 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17654 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17656 appData.NrRanks = gameInfo.boardHeight - i; i=0;
17659 #if(BOARD_FILES >= 10)
17660 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17661 p++; emptycount=10;
17662 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17663 while (emptycount--)
17664 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17666 } else if (*p == '*') {
17667 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17668 } else if (isdigit(*p)) {
17669 emptycount = *p++ - '0';
17670 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17671 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17672 while (emptycount--)
17673 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17674 } else if (*p == '+' || isalpha(*p)) {
17675 if (j >= gameInfo.boardWidth) return FALSE;
17677 piece = CharToPiece(*++p);
17678 if(piece == EmptySquare) return FALSE; /* unknown piece */
17679 piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17680 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17681 } else piece = CharToPiece(*p++);
17683 if(piece==EmptySquare) return FALSE; /* unknown piece */
17684 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17685 piece = (ChessSquare) (PROMOTED piece);
17686 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17689 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17695 while (*p == '/' || *p == ' ') p++;
17697 if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17699 /* [HGM] by default clear Crazyhouse holdings, if present */
17700 if(gameInfo.holdingsWidth) {
17701 for(i=0; i<BOARD_HEIGHT; i++) {
17702 board[i][0] = EmptySquare; /* black holdings */
17703 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17704 board[i][1] = (ChessSquare) 0; /* black counts */
17705 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17709 /* [HGM] look for Crazyhouse holdings here */
17710 while(*p==' ') p++;
17711 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17713 if(*p == '-' ) p++; /* empty holdings */ else {
17714 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17715 /* if we would allow FEN reading to set board size, we would */
17716 /* have to add holdings and shift the board read so far here */
17717 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17719 if((int) piece >= (int) BlackPawn ) {
17720 i = (int)piece - (int)BlackPawn;
17721 i = PieceToNumber((ChessSquare)i);
17722 if( i >= gameInfo.holdingsSize ) return FALSE;
17723 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17724 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17726 i = (int)piece - (int)WhitePawn;
17727 i = PieceToNumber((ChessSquare)i);
17728 if( i >= gameInfo.holdingsSize ) return FALSE;
17729 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17730 board[i][BOARD_WIDTH-2]++; /* black holdings */
17737 while(*p == ' ') p++;
17741 if(appData.colorNickNames) {
17742 if( c == appData.colorNickNames[0] ) c = 'w'; else
17743 if( c == appData.colorNickNames[1] ) c = 'b';
17747 *blackPlaysFirst = FALSE;
17750 *blackPlaysFirst = TRUE;
17756 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17757 /* return the extra info in global variiables */
17759 /* set defaults in case FEN is incomplete */
17760 board[EP_STATUS] = EP_UNKNOWN;
17761 for(i=0; i<nrCastlingRights; i++ ) {
17762 board[CASTLING][i] =
17763 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17764 } /* assume possible unless obviously impossible */
17765 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17766 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17767 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17768 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17769 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17770 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17771 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17772 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17775 while(*p==' ') p++;
17776 if(nrCastlingRights) {
17777 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17778 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17779 /* castling indicator present, so default becomes no castlings */
17780 for(i=0; i<nrCastlingRights; i++ ) {
17781 board[CASTLING][i] = NoRights;
17784 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17785 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17786 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17787 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17788 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17790 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17791 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17792 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17794 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17795 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17796 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17797 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17798 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17799 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17802 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17803 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17804 board[CASTLING][2] = whiteKingFile;
17805 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17806 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17809 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17810 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17811 board[CASTLING][2] = whiteKingFile;
17812 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17813 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17816 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17817 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17818 board[CASTLING][5] = blackKingFile;
17819 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17820 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17823 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17824 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17825 board[CASTLING][5] = blackKingFile;
17826 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17827 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17830 default: /* FRC castlings */
17831 if(c >= 'a') { /* black rights */
17832 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17833 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17834 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17835 if(i == BOARD_RGHT) break;
17836 board[CASTLING][5] = i;
17838 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17839 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17841 board[CASTLING][3] = c;
17843 board[CASTLING][4] = c;
17844 } else { /* white rights */
17845 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17846 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17847 if(board[0][i] == WhiteKing) break;
17848 if(i == BOARD_RGHT) break;
17849 board[CASTLING][2] = i;
17850 c -= AAA - 'a' + 'A';
17851 if(board[0][c] >= WhiteKing) break;
17853 board[CASTLING][0] = c;
17855 board[CASTLING][1] = c;
17859 for(i=0; i<nrCastlingRights; i++)
17860 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17861 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17862 if (appData.debugMode) {
17863 fprintf(debugFP, "FEN castling rights:");
17864 for(i=0; i<nrCastlingRights; i++)
17865 fprintf(debugFP, " %d", board[CASTLING][i]);
17866 fprintf(debugFP, "\n");
17869 while(*p==' ') p++;
17872 /* read e.p. field in games that know e.p. capture */
17873 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17874 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17875 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17877 p++; board[EP_STATUS] = EP_NONE;
17879 char c = *p++ - AAA;
17881 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17882 if(*p >= '0' && *p <='9') p++;
17883 board[EP_STATUS] = c;
17888 if(sscanf(p, "%d", &i) == 1) {
17889 FENrulePlies = i; /* 50-move ply counter */
17890 /* (The move number is still ignored) */
17897 EditPositionPasteFEN (char *fen)
17900 Board initial_position;
17902 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17903 DisplayError(_("Bad FEN position in clipboard"), 0);
17906 int savedBlackPlaysFirst = blackPlaysFirst;
17907 EditPositionEvent();
17908 blackPlaysFirst = savedBlackPlaysFirst;
17909 CopyBoard(boards[0], initial_position);
17910 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17911 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17912 DisplayBothClocks();
17913 DrawPosition(FALSE, boards[currentMove]);
17918 static char cseq[12] = "\\ ";
17921 set_cont_sequence (char *new_seq)
17926 // handle bad attempts to set the sequence
17928 return 0; // acceptable error - no debug
17930 len = strlen(new_seq);
17931 ret = (len > 0) && (len < sizeof(cseq));
17933 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17934 else if (appData.debugMode)
17935 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17940 reformat a source message so words don't cross the width boundary. internal
17941 newlines are not removed. returns the wrapped size (no null character unless
17942 included in source message). If dest is NULL, only calculate the size required
17943 for the dest buffer. lp argument indicats line position upon entry, and it's
17944 passed back upon exit.
17947 wrap (char *dest, char *src, int count, int width, int *lp)
17949 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17951 cseq_len = strlen(cseq);
17952 old_line = line = *lp;
17953 ansi = len = clen = 0;
17955 for (i=0; i < count; i++)
17957 if (src[i] == '\033')
17960 // if we hit the width, back up
17961 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17963 // store i & len in case the word is too long
17964 old_i = i, old_len = len;
17966 // find the end of the last word
17967 while (i && src[i] != ' ' && src[i] != '\n')
17973 // word too long? restore i & len before splitting it
17974 if ((old_i-i+clen) >= width)
17981 if (i && src[i-1] == ' ')
17984 if (src[i] != ' ' && src[i] != '\n')
17991 // now append the newline and continuation sequence
17996 strncpy(dest+len, cseq, cseq_len);
18004 dest[len] = src[i];
18008 if (src[i] == '\n')
18013 if (dest && appData.debugMode)
18015 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18016 count, width, line, len, *lp);
18017 show_bytes(debugFP, src, count);
18018 fprintf(debugFP, "\ndest: ");
18019 show_bytes(debugFP, dest, len);
18020 fprintf(debugFP, "\n");
18022 *lp = dest ? line : old_line;
18027 // [HGM] vari: routines for shelving variations
18028 Boolean modeRestore = FALSE;
18031 PushInner (int firstMove, int lastMove)
18033 int i, j, nrMoves = lastMove - firstMove;
18035 // push current tail of game on stack
18036 savedResult[storedGames] = gameInfo.result;
18037 savedDetails[storedGames] = gameInfo.resultDetails;
18038 gameInfo.resultDetails = NULL;
18039 savedFirst[storedGames] = firstMove;
18040 savedLast [storedGames] = lastMove;
18041 savedFramePtr[storedGames] = framePtr;
18042 framePtr -= nrMoves; // reserve space for the boards
18043 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18044 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18045 for(j=0; j<MOVE_LEN; j++)
18046 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18047 for(j=0; j<2*MOVE_LEN; j++)
18048 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18049 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18050 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18051 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18052 pvInfoList[firstMove+i-1].depth = 0;
18053 commentList[framePtr+i] = commentList[firstMove+i];
18054 commentList[firstMove+i] = NULL;
18058 forwardMostMove = firstMove; // truncate game so we can start variation
18062 PushTail (int firstMove, int lastMove)
18064 if(appData.icsActive) { // only in local mode
18065 forwardMostMove = currentMove; // mimic old ICS behavior
18068 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18070 PushInner(firstMove, lastMove);
18071 if(storedGames == 1) GreyRevert(FALSE);
18072 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18076 PopInner (Boolean annotate)
18079 char buf[8000], moveBuf[20];
18081 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18082 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18083 nrMoves = savedLast[storedGames] - currentMove;
18086 if(!WhiteOnMove(currentMove))
18087 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18088 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18089 for(i=currentMove; i<forwardMostMove; i++) {
18091 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18092 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18093 strcat(buf, moveBuf);
18094 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18095 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18099 for(i=1; i<=nrMoves; i++) { // copy last variation back
18100 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18101 for(j=0; j<MOVE_LEN; j++)
18102 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18103 for(j=0; j<2*MOVE_LEN; j++)
18104 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18105 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18106 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18107 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18108 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18109 commentList[currentMove+i] = commentList[framePtr+i];
18110 commentList[framePtr+i] = NULL;
18112 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18113 framePtr = savedFramePtr[storedGames];
18114 gameInfo.result = savedResult[storedGames];
18115 if(gameInfo.resultDetails != NULL) {
18116 free(gameInfo.resultDetails);
18118 gameInfo.resultDetails = savedDetails[storedGames];
18119 forwardMostMove = currentMove + nrMoves;
18123 PopTail (Boolean annotate)
18125 if(appData.icsActive) return FALSE; // only in local mode
18126 if(!storedGames) return FALSE; // sanity
18127 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18129 PopInner(annotate);
18130 if(currentMove < forwardMostMove) ForwardEvent(); else
18131 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18133 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18139 { // remove all shelved variations
18141 for(i=0; i<storedGames; i++) {
18142 if(savedDetails[i])
18143 free(savedDetails[i]);
18144 savedDetails[i] = NULL;
18146 for(i=framePtr; i<MAX_MOVES; i++) {
18147 if(commentList[i]) free(commentList[i]);
18148 commentList[i] = NULL;
18150 framePtr = MAX_MOVES-1;
18155 LoadVariation (int index, char *text)
18156 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18157 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18158 int level = 0, move;
18160 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18161 // first find outermost bracketing variation
18162 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18163 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18164 if(*p == '{') wait = '}'; else
18165 if(*p == '[') wait = ']'; else
18166 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18167 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18169 if(*p == wait) wait = NULLCHAR; // closing ]} found
18172 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18173 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18174 end[1] = NULLCHAR; // clip off comment beyond variation
18175 ToNrEvent(currentMove-1);
18176 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18177 // kludge: use ParsePV() to append variation to game
18178 move = currentMove;
18179 ParsePV(start, TRUE, TRUE);
18180 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18181 ClearPremoveHighlights();
18183 ToNrEvent(currentMove+1);
18189 char *p, *q, buf[MSG_SIZ];
18190 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18191 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18192 ParseArgsFromString(buf);
18193 ActivateTheme(TRUE); // also redo colors
18197 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18200 q = appData.themeNames;
18201 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18202 if(appData.useBitmaps) {
18203 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18204 appData.liteBackTextureFile, appData.darkBackTextureFile,
18205 appData.liteBackTextureMode,
18206 appData.darkBackTextureMode );
18208 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18209 Col2Text(2), // lightSquareColor
18210 Col2Text(3) ); // darkSquareColor
18212 if(appData.useBorder) {
18213 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18216 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18218 if(appData.useFont) {
18219 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18220 appData.renderPiecesWithFont,
18221 appData.fontToPieceTable,
18222 Col2Text(9), // appData.fontBackColorWhite
18223 Col2Text(10) ); // appData.fontForeColorBlack
18225 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18226 appData.pieceDirectory);
18227 if(!appData.pieceDirectory[0])
18228 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18229 Col2Text(0), // whitePieceColor
18230 Col2Text(1) ); // blackPieceColor
18232 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18233 Col2Text(4), // highlightSquareColor
18234 Col2Text(5) ); // premoveHighlightColor
18235 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18236 if(insert != q) insert[-1] = NULLCHAR;
18237 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18240 ActivateTheme(FALSE);