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 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1568 !strcmp(appData.variant, "normal") && // no explicit variant request
1569 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1570 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1571 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1572 char c, *q = first.variants, *p = strchr(q, ',');
1573 if(p) *p = NULLCHAR;
1574 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1576 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1577 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1578 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1579 Reset(TRUE, FALSE); // and re-initialize
1584 InitChessProgram(&first, startedFromSetupPosition);
1586 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1587 free(programVersion);
1588 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1589 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1590 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1593 if (appData.icsActive) {
1595 /* [DM] Make a console window if needed [HGM] merged ifs */
1601 if (*appData.icsCommPort != NULLCHAR)
1602 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1603 appData.icsCommPort);
1605 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1606 appData.icsHost, appData.icsPort);
1608 if( (len >= MSG_SIZ) && appData.debugMode )
1609 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1611 DisplayFatalError(buf, err, 1);
1616 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1618 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1619 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1620 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1621 } else if (appData.noChessProgram) {
1627 if (*appData.cmailGameName != NULLCHAR) {
1629 OpenLoopback(&cmailPR);
1631 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1635 DisplayMessage("", "");
1636 if (StrCaseCmp(appData.initialMode, "") == 0) {
1637 initialMode = BeginningOfGame;
1638 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1639 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1640 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1641 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1644 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1645 initialMode = TwoMachinesPlay;
1646 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1647 initialMode = AnalyzeFile;
1648 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1649 initialMode = AnalyzeMode;
1650 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1651 initialMode = MachinePlaysWhite;
1652 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1653 initialMode = MachinePlaysBlack;
1654 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1655 initialMode = EditGame;
1656 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1657 initialMode = EditPosition;
1658 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1659 initialMode = Training;
1661 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1662 if( (len >= MSG_SIZ) && appData.debugMode )
1663 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1665 DisplayFatalError(buf, 0, 2);
1669 if (appData.matchMode) {
1670 if(appData.tourneyFile[0]) { // start tourney from command line
1672 if(f = fopen(appData.tourneyFile, "r")) {
1673 ParseArgsFromFile(f); // make sure tourney parmeters re known
1675 appData.clockMode = TRUE;
1677 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1680 } else if (*appData.cmailGameName != NULLCHAR) {
1681 /* Set up cmail mode */
1682 ReloadCmailMsgEvent(TRUE);
1684 /* Set up other modes */
1685 if (initialMode == AnalyzeFile) {
1686 if (*appData.loadGameFile == NULLCHAR) {
1687 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1691 if (*appData.loadGameFile != NULLCHAR) {
1692 (void) LoadGameFromFile(appData.loadGameFile,
1693 appData.loadGameIndex,
1694 appData.loadGameFile, TRUE);
1695 } else if (*appData.loadPositionFile != NULLCHAR) {
1696 (void) LoadPositionFromFile(appData.loadPositionFile,
1697 appData.loadPositionIndex,
1698 appData.loadPositionFile);
1699 /* [HGM] try to make self-starting even after FEN load */
1700 /* to allow automatic setup of fairy variants with wtm */
1701 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1702 gameMode = BeginningOfGame;
1703 setboardSpoiledMachineBlack = 1;
1705 /* [HGM] loadPos: make that every new game uses the setup */
1706 /* from file as long as we do not switch variant */
1707 if(!blackPlaysFirst) {
1708 startedFromPositionFile = TRUE;
1709 CopyBoard(filePosition, boards[0]);
1712 if (initialMode == AnalyzeMode) {
1713 if (appData.noChessProgram) {
1714 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1717 if (appData.icsActive) {
1718 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1722 } else if (initialMode == AnalyzeFile) {
1723 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1724 ShowThinkingEvent();
1726 AnalysisPeriodicEvent(1);
1727 } else if (initialMode == MachinePlaysWhite) {
1728 if (appData.noChessProgram) {
1729 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1733 if (appData.icsActive) {
1734 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1738 MachineWhiteEvent();
1739 } else if (initialMode == MachinePlaysBlack) {
1740 if (appData.noChessProgram) {
1741 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1745 if (appData.icsActive) {
1746 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1750 MachineBlackEvent();
1751 } else if (initialMode == TwoMachinesPlay) {
1752 if (appData.noChessProgram) {
1753 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1757 if (appData.icsActive) {
1758 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1763 } else if (initialMode == EditGame) {
1765 } else if (initialMode == EditPosition) {
1766 EditPositionEvent();
1767 } else if (initialMode == Training) {
1768 if (*appData.loadGameFile == NULLCHAR) {
1769 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1778 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1780 DisplayBook(current+1);
1782 MoveHistorySet( movelist, first, last, current, pvInfoList );
1784 EvalGraphSet( first, last, current, pvInfoList );
1786 MakeEngineOutputTitle();
1790 * Establish will establish a contact to a remote host.port.
1791 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1792 * used to talk to the host.
1793 * Returns 0 if okay, error code if not.
1800 if (*appData.icsCommPort != NULLCHAR) {
1801 /* Talk to the host through a serial comm port */
1802 return OpenCommPort(appData.icsCommPort, &icsPR);
1804 } else if (*appData.gateway != NULLCHAR) {
1805 if (*appData.remoteShell == NULLCHAR) {
1806 /* Use the rcmd protocol to run telnet program on a gateway host */
1807 snprintf(buf, sizeof(buf), "%s %s %s",
1808 appData.telnetProgram, appData.icsHost, appData.icsPort);
1809 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1812 /* Use the rsh program to run telnet program on a gateway host */
1813 if (*appData.remoteUser == NULLCHAR) {
1814 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1815 appData.gateway, appData.telnetProgram,
1816 appData.icsHost, appData.icsPort);
1818 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1819 appData.remoteShell, appData.gateway,
1820 appData.remoteUser, appData.telnetProgram,
1821 appData.icsHost, appData.icsPort);
1823 return StartChildProcess(buf, "", &icsPR);
1826 } else if (appData.useTelnet) {
1827 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1830 /* TCP socket interface differs somewhat between
1831 Unix and NT; handle details in the front end.
1833 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1838 EscapeExpand (char *p, char *q)
1839 { // [HGM] initstring: routine to shape up string arguments
1840 while(*p++ = *q++) if(p[-1] == '\\')
1842 case 'n': p[-1] = '\n'; break;
1843 case 'r': p[-1] = '\r'; break;
1844 case 't': p[-1] = '\t'; break;
1845 case '\\': p[-1] = '\\'; break;
1846 case 0: *p = 0; return;
1847 default: p[-1] = q[-1]; break;
1852 show_bytes (FILE *fp, char *buf, int count)
1855 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1856 fprintf(fp, "\\%03o", *buf & 0xff);
1865 /* Returns an errno value */
1867 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1869 char buf[8192], *p, *q, *buflim;
1870 int left, newcount, outcount;
1872 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1873 *appData.gateway != NULLCHAR) {
1874 if (appData.debugMode) {
1875 fprintf(debugFP, ">ICS: ");
1876 show_bytes(debugFP, message, count);
1877 fprintf(debugFP, "\n");
1879 return OutputToProcess(pr, message, count, outError);
1882 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1889 if (appData.debugMode) {
1890 fprintf(debugFP, ">ICS: ");
1891 show_bytes(debugFP, buf, newcount);
1892 fprintf(debugFP, "\n");
1894 outcount = OutputToProcess(pr, buf, newcount, outError);
1895 if (outcount < newcount) return -1; /* to be sure */
1902 } else if (((unsigned char) *p) == TN_IAC) {
1903 *q++ = (char) TN_IAC;
1910 if (appData.debugMode) {
1911 fprintf(debugFP, ">ICS: ");
1912 show_bytes(debugFP, buf, newcount);
1913 fprintf(debugFP, "\n");
1915 outcount = OutputToProcess(pr, buf, newcount, outError);
1916 if (outcount < newcount) return -1; /* to be sure */
1921 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1923 int outError, outCount;
1924 static int gotEof = 0;
1927 /* Pass data read from player on to ICS */
1930 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1931 if (outCount < count) {
1932 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1934 if(have_sent_ICS_logon == 2) {
1935 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1936 fprintf(ini, "%s", message);
1937 have_sent_ICS_logon = 3;
1939 have_sent_ICS_logon = 1;
1940 } else if(have_sent_ICS_logon == 3) {
1941 fprintf(ini, "%s", message);
1943 have_sent_ICS_logon = 1;
1945 } else if (count < 0) {
1946 RemoveInputSource(isr);
1947 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1948 } else if (gotEof++ > 0) {
1949 RemoveInputSource(isr);
1950 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1956 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1957 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1958 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1959 SendToICS("date\n");
1960 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1963 /* added routine for printf style output to ics */
1965 ics_printf (char *format, ...)
1967 char buffer[MSG_SIZ];
1970 va_start(args, format);
1971 vsnprintf(buffer, sizeof(buffer), format, args);
1972 buffer[sizeof(buffer)-1] = '\0';
1980 int count, outCount, outError;
1982 if (icsPR == NoProc) return;
1985 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1986 if (outCount < count) {
1987 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1991 /* This is used for sending logon scripts to the ICS. Sending
1992 without a delay causes problems when using timestamp on ICC
1993 (at least on my machine). */
1995 SendToICSDelayed (char *s, long msdelay)
1997 int count, outCount, outError;
1999 if (icsPR == NoProc) return;
2002 if (appData.debugMode) {
2003 fprintf(debugFP, ">ICS: ");
2004 show_bytes(debugFP, s, count);
2005 fprintf(debugFP, "\n");
2007 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2009 if (outCount < count) {
2010 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2015 /* Remove all highlighting escape sequences in s
2016 Also deletes any suffix starting with '('
2019 StripHighlightAndTitle (char *s)
2021 static char retbuf[MSG_SIZ];
2024 while (*s != NULLCHAR) {
2025 while (*s == '\033') {
2026 while (*s != NULLCHAR && !isalpha(*s)) s++;
2027 if (*s != NULLCHAR) s++;
2029 while (*s != NULLCHAR && *s != '\033') {
2030 if (*s == '(' || *s == '[') {
2041 /* Remove all highlighting escape sequences in s */
2043 StripHighlight (char *s)
2045 static char retbuf[MSG_SIZ];
2048 while (*s != NULLCHAR) {
2049 while (*s == '\033') {
2050 while (*s != NULLCHAR && !isalpha(*s)) s++;
2051 if (*s != NULLCHAR) s++;
2053 while (*s != NULLCHAR && *s != '\033') {
2061 char engineVariant[MSG_SIZ];
2062 char *variantNames[] = VARIANT_NAMES;
2064 VariantName (VariantClass v)
2066 if(v == VariantUnknown || *engineVariant) return engineVariant;
2067 return variantNames[v];
2071 /* Identify a variant from the strings the chess servers use or the
2072 PGN Variant tag names we use. */
2074 StringToVariant (char *e)
2078 VariantClass v = VariantNormal;
2079 int i, found = FALSE;
2085 /* [HGM] skip over optional board-size prefixes */
2086 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2087 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2088 while( *e++ != '_');
2091 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2095 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2096 if (p = StrCaseStr(e, variantNames[i])) {
2097 if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2098 v = (VariantClass) i;
2105 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2106 || StrCaseStr(e, "wild/fr")
2107 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2108 v = VariantFischeRandom;
2109 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2110 (i = 1, p = StrCaseStr(e, "w"))) {
2112 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2119 case 0: /* FICS only, actually */
2121 /* Castling legal even if K starts on d-file */
2122 v = VariantWildCastle;
2127 /* Castling illegal even if K & R happen to start in
2128 normal positions. */
2129 v = VariantNoCastle;
2142 /* Castling legal iff K & R start in normal positions */
2148 /* Special wilds for position setup; unclear what to do here */
2149 v = VariantLoadable;
2152 /* Bizarre ICC game */
2153 v = VariantTwoKings;
2156 v = VariantKriegspiel;
2162 v = VariantFischeRandom;
2165 v = VariantCrazyhouse;
2168 v = VariantBughouse;
2174 /* Not quite the same as FICS suicide! */
2175 v = VariantGiveaway;
2181 v = VariantShatranj;
2184 /* Temporary names for future ICC types. The name *will* change in
2185 the next xboard/WinBoard release after ICC defines it. */
2223 v = VariantCapablanca;
2226 v = VariantKnightmate;
2232 v = VariantCylinder;
2238 v = VariantCapaRandom;
2241 v = VariantBerolina;
2253 /* Found "wild" or "w" in the string but no number;
2254 must assume it's normal chess. */
2258 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2259 if( (len >= MSG_SIZ) && appData.debugMode )
2260 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2262 DisplayError(buf, 0);
2268 if (appData.debugMode) {
2269 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2270 e, wnum, VariantName(v));
2275 static int leftover_start = 0, leftover_len = 0;
2276 char star_match[STAR_MATCH_N][MSG_SIZ];
2278 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2279 advance *index beyond it, and set leftover_start to the new value of
2280 *index; else return FALSE. If pattern contains the character '*', it
2281 matches any sequence of characters not containing '\r', '\n', or the
2282 character following the '*' (if any), and the matched sequence(s) are
2283 copied into star_match.
2286 looking_at ( char *buf, int *index, char *pattern)
2288 char *bufp = &buf[*index], *patternp = pattern;
2290 char *matchp = star_match[0];
2293 if (*patternp == NULLCHAR) {
2294 *index = leftover_start = bufp - buf;
2298 if (*bufp == NULLCHAR) return FALSE;
2299 if (*patternp == '*') {
2300 if (*bufp == *(patternp + 1)) {
2302 matchp = star_match[++star_count];
2306 } else if (*bufp == '\n' || *bufp == '\r') {
2308 if (*patternp == NULLCHAR)
2313 *matchp++ = *bufp++;
2317 if (*patternp != *bufp) return FALSE;
2324 SendToPlayer (char *data, int length)
2326 int error, outCount;
2327 outCount = OutputToProcess(NoProc, data, length, &error);
2328 if (outCount < length) {
2329 DisplayFatalError(_("Error writing to display"), error, 1);
2334 PackHolding (char packed[], char *holding)
2344 switch (runlength) {
2355 sprintf(q, "%d", runlength);
2367 /* Telnet protocol requests from the front end */
2369 TelnetRequest (unsigned char ddww, unsigned char option)
2371 unsigned char msg[3];
2372 int outCount, outError;
2374 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2376 if (appData.debugMode) {
2377 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2393 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2402 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2405 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2410 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2412 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2419 if (!appData.icsActive) return;
2420 TelnetRequest(TN_DO, TN_ECHO);
2426 if (!appData.icsActive) return;
2427 TelnetRequest(TN_DONT, TN_ECHO);
2431 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2433 /* put the holdings sent to us by the server on the board holdings area */
2434 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2438 if(gameInfo.holdingsWidth < 2) return;
2439 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2440 return; // prevent overwriting by pre-board holdings
2442 if( (int)lowestPiece >= BlackPawn ) {
2445 holdingsStartRow = BOARD_HEIGHT-1;
2448 holdingsColumn = BOARD_WIDTH-1;
2449 countsColumn = BOARD_WIDTH-2;
2450 holdingsStartRow = 0;
2454 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2455 board[i][holdingsColumn] = EmptySquare;
2456 board[i][countsColumn] = (ChessSquare) 0;
2458 while( (p=*holdings++) != NULLCHAR ) {
2459 piece = CharToPiece( ToUpper(p) );
2460 if(piece == EmptySquare) continue;
2461 /*j = (int) piece - (int) WhitePawn;*/
2462 j = PieceToNumber(piece);
2463 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2464 if(j < 0) continue; /* should not happen */
2465 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2466 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2467 board[holdingsStartRow+j*direction][countsColumn]++;
2473 VariantSwitch (Board board, VariantClass newVariant)
2475 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2476 static Board oldBoard;
2478 startedFromPositionFile = FALSE;
2479 if(gameInfo.variant == newVariant) return;
2481 /* [HGM] This routine is called each time an assignment is made to
2482 * gameInfo.variant during a game, to make sure the board sizes
2483 * are set to match the new variant. If that means adding or deleting
2484 * holdings, we shift the playing board accordingly
2485 * This kludge is needed because in ICS observe mode, we get boards
2486 * of an ongoing game without knowing the variant, and learn about the
2487 * latter only later. This can be because of the move list we requested,
2488 * in which case the game history is refilled from the beginning anyway,
2489 * but also when receiving holdings of a crazyhouse game. In the latter
2490 * case we want to add those holdings to the already received position.
2494 if (appData.debugMode) {
2495 fprintf(debugFP, "Switch board from %s to %s\n",
2496 VariantName(gameInfo.variant), VariantName(newVariant));
2497 setbuf(debugFP, NULL);
2499 shuffleOpenings = 0; /* [HGM] shuffle */
2500 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2504 newWidth = 9; newHeight = 9;
2505 gameInfo.holdingsSize = 7;
2506 case VariantBughouse:
2507 case VariantCrazyhouse:
2508 newHoldingsWidth = 2; break;
2512 newHoldingsWidth = 2;
2513 gameInfo.holdingsSize = 8;
2516 case VariantCapablanca:
2517 case VariantCapaRandom:
2520 newHoldingsWidth = gameInfo.holdingsSize = 0;
2523 if(newWidth != gameInfo.boardWidth ||
2524 newHeight != gameInfo.boardHeight ||
2525 newHoldingsWidth != gameInfo.holdingsWidth ) {
2527 /* shift position to new playing area, if needed */
2528 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2529 for(i=0; i<BOARD_HEIGHT; i++)
2530 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2531 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2533 for(i=0; i<newHeight; i++) {
2534 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2535 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2537 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2538 for(i=0; i<BOARD_HEIGHT; i++)
2539 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2540 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2543 board[HOLDINGS_SET] = 0;
2544 gameInfo.boardWidth = newWidth;
2545 gameInfo.boardHeight = newHeight;
2546 gameInfo.holdingsWidth = newHoldingsWidth;
2547 gameInfo.variant = newVariant;
2548 InitDrawingSizes(-2, 0);
2549 } else gameInfo.variant = newVariant;
2550 CopyBoard(oldBoard, board); // remember correctly formatted board
2551 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2552 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2555 static int loggedOn = FALSE;
2557 /*-- Game start info cache: --*/
2559 char gs_kind[MSG_SIZ];
2560 static char player1Name[128] = "";
2561 static char player2Name[128] = "";
2562 static char cont_seq[] = "\n\\ ";
2563 static int player1Rating = -1;
2564 static int player2Rating = -1;
2565 /*----------------------------*/
2567 ColorClass curColor = ColorNormal;
2568 int suppressKibitz = 0;
2571 Boolean soughtPending = FALSE;
2572 Boolean seekGraphUp;
2573 #define MAX_SEEK_ADS 200
2575 char *seekAdList[MAX_SEEK_ADS];
2576 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2577 float tcList[MAX_SEEK_ADS];
2578 char colorList[MAX_SEEK_ADS];
2579 int nrOfSeekAds = 0;
2580 int minRating = 1010, maxRating = 2800;
2581 int hMargin = 10, vMargin = 20, h, w;
2582 extern int squareSize, lineGap;
2587 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2588 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2589 if(r < minRating+100 && r >=0 ) r = minRating+100;
2590 if(r > maxRating) r = maxRating;
2591 if(tc < 1.f) tc = 1.f;
2592 if(tc > 95.f) tc = 95.f;
2593 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2594 y = ((double)r - minRating)/(maxRating - minRating)
2595 * (h-vMargin-squareSize/8-1) + vMargin;
2596 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2597 if(strstr(seekAdList[i], " u ")) color = 1;
2598 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2599 !strstr(seekAdList[i], "bullet") &&
2600 !strstr(seekAdList[i], "blitz") &&
2601 !strstr(seekAdList[i], "standard") ) color = 2;
2602 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2603 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2607 PlotSingleSeekAd (int i)
2613 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2615 char buf[MSG_SIZ], *ext = "";
2616 VariantClass v = StringToVariant(type);
2617 if(strstr(type, "wild")) {
2618 ext = type + 4; // append wild number
2619 if(v == VariantFischeRandom) type = "chess960"; else
2620 if(v == VariantLoadable) type = "setup"; else
2621 type = VariantName(v);
2623 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2624 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2625 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2626 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2627 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2628 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2629 seekNrList[nrOfSeekAds] = nr;
2630 zList[nrOfSeekAds] = 0;
2631 seekAdList[nrOfSeekAds++] = StrSave(buf);
2632 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2637 EraseSeekDot (int i)
2639 int x = xList[i], y = yList[i], d=squareSize/4, k;
2640 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2641 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2642 // now replot every dot that overlapped
2643 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2644 int xx = xList[k], yy = yList[k];
2645 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2646 DrawSeekDot(xx, yy, colorList[k]);
2651 RemoveSeekAd (int nr)
2654 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2656 if(seekAdList[i]) free(seekAdList[i]);
2657 seekAdList[i] = seekAdList[--nrOfSeekAds];
2658 seekNrList[i] = seekNrList[nrOfSeekAds];
2659 ratingList[i] = ratingList[nrOfSeekAds];
2660 colorList[i] = colorList[nrOfSeekAds];
2661 tcList[i] = tcList[nrOfSeekAds];
2662 xList[i] = xList[nrOfSeekAds];
2663 yList[i] = yList[nrOfSeekAds];
2664 zList[i] = zList[nrOfSeekAds];
2665 seekAdList[nrOfSeekAds] = NULL;
2671 MatchSoughtLine (char *line)
2673 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2674 int nr, base, inc, u=0; char dummy;
2676 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2677 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2679 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2680 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2681 // match: compact and save the line
2682 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2692 if(!seekGraphUp) return FALSE;
2693 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2694 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2696 DrawSeekBackground(0, 0, w, h);
2697 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2698 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2699 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2700 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2702 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2705 snprintf(buf, MSG_SIZ, "%d", i);
2706 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2709 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2710 for(i=1; i<100; i+=(i<10?1:5)) {
2711 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2712 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2713 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2715 snprintf(buf, MSG_SIZ, "%d", i);
2716 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2719 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2724 SeekGraphClick (ClickType click, int x, int y, int moving)
2726 static int lastDown = 0, displayed = 0, lastSecond;
2727 if(y < 0) return FALSE;
2728 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2729 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2730 if(!seekGraphUp) return FALSE;
2731 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2732 DrawPosition(TRUE, NULL);
2735 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2736 if(click == Release || moving) return FALSE;
2738 soughtPending = TRUE;
2739 SendToICS(ics_prefix);
2740 SendToICS("sought\n"); // should this be "sought all"?
2741 } else { // issue challenge based on clicked ad
2742 int dist = 10000; int i, closest = 0, second = 0;
2743 for(i=0; i<nrOfSeekAds; i++) {
2744 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2745 if(d < dist) { dist = d; closest = i; }
2746 second += (d - zList[i] < 120); // count in-range ads
2747 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2751 second = (second > 1);
2752 if(displayed != closest || second != lastSecond) {
2753 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2754 lastSecond = second; displayed = closest;
2756 if(click == Press) {
2757 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2760 } // on press 'hit', only show info
2761 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2762 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2763 SendToICS(ics_prefix);
2765 return TRUE; // let incoming board of started game pop down the graph
2766 } else if(click == Release) { // release 'miss' is ignored
2767 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2768 if(moving == 2) { // right up-click
2769 nrOfSeekAds = 0; // refresh graph
2770 soughtPending = TRUE;
2771 SendToICS(ics_prefix);
2772 SendToICS("sought\n"); // should this be "sought all"?
2775 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2776 // press miss or release hit 'pop down' seek graph
2777 seekGraphUp = FALSE;
2778 DrawPosition(TRUE, NULL);
2784 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2786 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2787 #define STARTED_NONE 0
2788 #define STARTED_MOVES 1
2789 #define STARTED_BOARD 2
2790 #define STARTED_OBSERVE 3
2791 #define STARTED_HOLDINGS 4
2792 #define STARTED_CHATTER 5
2793 #define STARTED_COMMENT 6
2794 #define STARTED_MOVES_NOHIDE 7
2796 static int started = STARTED_NONE;
2797 static char parse[20000];
2798 static int parse_pos = 0;
2799 static char buf[BUF_SIZE + 1];
2800 static int firstTime = TRUE, intfSet = FALSE;
2801 static ColorClass prevColor = ColorNormal;
2802 static int savingComment = FALSE;
2803 static int cmatch = 0; // continuation sequence match
2810 int backup; /* [DM] For zippy color lines */
2812 char talker[MSG_SIZ]; // [HGM] chat
2815 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2817 if (appData.debugMode) {
2819 fprintf(debugFP, "<ICS: ");
2820 show_bytes(debugFP, data, count);
2821 fprintf(debugFP, "\n");
2825 if (appData.debugMode) { int f = forwardMostMove;
2826 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2827 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2828 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2831 /* If last read ended with a partial line that we couldn't parse,
2832 prepend it to the new read and try again. */
2833 if (leftover_len > 0) {
2834 for (i=0; i<leftover_len; i++)
2835 buf[i] = buf[leftover_start + i];
2838 /* copy new characters into the buffer */
2839 bp = buf + leftover_len;
2840 buf_len=leftover_len;
2841 for (i=0; i<count; i++)
2844 if (data[i] == '\r')
2847 // join lines split by ICS?
2848 if (!appData.noJoin)
2851 Joining just consists of finding matches against the
2852 continuation sequence, and discarding that sequence
2853 if found instead of copying it. So, until a match
2854 fails, there's nothing to do since it might be the
2855 complete sequence, and thus, something we don't want
2858 if (data[i] == cont_seq[cmatch])
2861 if (cmatch == strlen(cont_seq))
2863 cmatch = 0; // complete match. just reset the counter
2866 it's possible for the ICS to not include the space
2867 at the end of the last word, making our [correct]
2868 join operation fuse two separate words. the server
2869 does this when the space occurs at the width setting.
2871 if (!buf_len || buf[buf_len-1] != ' ')
2882 match failed, so we have to copy what matched before
2883 falling through and copying this character. In reality,
2884 this will only ever be just the newline character, but
2885 it doesn't hurt to be precise.
2887 strncpy(bp, cont_seq, cmatch);
2899 buf[buf_len] = NULLCHAR;
2900 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2905 while (i < buf_len) {
2906 /* Deal with part of the TELNET option negotiation
2907 protocol. We refuse to do anything beyond the
2908 defaults, except that we allow the WILL ECHO option,
2909 which ICS uses to turn off password echoing when we are
2910 directly connected to it. We reject this option
2911 if localLineEditing mode is on (always on in xboard)
2912 and we are talking to port 23, which might be a real
2913 telnet server that will try to keep WILL ECHO on permanently.
2915 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2916 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2917 unsigned char option;
2919 switch ((unsigned char) buf[++i]) {
2921 if (appData.debugMode)
2922 fprintf(debugFP, "\n<WILL ");
2923 switch (option = (unsigned char) buf[++i]) {
2925 if (appData.debugMode)
2926 fprintf(debugFP, "ECHO ");
2927 /* Reply only if this is a change, according
2928 to the protocol rules. */
2929 if (remoteEchoOption) break;
2930 if (appData.localLineEditing &&
2931 atoi(appData.icsPort) == TN_PORT) {
2932 TelnetRequest(TN_DONT, TN_ECHO);
2935 TelnetRequest(TN_DO, TN_ECHO);
2936 remoteEchoOption = TRUE;
2940 if (appData.debugMode)
2941 fprintf(debugFP, "%d ", option);
2942 /* Whatever this is, we don't want it. */
2943 TelnetRequest(TN_DONT, option);
2948 if (appData.debugMode)
2949 fprintf(debugFP, "\n<WONT ");
2950 switch (option = (unsigned char) buf[++i]) {
2952 if (appData.debugMode)
2953 fprintf(debugFP, "ECHO ");
2954 /* Reply only if this is a change, according
2955 to the protocol rules. */
2956 if (!remoteEchoOption) break;
2958 TelnetRequest(TN_DONT, TN_ECHO);
2959 remoteEchoOption = FALSE;
2962 if (appData.debugMode)
2963 fprintf(debugFP, "%d ", (unsigned char) option);
2964 /* Whatever this is, it must already be turned
2965 off, because we never agree to turn on
2966 anything non-default, so according to the
2967 protocol rules, we don't reply. */
2972 if (appData.debugMode)
2973 fprintf(debugFP, "\n<DO ");
2974 switch (option = (unsigned char) buf[++i]) {
2976 /* Whatever this is, we refuse to do it. */
2977 if (appData.debugMode)
2978 fprintf(debugFP, "%d ", option);
2979 TelnetRequest(TN_WONT, option);
2984 if (appData.debugMode)
2985 fprintf(debugFP, "\n<DONT ");
2986 switch (option = (unsigned char) buf[++i]) {
2988 if (appData.debugMode)
2989 fprintf(debugFP, "%d ", option);
2990 /* Whatever this is, we are already not doing
2991 it, because we never agree to do anything
2992 non-default, so according to the protocol
2993 rules, we don't reply. */
2998 if (appData.debugMode)
2999 fprintf(debugFP, "\n<IAC ");
3000 /* Doubled IAC; pass it through */
3004 if (appData.debugMode)
3005 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3006 /* Drop all other telnet commands on the floor */
3009 if (oldi > next_out)
3010 SendToPlayer(&buf[next_out], oldi - next_out);
3016 /* OK, this at least will *usually* work */
3017 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3021 if (loggedOn && !intfSet) {
3022 if (ics_type == ICS_ICC) {
3023 snprintf(str, MSG_SIZ,
3024 "/set-quietly interface %s\n/set-quietly style 12\n",
3026 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3027 strcat(str, "/set-2 51 1\n/set seek 1\n");
3028 } else if (ics_type == ICS_CHESSNET) {
3029 snprintf(str, MSG_SIZ, "/style 12\n");
3031 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3032 strcat(str, programVersion);
3033 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3034 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3035 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3037 strcat(str, "$iset nohighlight 1\n");
3039 strcat(str, "$iset lock 1\n$style 12\n");
3042 NotifyFrontendLogin();
3046 if (started == STARTED_COMMENT) {
3047 /* Accumulate characters in comment */
3048 parse[parse_pos++] = buf[i];
3049 if (buf[i] == '\n') {
3050 parse[parse_pos] = NULLCHAR;
3051 if(chattingPartner>=0) {
3053 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3054 OutputChatMessage(chattingPartner, mess);
3055 chattingPartner = -1;
3056 next_out = i+1; // [HGM] suppress printing in ICS window
3058 if(!suppressKibitz) // [HGM] kibitz
3059 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3060 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3061 int nrDigit = 0, nrAlph = 0, j;
3062 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3063 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3064 parse[parse_pos] = NULLCHAR;
3065 // try to be smart: if it does not look like search info, it should go to
3066 // ICS interaction window after all, not to engine-output window.
3067 for(j=0; j<parse_pos; j++) { // count letters and digits
3068 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3069 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3070 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3072 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3073 int depth=0; float score;
3074 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3075 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3076 pvInfoList[forwardMostMove-1].depth = depth;
3077 pvInfoList[forwardMostMove-1].score = 100*score;
3079 OutputKibitz(suppressKibitz, parse);
3082 if(gameMode == IcsObserving) // restore original ICS messages
3083 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3084 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3086 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3087 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3088 SendToPlayer(tmp, strlen(tmp));
3090 next_out = i+1; // [HGM] suppress printing in ICS window
3092 started = STARTED_NONE;
3094 /* Don't match patterns against characters in comment */
3099 if (started == STARTED_CHATTER) {
3100 if (buf[i] != '\n') {
3101 /* Don't match patterns against characters in chatter */
3105 started = STARTED_NONE;
3106 if(suppressKibitz) next_out = i+1;
3109 /* Kludge to deal with rcmd protocol */
3110 if (firstTime && looking_at(buf, &i, "\001*")) {
3111 DisplayFatalError(&buf[1], 0, 1);
3117 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3120 if (appData.debugMode)
3121 fprintf(debugFP, "ics_type %d\n", ics_type);
3124 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3125 ics_type = ICS_FICS;
3127 if (appData.debugMode)
3128 fprintf(debugFP, "ics_type %d\n", ics_type);
3131 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3132 ics_type = ICS_CHESSNET;
3134 if (appData.debugMode)
3135 fprintf(debugFP, "ics_type %d\n", ics_type);
3140 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3141 looking_at(buf, &i, "Logging you in as \"*\"") ||
3142 looking_at(buf, &i, "will be \"*\""))) {
3143 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3147 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3149 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3150 DisplayIcsInteractionTitle(buf);
3151 have_set_title = TRUE;
3154 /* skip finger notes */
3155 if (started == STARTED_NONE &&
3156 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3157 (buf[i] == '1' && buf[i+1] == '0')) &&
3158 buf[i+2] == ':' && buf[i+3] == ' ') {
3159 started = STARTED_CHATTER;
3165 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3166 if(appData.seekGraph) {
3167 if(soughtPending && MatchSoughtLine(buf+i)) {
3168 i = strstr(buf+i, "rated") - buf;
3169 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3170 next_out = leftover_start = i;
3171 started = STARTED_CHATTER;
3172 suppressKibitz = TRUE;
3175 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3176 && looking_at(buf, &i, "* ads displayed")) {
3177 soughtPending = FALSE;
3182 if(appData.autoRefresh) {
3183 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3184 int s = (ics_type == ICS_ICC); // ICC format differs
3186 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3187 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3188 looking_at(buf, &i, "*% "); // eat prompt
3189 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3190 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3191 next_out = i; // suppress
3194 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3195 char *p = star_match[0];
3197 if(seekGraphUp) RemoveSeekAd(atoi(p));
3198 while(*p && *p++ != ' '); // next
3200 looking_at(buf, &i, "*% "); // eat prompt
3201 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3208 /* skip formula vars */
3209 if (started == STARTED_NONE &&
3210 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3211 started = STARTED_CHATTER;
3216 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3217 if (appData.autoKibitz && started == STARTED_NONE &&
3218 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3219 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3220 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3221 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3222 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3223 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3224 suppressKibitz = TRUE;
3225 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3227 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3228 && (gameMode == IcsPlayingWhite)) ||
3229 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3230 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3231 started = STARTED_CHATTER; // own kibitz we simply discard
3233 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3234 parse_pos = 0; parse[0] = NULLCHAR;
3235 savingComment = TRUE;
3236 suppressKibitz = gameMode != IcsObserving ? 2 :
3237 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3241 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3242 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3243 && atoi(star_match[0])) {
3244 // suppress the acknowledgements of our own autoKibitz
3246 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3247 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3248 SendToPlayer(star_match[0], strlen(star_match[0]));
3249 if(looking_at(buf, &i, "*% ")) // eat prompt
3250 suppressKibitz = FALSE;
3254 } // [HGM] kibitz: end of patch
3256 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3258 // [HGM] chat: intercept tells by users for which we have an open chat window
3260 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3261 looking_at(buf, &i, "* whispers:") ||
3262 looking_at(buf, &i, "* kibitzes:") ||
3263 looking_at(buf, &i, "* shouts:") ||
3264 looking_at(buf, &i, "* c-shouts:") ||
3265 looking_at(buf, &i, "--> * ") ||
3266 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3267 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3268 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3269 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3271 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3272 chattingPartner = -1;
3274 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3275 for(p=0; p<MAX_CHAT; p++) {
3276 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3277 talker[0] = '['; strcat(talker, "] ");
3278 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3279 chattingPartner = p; break;
3282 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3283 for(p=0; p<MAX_CHAT; p++) {
3284 if(!strcmp("kibitzes", chatPartner[p])) {
3285 talker[0] = '['; strcat(talker, "] ");
3286 chattingPartner = p; break;
3289 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3290 for(p=0; p<MAX_CHAT; p++) {
3291 if(!strcmp("whispers", chatPartner[p])) {
3292 talker[0] = '['; strcat(talker, "] ");
3293 chattingPartner = p; break;
3296 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3297 if(buf[i-8] == '-' && buf[i-3] == 't')
3298 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3299 if(!strcmp("c-shouts", chatPartner[p])) {
3300 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3301 chattingPartner = p; break;
3304 if(chattingPartner < 0)
3305 for(p=0; p<MAX_CHAT; p++) {
3306 if(!strcmp("shouts", chatPartner[p])) {
3307 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3308 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3309 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3310 chattingPartner = p; break;
3314 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3315 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3316 talker[0] = 0; Colorize(ColorTell, FALSE);
3317 chattingPartner = p; break;
3319 if(chattingPartner<0) i = oldi; else {
3320 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3321 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3322 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3323 started = STARTED_COMMENT;
3324 parse_pos = 0; parse[0] = NULLCHAR;
3325 savingComment = 3 + chattingPartner; // counts as TRUE
3326 suppressKibitz = TRUE;
3329 } // [HGM] chat: end of patch
3332 if (appData.zippyTalk || appData.zippyPlay) {
3333 /* [DM] Backup address for color zippy lines */
3335 if (loggedOn == TRUE)
3336 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3337 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3339 } // [DM] 'else { ' deleted
3341 /* Regular tells and says */
3342 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3343 looking_at(buf, &i, "* (your partner) tells you: ") ||
3344 looking_at(buf, &i, "* says: ") ||
3345 /* Don't color "message" or "messages" output */
3346 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3347 looking_at(buf, &i, "*. * at *:*: ") ||
3348 looking_at(buf, &i, "--* (*:*): ") ||
3349 /* Message notifications (same color as tells) */
3350 looking_at(buf, &i, "* has left a message ") ||
3351 looking_at(buf, &i, "* just sent you a message:\n") ||
3352 /* Whispers and kibitzes */
3353 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3354 looking_at(buf, &i, "* kibitzes: ") ||
3356 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3358 if (tkind == 1 && strchr(star_match[0], ':')) {
3359 /* Avoid "tells you:" spoofs in channels */
3362 if (star_match[0][0] == NULLCHAR ||
3363 strchr(star_match[0], ' ') ||
3364 (tkind == 3 && strchr(star_match[1], ' '))) {
3365 /* Reject bogus matches */
3368 if (appData.colorize) {
3369 if (oldi > next_out) {
3370 SendToPlayer(&buf[next_out], oldi - next_out);
3375 Colorize(ColorTell, FALSE);
3376 curColor = ColorTell;
3379 Colorize(ColorKibitz, FALSE);
3380 curColor = ColorKibitz;
3383 p = strrchr(star_match[1], '(');
3390 Colorize(ColorChannel1, FALSE);
3391 curColor = ColorChannel1;
3393 Colorize(ColorChannel, FALSE);
3394 curColor = ColorChannel;
3398 curColor = ColorNormal;
3402 if (started == STARTED_NONE && appData.autoComment &&
3403 (gameMode == IcsObserving ||
3404 gameMode == IcsPlayingWhite ||
3405 gameMode == IcsPlayingBlack)) {
3406 parse_pos = i - oldi;
3407 memcpy(parse, &buf[oldi], parse_pos);
3408 parse[parse_pos] = NULLCHAR;
3409 started = STARTED_COMMENT;
3410 savingComment = TRUE;
3412 started = STARTED_CHATTER;
3413 savingComment = FALSE;
3420 if (looking_at(buf, &i, "* s-shouts: ") ||
3421 looking_at(buf, &i, "* c-shouts: ")) {
3422 if (appData.colorize) {
3423 if (oldi > next_out) {
3424 SendToPlayer(&buf[next_out], oldi - next_out);
3427 Colorize(ColorSShout, FALSE);
3428 curColor = ColorSShout;
3431 started = STARTED_CHATTER;
3435 if (looking_at(buf, &i, "--->")) {
3440 if (looking_at(buf, &i, "* shouts: ") ||
3441 looking_at(buf, &i, "--> ")) {
3442 if (appData.colorize) {
3443 if (oldi > next_out) {
3444 SendToPlayer(&buf[next_out], oldi - next_out);
3447 Colorize(ColorShout, FALSE);
3448 curColor = ColorShout;
3451 started = STARTED_CHATTER;
3455 if (looking_at( buf, &i, "Challenge:")) {
3456 if (appData.colorize) {
3457 if (oldi > next_out) {
3458 SendToPlayer(&buf[next_out], oldi - next_out);
3461 Colorize(ColorChallenge, FALSE);
3462 curColor = ColorChallenge;
3468 if (looking_at(buf, &i, "* offers you") ||
3469 looking_at(buf, &i, "* offers to be") ||
3470 looking_at(buf, &i, "* would like to") ||
3471 looking_at(buf, &i, "* requests to") ||
3472 looking_at(buf, &i, "Your opponent offers") ||
3473 looking_at(buf, &i, "Your opponent requests")) {
3475 if (appData.colorize) {
3476 if (oldi > next_out) {
3477 SendToPlayer(&buf[next_out], oldi - next_out);
3480 Colorize(ColorRequest, FALSE);
3481 curColor = ColorRequest;
3486 if (looking_at(buf, &i, "* (*) seeking")) {
3487 if (appData.colorize) {
3488 if (oldi > next_out) {
3489 SendToPlayer(&buf[next_out], oldi - next_out);
3492 Colorize(ColorSeek, FALSE);
3493 curColor = ColorSeek;
3498 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3500 if (looking_at(buf, &i, "\\ ")) {
3501 if (prevColor != ColorNormal) {
3502 if (oldi > next_out) {
3503 SendToPlayer(&buf[next_out], oldi - next_out);
3506 Colorize(prevColor, TRUE);
3507 curColor = prevColor;
3509 if (savingComment) {
3510 parse_pos = i - oldi;
3511 memcpy(parse, &buf[oldi], parse_pos);
3512 parse[parse_pos] = NULLCHAR;
3513 started = STARTED_COMMENT;
3514 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3515 chattingPartner = savingComment - 3; // kludge to remember the box
3517 started = STARTED_CHATTER;
3522 if (looking_at(buf, &i, "Black Strength :") ||
3523 looking_at(buf, &i, "<<< style 10 board >>>") ||
3524 looking_at(buf, &i, "<10>") ||
3525 looking_at(buf, &i, "#@#")) {
3526 /* Wrong board style */
3528 SendToICS(ics_prefix);
3529 SendToICS("set style 12\n");
3530 SendToICS(ics_prefix);
3531 SendToICS("refresh\n");
3535 if (looking_at(buf, &i, "login:")) {
3536 if (!have_sent_ICS_logon) {
3538 have_sent_ICS_logon = 1;
3539 else // no init script was found
3540 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3541 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3542 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3547 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3548 (looking_at(buf, &i, "\n<12> ") ||
3549 looking_at(buf, &i, "<12> "))) {
3551 if (oldi > next_out) {
3552 SendToPlayer(&buf[next_out], oldi - next_out);
3555 started = STARTED_BOARD;
3560 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3561 looking_at(buf, &i, "<b1> ")) {
3562 if (oldi > next_out) {
3563 SendToPlayer(&buf[next_out], oldi - next_out);
3566 started = STARTED_HOLDINGS;
3571 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3573 /* Header for a move list -- first line */
3575 switch (ics_getting_history) {
3579 case BeginningOfGame:
3580 /* User typed "moves" or "oldmoves" while we
3581 were idle. Pretend we asked for these
3582 moves and soak them up so user can step
3583 through them and/or save them.
3586 gameMode = IcsObserving;
3589 ics_getting_history = H_GOT_UNREQ_HEADER;
3591 case EditGame: /*?*/
3592 case EditPosition: /*?*/
3593 /* Should above feature work in these modes too? */
3594 /* For now it doesn't */
3595 ics_getting_history = H_GOT_UNWANTED_HEADER;
3598 ics_getting_history = H_GOT_UNWANTED_HEADER;
3603 /* Is this the right one? */
3604 if (gameInfo.white && gameInfo.black &&
3605 strcmp(gameInfo.white, star_match[0]) == 0 &&
3606 strcmp(gameInfo.black, star_match[2]) == 0) {
3608 ics_getting_history = H_GOT_REQ_HEADER;
3611 case H_GOT_REQ_HEADER:
3612 case H_GOT_UNREQ_HEADER:
3613 case H_GOT_UNWANTED_HEADER:
3614 case H_GETTING_MOVES:
3615 /* Should not happen */
3616 DisplayError(_("Error gathering move list: two headers"), 0);
3617 ics_getting_history = H_FALSE;
3621 /* Save player ratings into gameInfo if needed */
3622 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3623 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3624 (gameInfo.whiteRating == -1 ||
3625 gameInfo.blackRating == -1)) {
3627 gameInfo.whiteRating = string_to_rating(star_match[1]);
3628 gameInfo.blackRating = string_to_rating(star_match[3]);
3629 if (appData.debugMode)
3630 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3631 gameInfo.whiteRating, gameInfo.blackRating);
3636 if (looking_at(buf, &i,
3637 "* * match, initial time: * minute*, increment: * second")) {
3638 /* Header for a move list -- second line */
3639 /* Initial board will follow if this is a wild game */
3640 if (gameInfo.event != NULL) free(gameInfo.event);
3641 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3642 gameInfo.event = StrSave(str);
3643 /* [HGM] we switched variant. Translate boards if needed. */
3644 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3648 if (looking_at(buf, &i, "Move ")) {
3649 /* Beginning of a move list */
3650 switch (ics_getting_history) {
3652 /* Normally should not happen */
3653 /* Maybe user hit reset while we were parsing */
3656 /* Happens if we are ignoring a move list that is not
3657 * the one we just requested. Common if the user
3658 * tries to observe two games without turning off
3661 case H_GETTING_MOVES:
3662 /* Should not happen */
3663 DisplayError(_("Error gathering move list: nested"), 0);
3664 ics_getting_history = H_FALSE;
3666 case H_GOT_REQ_HEADER:
3667 ics_getting_history = H_GETTING_MOVES;
3668 started = STARTED_MOVES;
3670 if (oldi > next_out) {
3671 SendToPlayer(&buf[next_out], oldi - next_out);
3674 case H_GOT_UNREQ_HEADER:
3675 ics_getting_history = H_GETTING_MOVES;
3676 started = STARTED_MOVES_NOHIDE;
3679 case H_GOT_UNWANTED_HEADER:
3680 ics_getting_history = H_FALSE;
3686 if (looking_at(buf, &i, "% ") ||
3687 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3688 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3689 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3690 soughtPending = FALSE;
3694 if(suppressKibitz) next_out = i;
3695 savingComment = FALSE;
3699 case STARTED_MOVES_NOHIDE:
3700 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3701 parse[parse_pos + i - oldi] = NULLCHAR;
3702 ParseGameHistory(parse);
3704 if (appData.zippyPlay && first.initDone) {
3705 FeedMovesToProgram(&first, forwardMostMove);
3706 if (gameMode == IcsPlayingWhite) {
3707 if (WhiteOnMove(forwardMostMove)) {
3708 if (first.sendTime) {
3709 if (first.useColors) {
3710 SendToProgram("black\n", &first);
3712 SendTimeRemaining(&first, TRUE);
3714 if (first.useColors) {
3715 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3717 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3718 first.maybeThinking = TRUE;
3720 if (first.usePlayother) {
3721 if (first.sendTime) {
3722 SendTimeRemaining(&first, TRUE);
3724 SendToProgram("playother\n", &first);
3730 } else if (gameMode == IcsPlayingBlack) {
3731 if (!WhiteOnMove(forwardMostMove)) {
3732 if (first.sendTime) {
3733 if (first.useColors) {
3734 SendToProgram("white\n", &first);
3736 SendTimeRemaining(&first, FALSE);
3738 if (first.useColors) {
3739 SendToProgram("black\n", &first);
3741 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3742 first.maybeThinking = TRUE;
3744 if (first.usePlayother) {
3745 if (first.sendTime) {
3746 SendTimeRemaining(&first, FALSE);
3748 SendToProgram("playother\n", &first);
3757 if (gameMode == IcsObserving && ics_gamenum == -1) {
3758 /* Moves came from oldmoves or moves command
3759 while we weren't doing anything else.
3761 currentMove = forwardMostMove;
3762 ClearHighlights();/*!!could figure this out*/
3763 flipView = appData.flipView;
3764 DrawPosition(TRUE, boards[currentMove]);
3765 DisplayBothClocks();
3766 snprintf(str, MSG_SIZ, "%s %s %s",
3767 gameInfo.white, _("vs."), gameInfo.black);
3771 /* Moves were history of an active game */
3772 if (gameInfo.resultDetails != NULL) {
3773 free(gameInfo.resultDetails);
3774 gameInfo.resultDetails = NULL;
3777 HistorySet(parseList, backwardMostMove,
3778 forwardMostMove, currentMove-1);
3779 DisplayMove(currentMove - 1);
3780 if (started == STARTED_MOVES) next_out = i;
3781 started = STARTED_NONE;
3782 ics_getting_history = H_FALSE;
3785 case STARTED_OBSERVE:
3786 started = STARTED_NONE;
3787 SendToICS(ics_prefix);
3788 SendToICS("refresh\n");
3794 if(bookHit) { // [HGM] book: simulate book reply
3795 static char bookMove[MSG_SIZ]; // a bit generous?
3797 programStats.nodes = programStats.depth = programStats.time =
3798 programStats.score = programStats.got_only_move = 0;
3799 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3801 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3802 strcat(bookMove, bookHit);
3803 HandleMachineMove(bookMove, &first);
3808 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3809 started == STARTED_HOLDINGS ||
3810 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3811 /* Accumulate characters in move list or board */
3812 parse[parse_pos++] = buf[i];
3815 /* Start of game messages. Mostly we detect start of game
3816 when the first board image arrives. On some versions
3817 of the ICS, though, we need to do a "refresh" after starting
3818 to observe in order to get the current board right away. */
3819 if (looking_at(buf, &i, "Adding game * to observation list")) {
3820 started = STARTED_OBSERVE;
3824 /* Handle auto-observe */
3825 if (appData.autoObserve &&
3826 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3827 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3829 /* Choose the player that was highlighted, if any. */
3830 if (star_match[0][0] == '\033' ||
3831 star_match[1][0] != '\033') {
3832 player = star_match[0];
3834 player = star_match[2];
3836 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3837 ics_prefix, StripHighlightAndTitle(player));
3840 /* Save ratings from notify string */
3841 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3842 player1Rating = string_to_rating(star_match[1]);
3843 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3844 player2Rating = string_to_rating(star_match[3]);
3846 if (appData.debugMode)
3848 "Ratings from 'Game notification:' %s %d, %s %d\n",
3849 player1Name, player1Rating,
3850 player2Name, player2Rating);
3855 /* Deal with automatic examine mode after a game,
3856 and with IcsObserving -> IcsExamining transition */
3857 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3858 looking_at(buf, &i, "has made you an examiner of game *")) {
3860 int gamenum = atoi(star_match[0]);
3861 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3862 gamenum == ics_gamenum) {
3863 /* We were already playing or observing this game;
3864 no need to refetch history */
3865 gameMode = IcsExamining;
3867 pauseExamForwardMostMove = forwardMostMove;
3868 } else if (currentMove < forwardMostMove) {
3869 ForwardInner(forwardMostMove);
3872 /* I don't think this case really can happen */
3873 SendToICS(ics_prefix);
3874 SendToICS("refresh\n");
3879 /* Error messages */
3880 // if (ics_user_moved) {
3881 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3882 if (looking_at(buf, &i, "Illegal move") ||
3883 looking_at(buf, &i, "Not a legal move") ||
3884 looking_at(buf, &i, "Your king is in check") ||
3885 looking_at(buf, &i, "It isn't your turn") ||
3886 looking_at(buf, &i, "It is not your move")) {
3888 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3889 currentMove = forwardMostMove-1;
3890 DisplayMove(currentMove - 1); /* before DMError */
3891 DrawPosition(FALSE, boards[currentMove]);
3892 SwitchClocks(forwardMostMove-1); // [HGM] race
3893 DisplayBothClocks();
3895 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3901 if (looking_at(buf, &i, "still have time") ||
3902 looking_at(buf, &i, "not out of time") ||
3903 looking_at(buf, &i, "either player is out of time") ||
3904 looking_at(buf, &i, "has timeseal; checking")) {
3905 /* We must have called his flag a little too soon */
3906 whiteFlag = blackFlag = FALSE;
3910 if (looking_at(buf, &i, "added * seconds to") ||
3911 looking_at(buf, &i, "seconds were added to")) {
3912 /* Update the clocks */
3913 SendToICS(ics_prefix);
3914 SendToICS("refresh\n");
3918 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3919 ics_clock_paused = TRUE;
3924 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3925 ics_clock_paused = FALSE;
3930 /* Grab player ratings from the Creating: message.
3931 Note we have to check for the special case when
3932 the ICS inserts things like [white] or [black]. */
3933 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3934 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3936 0 player 1 name (not necessarily white)
3938 2 empty, white, or black (IGNORED)
3939 3 player 2 name (not necessarily black)
3942 The names/ratings are sorted out when the game
3943 actually starts (below).
3945 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3946 player1Rating = string_to_rating(star_match[1]);
3947 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3948 player2Rating = string_to_rating(star_match[4]);
3950 if (appData.debugMode)
3952 "Ratings from 'Creating:' %s %d, %s %d\n",
3953 player1Name, player1Rating,
3954 player2Name, player2Rating);
3959 /* Improved generic start/end-of-game messages */
3960 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3961 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3962 /* If tkind == 0: */
3963 /* star_match[0] is the game number */
3964 /* [1] is the white player's name */
3965 /* [2] is the black player's name */
3966 /* For end-of-game: */
3967 /* [3] is the reason for the game end */
3968 /* [4] is a PGN end game-token, preceded by " " */
3969 /* For start-of-game: */
3970 /* [3] begins with "Creating" or "Continuing" */
3971 /* [4] is " *" or empty (don't care). */
3972 int gamenum = atoi(star_match[0]);
3973 char *whitename, *blackname, *why, *endtoken;
3974 ChessMove endtype = EndOfFile;
3977 whitename = star_match[1];
3978 blackname = star_match[2];
3979 why = star_match[3];
3980 endtoken = star_match[4];
3982 whitename = star_match[1];
3983 blackname = star_match[3];
3984 why = star_match[5];
3985 endtoken = star_match[6];
3988 /* Game start messages */
3989 if (strncmp(why, "Creating ", 9) == 0 ||
3990 strncmp(why, "Continuing ", 11) == 0) {
3991 gs_gamenum = gamenum;
3992 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3993 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3994 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3996 if (appData.zippyPlay) {
3997 ZippyGameStart(whitename, blackname);
4000 partnerBoardValid = FALSE; // [HGM] bughouse
4004 /* Game end messages */
4005 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4006 ics_gamenum != gamenum) {
4009 while (endtoken[0] == ' ') endtoken++;
4010 switch (endtoken[0]) {
4013 endtype = GameUnfinished;
4016 endtype = BlackWins;
4019 if (endtoken[1] == '/')
4020 endtype = GameIsDrawn;
4022 endtype = WhiteWins;
4025 GameEnds(endtype, why, GE_ICS);
4027 if (appData.zippyPlay && first.initDone) {
4028 ZippyGameEnd(endtype, why);
4029 if (first.pr == NoProc) {
4030 /* Start the next process early so that we'll
4031 be ready for the next challenge */
4032 StartChessProgram(&first);
4034 /* Send "new" early, in case this command takes
4035 a long time to finish, so that we'll be ready
4036 for the next challenge. */
4037 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4041 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4045 if (looking_at(buf, &i, "Removing game * from observation") ||
4046 looking_at(buf, &i, "no longer observing game *") ||
4047 looking_at(buf, &i, "Game * (*) has no examiners")) {
4048 if (gameMode == IcsObserving &&
4049 atoi(star_match[0]) == ics_gamenum)
4051 /* icsEngineAnalyze */
4052 if (appData.icsEngineAnalyze) {
4059 ics_user_moved = FALSE;
4064 if (looking_at(buf, &i, "no longer examining game *")) {
4065 if (gameMode == IcsExamining &&
4066 atoi(star_match[0]) == ics_gamenum)
4070 ics_user_moved = FALSE;
4075 /* Advance leftover_start past any newlines we find,
4076 so only partial lines can get reparsed */
4077 if (looking_at(buf, &i, "\n")) {
4078 prevColor = curColor;
4079 if (curColor != ColorNormal) {
4080 if (oldi > next_out) {
4081 SendToPlayer(&buf[next_out], oldi - next_out);
4084 Colorize(ColorNormal, FALSE);
4085 curColor = ColorNormal;
4087 if (started == STARTED_BOARD) {
4088 started = STARTED_NONE;
4089 parse[parse_pos] = NULLCHAR;
4090 ParseBoard12(parse);
4093 /* Send premove here */
4094 if (appData.premove) {
4096 if (currentMove == 0 &&
4097 gameMode == IcsPlayingWhite &&
4098 appData.premoveWhite) {
4099 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4100 if (appData.debugMode)
4101 fprintf(debugFP, "Sending premove:\n");
4103 } else if (currentMove == 1 &&
4104 gameMode == IcsPlayingBlack &&
4105 appData.premoveBlack) {
4106 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4107 if (appData.debugMode)
4108 fprintf(debugFP, "Sending premove:\n");
4110 } else if (gotPremove) {
4112 ClearPremoveHighlights();
4113 if (appData.debugMode)
4114 fprintf(debugFP, "Sending premove:\n");
4115 UserMoveEvent(premoveFromX, premoveFromY,
4116 premoveToX, premoveToY,
4121 /* Usually suppress following prompt */
4122 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4123 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4124 if (looking_at(buf, &i, "*% ")) {
4125 savingComment = FALSE;
4130 } else if (started == STARTED_HOLDINGS) {
4132 char new_piece[MSG_SIZ];
4133 started = STARTED_NONE;
4134 parse[parse_pos] = NULLCHAR;
4135 if (appData.debugMode)
4136 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4137 parse, currentMove);
4138 if (sscanf(parse, " game %d", &gamenum) == 1) {
4139 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4140 if (gameInfo.variant == VariantNormal) {
4141 /* [HGM] We seem to switch variant during a game!
4142 * Presumably no holdings were displayed, so we have
4143 * to move the position two files to the right to
4144 * create room for them!
4146 VariantClass newVariant;
4147 switch(gameInfo.boardWidth) { // base guess on board width
4148 case 9: newVariant = VariantShogi; break;
4149 case 10: newVariant = VariantGreat; break;
4150 default: newVariant = VariantCrazyhouse; break;
4152 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4153 /* Get a move list just to see the header, which
4154 will tell us whether this is really bug or zh */
4155 if (ics_getting_history == H_FALSE) {
4156 ics_getting_history = H_REQUESTED;
4157 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4161 new_piece[0] = NULLCHAR;
4162 sscanf(parse, "game %d white [%s black [%s <- %s",
4163 &gamenum, white_holding, black_holding,
4165 white_holding[strlen(white_holding)-1] = NULLCHAR;
4166 black_holding[strlen(black_holding)-1] = NULLCHAR;
4167 /* [HGM] copy holdings to board holdings area */
4168 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4169 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4170 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4172 if (appData.zippyPlay && first.initDone) {
4173 ZippyHoldings(white_holding, black_holding,
4177 if (tinyLayout || smallLayout) {
4178 char wh[16], bh[16];
4179 PackHolding(wh, white_holding);
4180 PackHolding(bh, black_holding);
4181 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4182 gameInfo.white, gameInfo.black);
4184 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4185 gameInfo.white, white_holding, _("vs."),
4186 gameInfo.black, black_holding);
4188 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4189 DrawPosition(FALSE, boards[currentMove]);
4191 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4192 sscanf(parse, "game %d white [%s black [%s <- %s",
4193 &gamenum, white_holding, black_holding,
4195 white_holding[strlen(white_holding)-1] = NULLCHAR;
4196 black_holding[strlen(black_holding)-1] = NULLCHAR;
4197 /* [HGM] copy holdings to partner-board holdings area */
4198 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4199 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4200 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4201 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4202 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4205 /* Suppress following prompt */
4206 if (looking_at(buf, &i, "*% ")) {
4207 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4208 savingComment = FALSE;
4216 i++; /* skip unparsed character and loop back */
4219 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4220 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4221 // SendToPlayer(&buf[next_out], i - next_out);
4222 started != STARTED_HOLDINGS && leftover_start > next_out) {
4223 SendToPlayer(&buf[next_out], leftover_start - next_out);
4227 leftover_len = buf_len - leftover_start;
4228 /* if buffer ends with something we couldn't parse,
4229 reparse it after appending the next read */
4231 } else if (count == 0) {
4232 RemoveInputSource(isr);
4233 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4235 DisplayFatalError(_("Error reading from ICS"), error, 1);
4240 /* Board style 12 looks like this:
4242 <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
4244 * The "<12> " is stripped before it gets to this routine. The two
4245 * trailing 0's (flip state and clock ticking) are later addition, and
4246 * some chess servers may not have them, or may have only the first.
4247 * Additional trailing fields may be added in the future.
4250 #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"
4252 #define RELATION_OBSERVING_PLAYED 0
4253 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4254 #define RELATION_PLAYING_MYMOVE 1
4255 #define RELATION_PLAYING_NOTMYMOVE -1
4256 #define RELATION_EXAMINING 2
4257 #define RELATION_ISOLATED_BOARD -3
4258 #define RELATION_STARTING_POSITION -4 /* FICS only */
4261 ParseBoard12 (char *string)
4265 char *bookHit = NULL; // [HGM] book
4267 GameMode newGameMode;
4268 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4269 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4270 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4271 char to_play, board_chars[200];
4272 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4273 char black[32], white[32];
4275 int prevMove = currentMove;
4278 int fromX, fromY, toX, toY;
4280 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4281 Boolean weird = FALSE, reqFlag = FALSE;
4283 fromX = fromY = toX = toY = -1;
4287 if (appData.debugMode)
4288 fprintf(debugFP, "Parsing board: %s\n", string);
4290 move_str[0] = NULLCHAR;
4291 elapsed_time[0] = NULLCHAR;
4292 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4294 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4295 if(string[i] == ' ') { ranks++; files = 0; }
4297 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4300 for(j = 0; j <i; j++) board_chars[j] = string[j];
4301 board_chars[i] = '\0';
4304 n = sscanf(string, PATTERN, &to_play, &double_push,
4305 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4306 &gamenum, white, black, &relation, &basetime, &increment,
4307 &white_stren, &black_stren, &white_time, &black_time,
4308 &moveNum, str, elapsed_time, move_str, &ics_flip,
4312 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4313 DisplayError(str, 0);
4317 /* Convert the move number to internal form */
4318 moveNum = (moveNum - 1) * 2;
4319 if (to_play == 'B') moveNum++;
4320 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4321 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4327 case RELATION_OBSERVING_PLAYED:
4328 case RELATION_OBSERVING_STATIC:
4329 if (gamenum == -1) {
4330 /* Old ICC buglet */
4331 relation = RELATION_OBSERVING_STATIC;
4333 newGameMode = IcsObserving;
4335 case RELATION_PLAYING_MYMOVE:
4336 case RELATION_PLAYING_NOTMYMOVE:
4338 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4339 IcsPlayingWhite : IcsPlayingBlack;
4340 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4342 case RELATION_EXAMINING:
4343 newGameMode = IcsExamining;
4345 case RELATION_ISOLATED_BOARD:
4347 /* Just display this board. If user was doing something else,
4348 we will forget about it until the next board comes. */
4349 newGameMode = IcsIdle;
4351 case RELATION_STARTING_POSITION:
4352 newGameMode = gameMode;
4356 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4357 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4358 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4359 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4360 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4361 static int lastBgGame = -1;
4363 for (k = 0; k < ranks; k++) {
4364 for (j = 0; j < files; j++)
4365 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4366 if(gameInfo.holdingsWidth > 1) {
4367 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4368 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4371 CopyBoard(partnerBoard, board);
4372 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4373 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4374 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4375 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4376 if(toSqr = strchr(str, '-')) {
4377 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4378 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4379 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4380 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4381 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4382 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4384 DisplayWhiteClock(white_time*fac, to_play == 'W');
4385 DisplayBlackClock(black_time*fac, to_play != 'W');
4386 activePartner = to_play;
4387 if(gamenum != lastBgGame) {
4389 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4392 lastBgGame = gamenum;
4393 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4394 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4395 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4396 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4397 if(!twoBoards) DisplayMessage(partnerStatus, "");
4398 partnerBoardValid = TRUE;
4402 if(appData.dualBoard && appData.bgObserve) {
4403 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4404 SendToICS(ics_prefix), SendToICS("pobserve\n");
4405 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4407 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4412 /* Modify behavior for initial board display on move listing
4415 switch (ics_getting_history) {
4419 case H_GOT_REQ_HEADER:
4420 case H_GOT_UNREQ_HEADER:
4421 /* This is the initial position of the current game */
4422 gamenum = ics_gamenum;
4423 moveNum = 0; /* old ICS bug workaround */
4424 if (to_play == 'B') {
4425 startedFromSetupPosition = TRUE;
4426 blackPlaysFirst = TRUE;
4428 if (forwardMostMove == 0) forwardMostMove = 1;
4429 if (backwardMostMove == 0) backwardMostMove = 1;
4430 if (currentMove == 0) currentMove = 1;
4432 newGameMode = gameMode;
4433 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4435 case H_GOT_UNWANTED_HEADER:
4436 /* This is an initial board that we don't want */
4438 case H_GETTING_MOVES:
4439 /* Should not happen */
4440 DisplayError(_("Error gathering move list: extra board"), 0);
4441 ics_getting_history = H_FALSE;
4445 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4446 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4447 weird && (int)gameInfo.variant < (int)VariantShogi) {
4448 /* [HGM] We seem to have switched variant unexpectedly
4449 * Try to guess new variant from board size
4451 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4452 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4453 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4454 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4455 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4456 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4457 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4458 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4459 /* Get a move list just to see the header, which
4460 will tell us whether this is really bug or zh */
4461 if (ics_getting_history == H_FALSE) {
4462 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4463 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4468 /* Take action if this is the first board of a new game, or of a
4469 different game than is currently being displayed. */
4470 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4471 relation == RELATION_ISOLATED_BOARD) {
4473 /* Forget the old game and get the history (if any) of the new one */
4474 if (gameMode != BeginningOfGame) {
4478 if (appData.autoRaiseBoard) BoardToTop();
4480 if (gamenum == -1) {
4481 newGameMode = IcsIdle;
4482 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4483 appData.getMoveList && !reqFlag) {
4484 /* Need to get game history */
4485 ics_getting_history = H_REQUESTED;
4486 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4490 /* Initially flip the board to have black on the bottom if playing
4491 black or if the ICS flip flag is set, but let the user change
4492 it with the Flip View button. */
4493 flipView = appData.autoFlipView ?
4494 (newGameMode == IcsPlayingBlack) || ics_flip :
4497 /* Done with values from previous mode; copy in new ones */
4498 gameMode = newGameMode;
4500 ics_gamenum = gamenum;
4501 if (gamenum == gs_gamenum) {
4502 int klen = strlen(gs_kind);
4503 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4504 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4505 gameInfo.event = StrSave(str);
4507 gameInfo.event = StrSave("ICS game");
4509 gameInfo.site = StrSave(appData.icsHost);
4510 gameInfo.date = PGNDate();
4511 gameInfo.round = StrSave("-");
4512 gameInfo.white = StrSave(white);
4513 gameInfo.black = StrSave(black);
4514 timeControl = basetime * 60 * 1000;
4516 timeIncrement = increment * 1000;
4517 movesPerSession = 0;
4518 gameInfo.timeControl = TimeControlTagValue();
4519 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4520 if (appData.debugMode) {
4521 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4522 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4523 setbuf(debugFP, NULL);
4526 gameInfo.outOfBook = NULL;
4528 /* Do we have the ratings? */
4529 if (strcmp(player1Name, white) == 0 &&
4530 strcmp(player2Name, black) == 0) {
4531 if (appData.debugMode)
4532 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4533 player1Rating, player2Rating);
4534 gameInfo.whiteRating = player1Rating;
4535 gameInfo.blackRating = player2Rating;
4536 } else if (strcmp(player2Name, white) == 0 &&
4537 strcmp(player1Name, black) == 0) {
4538 if (appData.debugMode)
4539 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4540 player2Rating, player1Rating);
4541 gameInfo.whiteRating = player2Rating;
4542 gameInfo.blackRating = player1Rating;
4544 player1Name[0] = player2Name[0] = NULLCHAR;
4546 /* Silence shouts if requested */
4547 if (appData.quietPlay &&
4548 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4549 SendToICS(ics_prefix);
4550 SendToICS("set shout 0\n");
4554 /* Deal with midgame name changes */
4556 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4557 if (gameInfo.white) free(gameInfo.white);
4558 gameInfo.white = StrSave(white);
4560 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4561 if (gameInfo.black) free(gameInfo.black);
4562 gameInfo.black = StrSave(black);
4566 /* Throw away game result if anything actually changes in examine mode */
4567 if (gameMode == IcsExamining && !newGame) {
4568 gameInfo.result = GameUnfinished;
4569 if (gameInfo.resultDetails != NULL) {
4570 free(gameInfo.resultDetails);
4571 gameInfo.resultDetails = NULL;
4575 /* In pausing && IcsExamining mode, we ignore boards coming
4576 in if they are in a different variation than we are. */
4577 if (pauseExamInvalid) return;
4578 if (pausing && gameMode == IcsExamining) {
4579 if (moveNum <= pauseExamForwardMostMove) {
4580 pauseExamInvalid = TRUE;
4581 forwardMostMove = pauseExamForwardMostMove;
4586 if (appData.debugMode) {
4587 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4589 /* Parse the board */
4590 for (k = 0; k < ranks; k++) {
4591 for (j = 0; j < files; j++)
4592 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4593 if(gameInfo.holdingsWidth > 1) {
4594 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4595 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4598 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4599 board[5][BOARD_RGHT+1] = WhiteAngel;
4600 board[6][BOARD_RGHT+1] = WhiteMarshall;
4601 board[1][0] = BlackMarshall;
4602 board[2][0] = BlackAngel;
4603 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4605 CopyBoard(boards[moveNum], board);
4606 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4608 startedFromSetupPosition =
4609 !CompareBoards(board, initialPosition);
4610 if(startedFromSetupPosition)
4611 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4614 /* [HGM] Set castling rights. Take the outermost Rooks,
4615 to make it also work for FRC opening positions. Note that board12
4616 is really defective for later FRC positions, as it has no way to
4617 indicate which Rook can castle if they are on the same side of King.
4618 For the initial position we grant rights to the outermost Rooks,
4619 and remember thos rights, and we then copy them on positions
4620 later in an FRC game. This means WB might not recognize castlings with
4621 Rooks that have moved back to their original position as illegal,
4622 but in ICS mode that is not its job anyway.
4624 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4625 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4627 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4628 if(board[0][i] == WhiteRook) j = i;
4629 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4630 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4631 if(board[0][i] == WhiteRook) j = i;
4632 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4633 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4634 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4635 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4636 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4637 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4638 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4640 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4641 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4642 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4643 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4644 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4645 if(board[BOARD_HEIGHT-1][k] == bKing)
4646 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4647 if(gameInfo.variant == VariantTwoKings) {
4648 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4649 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4650 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4653 r = boards[moveNum][CASTLING][0] = initialRights[0];
4654 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4655 r = boards[moveNum][CASTLING][1] = initialRights[1];
4656 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4657 r = boards[moveNum][CASTLING][3] = initialRights[3];
4658 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4659 r = boards[moveNum][CASTLING][4] = initialRights[4];
4660 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4661 /* wildcastle kludge: always assume King has rights */
4662 r = boards[moveNum][CASTLING][2] = initialRights[2];
4663 r = boards[moveNum][CASTLING][5] = initialRights[5];
4665 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4666 boards[moveNum][EP_STATUS] = EP_NONE;
4667 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4668 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4669 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4672 if (ics_getting_history == H_GOT_REQ_HEADER ||
4673 ics_getting_history == H_GOT_UNREQ_HEADER) {
4674 /* This was an initial position from a move list, not
4675 the current position */
4679 /* Update currentMove and known move number limits */
4680 newMove = newGame || moveNum > forwardMostMove;
4683 forwardMostMove = backwardMostMove = currentMove = moveNum;
4684 if (gameMode == IcsExamining && moveNum == 0) {
4685 /* Workaround for ICS limitation: we are not told the wild
4686 type when starting to examine a game. But if we ask for
4687 the move list, the move list header will tell us */
4688 ics_getting_history = H_REQUESTED;
4689 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4692 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4693 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4695 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4696 /* [HGM] applied this also to an engine that is silently watching */
4697 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4698 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4699 gameInfo.variant == currentlyInitializedVariant) {
4700 takeback = forwardMostMove - moveNum;
4701 for (i = 0; i < takeback; i++) {
4702 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4703 SendToProgram("undo\n", &first);
4708 forwardMostMove = moveNum;
4709 if (!pausing || currentMove > forwardMostMove)
4710 currentMove = forwardMostMove;
4712 /* New part of history that is not contiguous with old part */
4713 if (pausing && gameMode == IcsExamining) {
4714 pauseExamInvalid = TRUE;
4715 forwardMostMove = pauseExamForwardMostMove;
4718 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4720 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4721 // [HGM] when we will receive the move list we now request, it will be
4722 // fed to the engine from the first move on. So if the engine is not
4723 // in the initial position now, bring it there.
4724 InitChessProgram(&first, 0);
4727 ics_getting_history = H_REQUESTED;
4728 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4731 forwardMostMove = backwardMostMove = currentMove = moveNum;
4734 /* Update the clocks */
4735 if (strchr(elapsed_time, '.')) {
4737 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4738 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4740 /* Time is in seconds */
4741 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4742 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4747 if (appData.zippyPlay && newGame &&
4748 gameMode != IcsObserving && gameMode != IcsIdle &&
4749 gameMode != IcsExamining)
4750 ZippyFirstBoard(moveNum, basetime, increment);
4753 /* Put the move on the move list, first converting
4754 to canonical algebraic form. */
4756 if (appData.debugMode) {
4757 int f = forwardMostMove;
4758 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4759 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4760 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4761 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4762 fprintf(debugFP, "moveNum = %d\n", moveNum);
4763 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4764 setbuf(debugFP, NULL);
4766 if (moveNum <= backwardMostMove) {
4767 /* We don't know what the board looked like before
4769 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4770 strcat(parseList[moveNum - 1], " ");
4771 strcat(parseList[moveNum - 1], elapsed_time);
4772 moveList[moveNum - 1][0] = NULLCHAR;
4773 } else if (strcmp(move_str, "none") == 0) {
4774 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4775 /* Again, we don't know what the board looked like;
4776 this is really the start of the game. */
4777 parseList[moveNum - 1][0] = NULLCHAR;
4778 moveList[moveNum - 1][0] = NULLCHAR;
4779 backwardMostMove = moveNum;
4780 startedFromSetupPosition = TRUE;
4781 fromX = fromY = toX = toY = -1;
4783 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4784 // So we parse the long-algebraic move string in stead of the SAN move
4785 int valid; char buf[MSG_SIZ], *prom;
4787 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4788 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4789 // str looks something like "Q/a1-a2"; kill the slash
4791 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4792 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4793 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4794 strcat(buf, prom); // long move lacks promo specification!
4795 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4796 if(appData.debugMode)
4797 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4798 safeStrCpy(move_str, buf, MSG_SIZ);
4800 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4801 &fromX, &fromY, &toX, &toY, &promoChar)
4802 || ParseOneMove(buf, moveNum - 1, &moveType,
4803 &fromX, &fromY, &toX, &toY, &promoChar);
4804 // end of long SAN patch
4806 (void) CoordsToAlgebraic(boards[moveNum - 1],
4807 PosFlags(moveNum - 1),
4808 fromY, fromX, toY, toX, promoChar,
4809 parseList[moveNum-1]);
4810 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4816 if(!IS_SHOGI(gameInfo.variant))
4817 strcat(parseList[moveNum - 1], "+");
4820 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4821 strcat(parseList[moveNum - 1], "#");
4824 strcat(parseList[moveNum - 1], " ");
4825 strcat(parseList[moveNum - 1], elapsed_time);
4826 /* currentMoveString is set as a side-effect of ParseOneMove */
4827 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4828 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4829 strcat(moveList[moveNum - 1], "\n");
4831 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4832 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4833 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4834 ChessSquare old, new = boards[moveNum][k][j];
4835 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4836 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4837 if(old == new) continue;
4838 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4839 else if(new == WhiteWazir || new == BlackWazir) {
4840 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4841 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4842 else boards[moveNum][k][j] = old; // preserve type of Gold
4843 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4844 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4847 /* Move from ICS was illegal!? Punt. */
4848 if (appData.debugMode) {
4849 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4850 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4852 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4853 strcat(parseList[moveNum - 1], " ");
4854 strcat(parseList[moveNum - 1], elapsed_time);
4855 moveList[moveNum - 1][0] = NULLCHAR;
4856 fromX = fromY = toX = toY = -1;
4859 if (appData.debugMode) {
4860 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4861 setbuf(debugFP, NULL);
4865 /* Send move to chess program (BEFORE animating it). */
4866 if (appData.zippyPlay && !newGame && newMove &&
4867 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4869 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4870 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4871 if (moveList[moveNum - 1][0] == NULLCHAR) {
4872 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4874 DisplayError(str, 0);
4876 if (first.sendTime) {
4877 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4879 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4880 if (firstMove && !bookHit) {
4882 if (first.useColors) {
4883 SendToProgram(gameMode == IcsPlayingWhite ?
4885 "black\ngo\n", &first);
4887 SendToProgram("go\n", &first);
4889 first.maybeThinking = TRUE;
4892 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4893 if (moveList[moveNum - 1][0] == NULLCHAR) {
4894 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4895 DisplayError(str, 0);
4897 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4898 SendMoveToProgram(moveNum - 1, &first);
4905 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4906 /* If move comes from a remote source, animate it. If it
4907 isn't remote, it will have already been animated. */
4908 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4909 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4911 if (!pausing && appData.highlightLastMove) {
4912 SetHighlights(fromX, fromY, toX, toY);
4916 /* Start the clocks */
4917 whiteFlag = blackFlag = FALSE;
4918 appData.clockMode = !(basetime == 0 && increment == 0);
4920 ics_clock_paused = TRUE;
4922 } else if (ticking == 1) {
4923 ics_clock_paused = FALSE;
4925 if (gameMode == IcsIdle ||
4926 relation == RELATION_OBSERVING_STATIC ||
4927 relation == RELATION_EXAMINING ||
4929 DisplayBothClocks();
4933 /* Display opponents and material strengths */
4934 if (gameInfo.variant != VariantBughouse &&
4935 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4936 if (tinyLayout || smallLayout) {
4937 if(gameInfo.variant == VariantNormal)
4938 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4939 gameInfo.white, white_stren, gameInfo.black, black_stren,
4940 basetime, increment);
4942 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4943 gameInfo.white, white_stren, gameInfo.black, black_stren,
4944 basetime, increment, (int) gameInfo.variant);
4946 if(gameInfo.variant == VariantNormal)
4947 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4948 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4949 basetime, increment);
4951 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4952 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4953 basetime, increment, VariantName(gameInfo.variant));
4956 if (appData.debugMode) {
4957 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4962 /* Display the board */
4963 if (!pausing && !appData.noGUI) {
4965 if (appData.premove)
4967 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4968 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4969 ClearPremoveHighlights();
4971 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4972 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4973 DrawPosition(j, boards[currentMove]);
4975 DisplayMove(moveNum - 1);
4976 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4977 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4978 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4979 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4983 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4985 if(bookHit) { // [HGM] book: simulate book reply
4986 static char bookMove[MSG_SIZ]; // a bit generous?
4988 programStats.nodes = programStats.depth = programStats.time =
4989 programStats.score = programStats.got_only_move = 0;
4990 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4992 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4993 strcat(bookMove, bookHit);
4994 HandleMachineMove(bookMove, &first);
5003 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5004 ics_getting_history = H_REQUESTED;
5005 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5011 SendToBoth (char *msg)
5012 { // to make it easy to keep two engines in step in dual analysis
5013 SendToProgram(msg, &first);
5014 if(second.analyzing) SendToProgram(msg, &second);
5018 AnalysisPeriodicEvent (int force)
5020 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5021 && !force) || !appData.periodicUpdates)
5024 /* Send . command to Crafty to collect stats */
5027 /* Don't send another until we get a response (this makes
5028 us stop sending to old Crafty's which don't understand
5029 the "." command (sending illegal cmds resets node count & time,
5030 which looks bad)) */
5031 programStats.ok_to_send = 0;
5035 ics_update_width (int new_width)
5037 ics_printf("set width %d\n", new_width);
5041 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5045 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5046 if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5047 sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5048 SendToProgram(buf, cps);
5051 // null move in variant where engine does not understand it (for analysis purposes)
5052 SendBoard(cps, moveNum + 1); // send position after move in stead.
5055 if (cps->useUsermove) {
5056 SendToProgram("usermove ", cps);
5060 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5061 int len = space - parseList[moveNum];
5062 memcpy(buf, parseList[moveNum], len);
5064 buf[len] = NULLCHAR;
5066 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5068 SendToProgram(buf, cps);
5070 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5071 AlphaRank(moveList[moveNum], 4);
5072 SendToProgram(moveList[moveNum], cps);
5073 AlphaRank(moveList[moveNum], 4); // and back
5075 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5076 * the engine. It would be nice to have a better way to identify castle
5078 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5079 && cps->useOOCastle) {
5080 int fromX = moveList[moveNum][0] - AAA;
5081 int fromY = moveList[moveNum][1] - ONE;
5082 int toX = moveList[moveNum][2] - AAA;
5083 int toY = moveList[moveNum][3] - ONE;
5084 if((boards[moveNum][fromY][fromX] == WhiteKing
5085 && boards[moveNum][toY][toX] == WhiteRook)
5086 || (boards[moveNum][fromY][fromX] == BlackKing
5087 && boards[moveNum][toY][toX] == BlackRook)) {
5088 if(toX > fromX) SendToProgram("O-O\n", cps);
5089 else SendToProgram("O-O-O\n", cps);
5091 else SendToProgram(moveList[moveNum], cps);
5093 if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5094 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5095 moveList[moveNum][5], moveList[moveNum][6] - '0',
5096 moveList[moveNum][5], moveList[moveNum][6] - '0',
5097 moveList[moveNum][2], moveList[moveNum][3] - '0');
5098 SendToProgram(buf, cps);
5100 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5101 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5102 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5103 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5104 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5106 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5107 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5108 SendToProgram(buf, cps);
5110 else SendToProgram(moveList[moveNum], cps);
5111 /* End of additions by Tord */
5114 /* [HGM] setting up the opening has brought engine in force mode! */
5115 /* Send 'go' if we are in a mode where machine should play. */
5116 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5117 (gameMode == TwoMachinesPlay ||
5119 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5121 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5122 SendToProgram("go\n", cps);
5123 if (appData.debugMode) {
5124 fprintf(debugFP, "(extra)\n");
5127 setboardSpoiledMachineBlack = 0;
5131 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5133 char user_move[MSG_SIZ];
5136 if(gameInfo.variant == VariantSChess && promoChar) {
5137 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5138 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5139 } else suffix[0] = NULLCHAR;
5143 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5144 (int)moveType, fromX, fromY, toX, toY);
5145 DisplayError(user_move + strlen("say "), 0);
5147 case WhiteKingSideCastle:
5148 case BlackKingSideCastle:
5149 case WhiteQueenSideCastleWild:
5150 case BlackQueenSideCastleWild:
5152 case WhiteHSideCastleFR:
5153 case BlackHSideCastleFR:
5155 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5157 case WhiteQueenSideCastle:
5158 case BlackQueenSideCastle:
5159 case WhiteKingSideCastleWild:
5160 case BlackKingSideCastleWild:
5162 case WhiteASideCastleFR:
5163 case BlackASideCastleFR:
5165 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5167 case WhiteNonPromotion:
5168 case BlackNonPromotion:
5169 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5171 case WhitePromotion:
5172 case BlackPromotion:
5173 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5174 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5175 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5176 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5177 PieceToChar(WhiteFerz));
5178 else if(gameInfo.variant == VariantGreat)
5179 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5180 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5181 PieceToChar(WhiteMan));
5183 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5184 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5190 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5191 ToUpper(PieceToChar((ChessSquare) fromX)),
5192 AAA + toX, ONE + toY);
5194 case IllegalMove: /* could be a variant we don't quite understand */
5195 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5197 case WhiteCapturesEnPassant:
5198 case BlackCapturesEnPassant:
5199 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5200 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5203 SendToICS(user_move);
5204 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5205 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5210 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5211 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5212 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5213 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5214 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5217 if(gameMode != IcsExamining) { // is this ever not the case?
5218 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5220 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5221 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5222 } else { // on FICS we must first go to general examine mode
5223 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5225 if(gameInfo.variant != VariantNormal) {
5226 // try figure out wild number, as xboard names are not always valid on ICS
5227 for(i=1; i<=36; i++) {
5228 snprintf(buf, MSG_SIZ, "wild/%d", i);
5229 if(StringToVariant(buf) == gameInfo.variant) break;
5231 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5232 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5233 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5234 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5235 SendToICS(ics_prefix);
5237 if(startedFromSetupPosition || backwardMostMove != 0) {
5238 fen = PositionToFEN(backwardMostMove, NULL, 1);
5239 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5240 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5242 } else { // FICS: everything has to set by separate bsetup commands
5243 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5244 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5246 if(!WhiteOnMove(backwardMostMove)) {
5247 SendToICS("bsetup tomove black\n");
5249 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5250 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5252 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5253 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5255 i = boards[backwardMostMove][EP_STATUS];
5256 if(i >= 0) { // set e.p.
5257 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5263 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5264 SendToICS("bsetup done\n"); // switch to normal examining.
5266 for(i = backwardMostMove; i<last; i++) {
5268 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5269 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5270 int len = strlen(moveList[i]);
5271 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5272 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5276 SendToICS(ics_prefix);
5277 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5280 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5283 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5285 if (rf == DROP_RANK) {
5286 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5287 sprintf(move, "%c@%c%c\n",
5288 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5290 if (promoChar == 'x' || promoChar == NULLCHAR) {
5291 sprintf(move, "%c%c%c%c\n",
5292 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5293 if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5295 sprintf(move, "%c%c%c%c%c\n",
5296 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5302 ProcessICSInitScript (FILE *f)
5306 while (fgets(buf, MSG_SIZ, f)) {
5307 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5314 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5316 static ClickType lastClickType;
5319 Partner (ChessSquare *p)
5320 { // change piece into promotion partner if one shogi-promotes to the other
5321 int stride = gameInfo.variant == VariantChu ? 22 : 11;
5322 ChessSquare partner;
5323 partner = (*p/stride & 1 ? *p - stride : *p + stride);
5324 if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5332 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5333 static int toggleFlag;
5334 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5335 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5336 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5337 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5338 if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5339 if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5341 if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5342 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5343 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5344 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5345 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5346 if(!step) step = -1;
5347 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5348 !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5349 appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5350 (promoSweep == WhiteLion || promoSweep == BlackLion)));
5352 int victim = boards[currentMove][toY][toX];
5353 boards[currentMove][toY][toX] = promoSweep;
5354 DrawPosition(FALSE, boards[currentMove]);
5355 boards[currentMove][toY][toX] = victim;
5357 ChangeDragPiece(promoSweep);
5361 PromoScroll (int x, int y)
5365 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5366 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5367 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5368 if(!step) return FALSE;
5369 lastX = x; lastY = y;
5370 if((promoSweep < BlackPawn) == flipView) step = -step;
5371 if(step > 0) selectFlag = 1;
5372 if(!selectFlag) Sweep(step);
5377 NextPiece (int step)
5379 ChessSquare piece = boards[currentMove][toY][toX];
5382 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5383 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5384 if(!step) step = -1;
5385 } while(PieceToChar(pieceSweep) == '.');
5386 boards[currentMove][toY][toX] = pieceSweep;
5387 DrawPosition(FALSE, boards[currentMove]);
5388 boards[currentMove][toY][toX] = piece;
5390 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5392 AlphaRank (char *move, int n)
5394 // char *p = move, c; int x, y;
5396 if (appData.debugMode) {
5397 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5401 move[2]>='0' && move[2]<='9' &&
5402 move[3]>='a' && move[3]<='x' ) {
5404 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5405 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5407 if(move[0]>='0' && move[0]<='9' &&
5408 move[1]>='a' && move[1]<='x' &&
5409 move[2]>='0' && move[2]<='9' &&
5410 move[3]>='a' && move[3]<='x' ) {
5411 /* input move, Shogi -> normal */
5412 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5413 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5414 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5415 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5418 move[3]>='0' && move[3]<='9' &&
5419 move[2]>='a' && move[2]<='x' ) {
5421 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5422 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5425 move[0]>='a' && move[0]<='x' &&
5426 move[3]>='0' && move[3]<='9' &&
5427 move[2]>='a' && move[2]<='x' ) {
5428 /* output move, normal -> Shogi */
5429 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5430 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5431 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5432 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5433 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5435 if (appData.debugMode) {
5436 fprintf(debugFP, " out = '%s'\n", move);
5440 char yy_textstr[8000];
5442 /* Parser for moves from gnuchess, ICS, or user typein box */
5444 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5446 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5448 switch (*moveType) {
5449 case WhitePromotion:
5450 case BlackPromotion:
5451 case WhiteNonPromotion:
5452 case BlackNonPromotion:
5455 case WhiteCapturesEnPassant:
5456 case BlackCapturesEnPassant:
5457 case WhiteKingSideCastle:
5458 case WhiteQueenSideCastle:
5459 case BlackKingSideCastle:
5460 case BlackQueenSideCastle:
5461 case WhiteKingSideCastleWild:
5462 case WhiteQueenSideCastleWild:
5463 case BlackKingSideCastleWild:
5464 case BlackQueenSideCastleWild:
5465 /* Code added by Tord: */
5466 case WhiteHSideCastleFR:
5467 case WhiteASideCastleFR:
5468 case BlackHSideCastleFR:
5469 case BlackASideCastleFR:
5470 /* End of code added by Tord */
5471 case IllegalMove: /* bug or odd chess variant */
5472 *fromX = currentMoveString[0] - AAA;
5473 *fromY = currentMoveString[1] - ONE;
5474 *toX = currentMoveString[2] - AAA;
5475 *toY = currentMoveString[3] - ONE;
5476 *promoChar = currentMoveString[4];
5477 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5478 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5479 if (appData.debugMode) {
5480 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5482 *fromX = *fromY = *toX = *toY = 0;
5485 if (appData.testLegality) {
5486 return (*moveType != IllegalMove);
5488 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5489 // [HGM] lion: if this is a double move we are less critical
5490 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5495 *fromX = *moveType == WhiteDrop ?
5496 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5497 (int) CharToPiece(ToLower(currentMoveString[0]));
5499 *toX = currentMoveString[2] - AAA;
5500 *toY = currentMoveString[3] - ONE;
5501 *promoChar = NULLCHAR;
5505 case ImpossibleMove:
5515 if (appData.debugMode) {
5516 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5519 *fromX = *fromY = *toX = *toY = 0;
5520 *promoChar = NULLCHAR;
5525 Boolean pushed = FALSE;
5526 char *lastParseAttempt;
5529 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5530 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5531 int fromX, fromY, toX, toY; char promoChar;
5536 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5537 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5538 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5541 endPV = forwardMostMove;
5543 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5544 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5545 lastParseAttempt = pv;
5546 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5547 if(!valid && nr == 0 &&
5548 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5549 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5550 // Hande case where played move is different from leading PV move
5551 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5552 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5553 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5554 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5555 endPV += 2; // if position different, keep this
5556 moveList[endPV-1][0] = fromX + AAA;
5557 moveList[endPV-1][1] = fromY + ONE;
5558 moveList[endPV-1][2] = toX + AAA;
5559 moveList[endPV-1][3] = toY + ONE;
5560 parseList[endPV-1][0] = NULLCHAR;
5561 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5564 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5565 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5566 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5567 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5568 valid++; // allow comments in PV
5572 if(endPV+1 > framePtr) break; // no space, truncate
5575 CopyBoard(boards[endPV], boards[endPV-1]);
5576 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5577 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5578 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5579 CoordsToAlgebraic(boards[endPV - 1],
5580 PosFlags(endPV - 1),
5581 fromY, fromX, toY, toX, promoChar,
5582 parseList[endPV - 1]);
5584 if(atEnd == 2) return; // used hidden, for PV conversion
5585 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5586 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5587 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5588 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5589 DrawPosition(TRUE, boards[currentMove]);
5593 MultiPV (ChessProgramState *cps)
5594 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5596 for(i=0; i<cps->nrOptions; i++)
5597 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5602 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5605 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5607 int startPV, multi, lineStart, origIndex = index;
5608 char *p, buf2[MSG_SIZ];
5609 ChessProgramState *cps = (pane ? &second : &first);
5611 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5612 lastX = x; lastY = y;
5613 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5614 lineStart = startPV = index;
5615 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5616 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5618 do{ while(buf[index] && buf[index] != '\n') index++;
5619 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5621 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5622 int n = cps->option[multi].value;
5623 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5624 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5625 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5626 cps->option[multi].value = n;
5629 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5630 ExcludeClick(origIndex - lineStart);
5633 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5634 *start = startPV; *end = index-1;
5635 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5642 static char buf[10*MSG_SIZ];
5643 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5645 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5646 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5647 for(i = forwardMostMove; i<endPV; i++){
5648 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5649 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5652 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5653 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5654 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5660 LoadPV (int x, int y)
5661 { // called on right mouse click to load PV
5662 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5663 lastX = x; lastY = y;
5664 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5672 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5673 if(endPV < 0) return;
5674 if(appData.autoCopyPV) CopyFENToClipboard();
5676 if(extendGame && currentMove > forwardMostMove) {
5677 Boolean saveAnimate = appData.animate;
5679 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5680 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5681 } else storedGames--; // abandon shelved tail of original game
5684 forwardMostMove = currentMove;
5685 currentMove = oldFMM;
5686 appData.animate = FALSE;
5687 ToNrEvent(forwardMostMove);
5688 appData.animate = saveAnimate;
5690 currentMove = forwardMostMove;
5691 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5692 ClearPremoveHighlights();
5693 DrawPosition(TRUE, boards[currentMove]);
5697 MovePV (int x, int y, int h)
5698 { // step through PV based on mouse coordinates (called on mouse move)
5699 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5701 // we must somehow check if right button is still down (might be released off board!)
5702 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5703 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5704 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5706 lastX = x; lastY = y;
5708 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5709 if(endPV < 0) return;
5710 if(y < margin) step = 1; else
5711 if(y > h - margin) step = -1;
5712 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5713 currentMove += step;
5714 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5715 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5716 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5717 DrawPosition(FALSE, boards[currentMove]);
5721 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5722 // All positions will have equal probability, but the current method will not provide a unique
5723 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5729 int piecesLeft[(int)BlackPawn];
5730 int seed, nrOfShuffles;
5733 GetPositionNumber ()
5734 { // sets global variable seed
5737 seed = appData.defaultFrcPosition;
5738 if(seed < 0) { // randomize based on time for negative FRC position numbers
5739 for(i=0; i<50; i++) seed += random();
5740 seed = random() ^ random() >> 8 ^ random() << 8;
5741 if(seed<0) seed = -seed;
5746 put (Board board, int pieceType, int rank, int n, int shade)
5747 // put the piece on the (n-1)-th empty squares of the given shade
5751 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5752 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5753 board[rank][i] = (ChessSquare) pieceType;
5754 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5756 piecesLeft[pieceType]--;
5765 AddOnePiece (Board board, int pieceType, int rank, int shade)
5766 // calculate where the next piece goes, (any empty square), and put it there
5770 i = seed % squaresLeft[shade];
5771 nrOfShuffles *= squaresLeft[shade];
5772 seed /= squaresLeft[shade];
5773 put(board, pieceType, rank, i, shade);
5777 AddTwoPieces (Board board, int pieceType, int rank)
5778 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5780 int i, n=squaresLeft[ANY], j=n-1, k;
5782 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5783 i = seed % k; // pick one
5786 while(i >= j) i -= j--;
5787 j = n - 1 - j; i += j;
5788 put(board, pieceType, rank, j, ANY);
5789 put(board, pieceType, rank, i, ANY);
5793 SetUpShuffle (Board board, int number)
5797 GetPositionNumber(); nrOfShuffles = 1;
5799 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5800 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5801 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5803 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5805 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5806 p = (int) board[0][i];
5807 if(p < (int) BlackPawn) piecesLeft[p] ++;
5808 board[0][i] = EmptySquare;
5811 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5812 // shuffles restricted to allow normal castling put KRR first
5813 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5814 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5815 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5816 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5817 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5818 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5819 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5820 put(board, WhiteRook, 0, 0, ANY);
5821 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5824 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5825 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5826 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5827 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5828 while(piecesLeft[p] >= 2) {
5829 AddOnePiece(board, p, 0, LITE);
5830 AddOnePiece(board, p, 0, DARK);
5832 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5835 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5836 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5837 // but we leave King and Rooks for last, to possibly obey FRC restriction
5838 if(p == (int)WhiteRook) continue;
5839 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5840 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5843 // now everything is placed, except perhaps King (Unicorn) and Rooks
5845 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5846 // Last King gets castling rights
5847 while(piecesLeft[(int)WhiteUnicorn]) {
5848 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5849 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5852 while(piecesLeft[(int)WhiteKing]) {
5853 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5854 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5859 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5860 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5863 // Only Rooks can be left; simply place them all
5864 while(piecesLeft[(int)WhiteRook]) {
5865 i = put(board, WhiteRook, 0, 0, ANY);
5866 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5869 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5871 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5874 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5875 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5878 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5882 SetCharTable (char *table, const char * map)
5883 /* [HGM] moved here from winboard.c because of its general usefulness */
5884 /* Basically a safe strcpy that uses the last character as King */
5886 int result = FALSE; int NrPieces;
5888 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5889 && NrPieces >= 12 && !(NrPieces&1)) {
5890 int i; /* [HGM] Accept even length from 12 to 34 */
5892 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5893 for( i=0; i<NrPieces/2-1; i++ ) {
5895 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5897 table[(int) WhiteKing] = map[NrPieces/2-1];
5898 table[(int) BlackKing] = map[NrPieces-1];
5907 Prelude (Board board)
5908 { // [HGM] superchess: random selection of exo-pieces
5909 int i, j, k; ChessSquare p;
5910 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5912 GetPositionNumber(); // use FRC position number
5914 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5915 SetCharTable(pieceToChar, appData.pieceToCharTable);
5916 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5917 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5920 j = seed%4; seed /= 4;
5921 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5922 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5923 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5924 j = seed%3 + (seed%3 >= j); seed /= 3;
5925 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5926 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5927 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5928 j = seed%3; seed /= 3;
5929 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5930 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5931 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5932 j = seed%2 + (seed%2 >= j); seed /= 2;
5933 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5934 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5935 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5936 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5937 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5938 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5939 put(board, exoPieces[0], 0, 0, ANY);
5940 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5944 InitPosition (int redraw)
5946 ChessSquare (* pieces)[BOARD_FILES];
5947 int i, j, pawnRow=1, pieceRows=1, overrule,
5948 oldx = gameInfo.boardWidth,
5949 oldy = gameInfo.boardHeight,
5950 oldh = gameInfo.holdingsWidth;
5953 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5955 /* [AS] Initialize pv info list [HGM] and game status */
5957 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5958 pvInfoList[i].depth = 0;
5959 boards[i][EP_STATUS] = EP_NONE;
5960 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5963 initialRulePlies = 0; /* 50-move counter start */
5965 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5966 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5970 /* [HGM] logic here is completely changed. In stead of full positions */
5971 /* the initialized data only consist of the two backranks. The switch */
5972 /* selects which one we will use, which is than copied to the Board */
5973 /* initialPosition, which for the rest is initialized by Pawns and */
5974 /* empty squares. This initial position is then copied to boards[0], */
5975 /* possibly after shuffling, so that it remains available. */
5977 gameInfo.holdingsWidth = 0; /* default board sizes */
5978 gameInfo.boardWidth = 8;
5979 gameInfo.boardHeight = 8;
5980 gameInfo.holdingsSize = 0;
5981 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5982 for(i=0; i<BOARD_FILES-2; i++)
5983 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5984 initialPosition[EP_STATUS] = EP_NONE;
5985 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5986 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5987 SetCharTable(pieceNickName, appData.pieceNickNames);
5988 else SetCharTable(pieceNickName, "............");
5991 switch (gameInfo.variant) {
5992 case VariantFischeRandom:
5993 shuffleOpenings = TRUE;
5996 case VariantShatranj:
5997 pieces = ShatranjArray;
5998 nrCastlingRights = 0;
5999 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6002 pieces = makrukArray;
6003 nrCastlingRights = 0;
6004 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6007 pieces = aseanArray;
6008 nrCastlingRights = 0;
6009 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6011 case VariantTwoKings:
6012 pieces = twoKingsArray;
6015 pieces = GrandArray;
6016 nrCastlingRights = 0;
6017 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6018 gameInfo.boardWidth = 10;
6019 gameInfo.boardHeight = 10;
6020 gameInfo.holdingsSize = 7;
6022 case VariantCapaRandom:
6023 shuffleOpenings = TRUE;
6024 case VariantCapablanca:
6025 pieces = CapablancaArray;
6026 gameInfo.boardWidth = 10;
6027 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6030 pieces = GothicArray;
6031 gameInfo.boardWidth = 10;
6032 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6035 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6036 gameInfo.holdingsSize = 7;
6037 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6040 pieces = JanusArray;
6041 gameInfo.boardWidth = 10;
6042 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6043 nrCastlingRights = 6;
6044 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6045 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6046 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6047 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6048 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6049 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6052 pieces = FalconArray;
6053 gameInfo.boardWidth = 10;
6054 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6056 case VariantXiangqi:
6057 pieces = XiangqiArray;
6058 gameInfo.boardWidth = 9;
6059 gameInfo.boardHeight = 10;
6060 nrCastlingRights = 0;
6061 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6064 pieces = ShogiArray;
6065 gameInfo.boardWidth = 9;
6066 gameInfo.boardHeight = 9;
6067 gameInfo.holdingsSize = 7;
6068 nrCastlingRights = 0;
6069 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6072 pieces = ChuArray; pieceRows = 3;
6073 gameInfo.boardWidth = 12;
6074 gameInfo.boardHeight = 12;
6075 nrCastlingRights = 0;
6076 SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6077 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6079 case VariantCourier:
6080 pieces = CourierArray;
6081 gameInfo.boardWidth = 12;
6082 nrCastlingRights = 0;
6083 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6085 case VariantKnightmate:
6086 pieces = KnightmateArray;
6087 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6089 case VariantSpartan:
6090 pieces = SpartanArray;
6091 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6095 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6097 case VariantChuChess:
6098 pieces = ChuChessArray;
6099 gameInfo.boardWidth = 10;
6100 gameInfo.boardHeight = 10;
6101 SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6104 pieces = fairyArray;
6105 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6108 pieces = GreatArray;
6109 gameInfo.boardWidth = 10;
6110 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6111 gameInfo.holdingsSize = 8;
6115 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6116 gameInfo.holdingsSize = 8;
6117 startedFromSetupPosition = TRUE;
6119 case VariantCrazyhouse:
6120 case VariantBughouse:
6122 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6123 gameInfo.holdingsSize = 5;
6125 case VariantWildCastle:
6127 /* !!?shuffle with kings guaranteed to be on d or e file */
6128 shuffleOpenings = 1;
6130 case VariantNoCastle:
6132 nrCastlingRights = 0;
6133 /* !!?unconstrained back-rank shuffle */
6134 shuffleOpenings = 1;
6139 if(appData.NrFiles >= 0) {
6140 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6141 gameInfo.boardWidth = appData.NrFiles;
6143 if(appData.NrRanks >= 0) {
6144 gameInfo.boardHeight = appData.NrRanks;
6146 if(appData.holdingsSize >= 0) {
6147 i = appData.holdingsSize;
6148 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6149 gameInfo.holdingsSize = i;
6151 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6152 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6153 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6155 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6156 if(pawnRow < 1) pawnRow = 1;
6157 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6158 gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6159 if(gameInfo.variant == VariantChu) pawnRow = 3;
6161 /* User pieceToChar list overrules defaults */
6162 if(appData.pieceToCharTable != NULL)
6163 SetCharTable(pieceToChar, appData.pieceToCharTable);
6165 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6167 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6168 s = (ChessSquare) 0; /* account holding counts in guard band */
6169 for( i=0; i<BOARD_HEIGHT; i++ )
6170 initialPosition[i][j] = s;
6172 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6173 initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6174 initialPosition[pawnRow][j] = WhitePawn;
6175 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6176 if(gameInfo.variant == VariantXiangqi) {
6178 initialPosition[pawnRow][j] =
6179 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6180 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6181 initialPosition[2][j] = WhiteCannon;
6182 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6186 if(gameInfo.variant == VariantChu) {
6187 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6188 initialPosition[pawnRow+1][j] = WhiteCobra,
6189 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6190 for(i=1; i<pieceRows; i++) {
6191 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6192 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6195 if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6196 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6197 initialPosition[0][j] = WhiteRook;
6198 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6201 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth];
6203 if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6204 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6207 initialPosition[1][j] = WhiteBishop;
6208 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6210 initialPosition[1][j] = WhiteRook;
6211 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6214 if( nrCastlingRights == -1) {
6215 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6216 /* This sets default castling rights from none to normal corners */
6217 /* Variants with other castling rights must set them themselves above */
6218 nrCastlingRights = 6;
6220 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6221 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6222 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6223 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6224 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6225 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6228 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6229 if(gameInfo.variant == VariantGreat) { // promotion commoners
6230 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6231 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6232 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6233 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6235 if( gameInfo.variant == VariantSChess ) {
6236 initialPosition[1][0] = BlackMarshall;
6237 initialPosition[2][0] = BlackAngel;
6238 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6239 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6240 initialPosition[1][1] = initialPosition[2][1] =
6241 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6243 if (appData.debugMode) {
6244 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6246 if(shuffleOpenings) {
6247 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6248 startedFromSetupPosition = TRUE;
6250 if(startedFromPositionFile) {
6251 /* [HGM] loadPos: use PositionFile for every new game */
6252 CopyBoard(initialPosition, filePosition);
6253 for(i=0; i<nrCastlingRights; i++)
6254 initialRights[i] = filePosition[CASTLING][i];
6255 startedFromSetupPosition = TRUE;
6258 CopyBoard(boards[0], initialPosition);
6260 if(oldx != gameInfo.boardWidth ||
6261 oldy != gameInfo.boardHeight ||
6262 oldv != gameInfo.variant ||
6263 oldh != gameInfo.holdingsWidth
6265 InitDrawingSizes(-2 ,0);
6267 oldv = gameInfo.variant;
6269 DrawPosition(TRUE, boards[currentMove]);
6273 SendBoard (ChessProgramState *cps, int moveNum)
6275 char message[MSG_SIZ];
6277 if (cps->useSetboard) {
6278 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6279 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6280 SendToProgram(message, cps);
6285 int i, j, left=0, right=BOARD_WIDTH;
6286 /* Kludge to set black to move, avoiding the troublesome and now
6287 * deprecated "black" command.
6289 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6290 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6292 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6294 SendToProgram("edit\n", cps);
6295 SendToProgram("#\n", cps);
6296 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6297 bp = &boards[moveNum][i][left];
6298 for (j = left; j < right; j++, bp++) {
6299 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6300 if ((int) *bp < (int) BlackPawn) {
6301 if(j == BOARD_RGHT+1)
6302 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6303 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6304 if(message[0] == '+' || message[0] == '~') {
6305 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6306 PieceToChar((ChessSquare)(DEMOTED *bp)),
6309 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6310 message[1] = BOARD_RGHT - 1 - j + '1';
6311 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6313 SendToProgram(message, cps);
6318 SendToProgram("c\n", cps);
6319 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6320 bp = &boards[moveNum][i][left];
6321 for (j = left; j < right; j++, bp++) {
6322 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6323 if (((int) *bp != (int) EmptySquare)
6324 && ((int) *bp >= (int) BlackPawn)) {
6325 if(j == BOARD_LEFT-2)
6326 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6327 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6329 if(message[0] == '+' || message[0] == '~') {
6330 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6331 PieceToChar((ChessSquare)(DEMOTED *bp)),
6334 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6335 message[1] = BOARD_RGHT - 1 - j + '1';
6336 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6338 SendToProgram(message, cps);
6343 SendToProgram(".\n", cps);
6345 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6348 char exclusionHeader[MSG_SIZ];
6349 int exCnt, excludePtr;
6350 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6351 static Exclusion excluTab[200];
6352 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6358 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6359 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6365 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6366 excludePtr = 24; exCnt = 0;
6371 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6372 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6373 char buf[2*MOVE_LEN], *p;
6374 Exclusion *e = excluTab;
6376 for(i=0; i<exCnt; i++)
6377 if(e[i].ff == fromX && e[i].fr == fromY &&
6378 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6379 if(i == exCnt) { // was not in exclude list; add it
6380 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6381 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6382 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6385 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6386 excludePtr++; e[i].mark = excludePtr++;
6387 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6390 exclusionHeader[e[i].mark] = state;
6394 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6395 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6399 if((signed char)promoChar == -1) { // kludge to indicate best move
6400 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6401 return 1; // if unparsable, abort
6403 // update exclusion map (resolving toggle by consulting existing state)
6404 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6406 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6407 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6408 excludeMap[k] |= 1<<j;
6409 else excludeMap[k] &= ~(1<<j);
6411 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6413 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6414 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6416 return (state == '+');
6420 ExcludeClick (int index)
6423 Exclusion *e = excluTab;
6424 if(index < 25) { // none, best or tail clicked
6425 if(index < 13) { // none: include all
6426 WriteMap(0); // clear map
6427 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6428 SendToBoth("include all\n"); // and inform engine
6429 } else if(index > 18) { // tail
6430 if(exclusionHeader[19] == '-') { // tail was excluded
6431 SendToBoth("include all\n");
6432 WriteMap(0); // clear map completely
6433 // now re-exclude selected moves
6434 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6435 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6436 } else { // tail was included or in mixed state
6437 SendToBoth("exclude all\n");
6438 WriteMap(0xFF); // fill map completely
6439 // now re-include selected moves
6440 j = 0; // count them
6441 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6442 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6443 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6446 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6449 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6450 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6451 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6458 DefaultPromoChoice (int white)
6461 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6462 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6463 result = WhiteFerz; // no choice
6464 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6465 result= WhiteKing; // in Suicide Q is the last thing we want
6466 else if(gameInfo.variant == VariantSpartan)
6467 result = white ? WhiteQueen : WhiteAngel;
6468 else result = WhiteQueen;
6469 if(!white) result = WHITE_TO_BLACK result;
6473 static int autoQueen; // [HGM] oneclick
6476 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6478 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6479 /* [HGM] add Shogi promotions */
6480 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6481 ChessSquare piece, partner;
6485 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6486 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6488 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6489 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6492 piece = boards[currentMove][fromY][fromX];
6493 if(gameInfo.variant == VariantChu) {
6494 int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6495 promotionZoneSize = BOARD_HEIGHT/3;
6496 highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6497 } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6498 promotionZoneSize = BOARD_HEIGHT/3;
6499 highestPromotingPiece = (int)WhiteAlfil;
6500 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6501 promotionZoneSize = 3;
6504 // Treat Lance as Pawn when it is not representing Amazon
6505 if(gameInfo.variant != VariantSuper) {
6506 if(piece == WhiteLance) piece = WhitePawn; else
6507 if(piece == BlackLance) piece = BlackPawn;
6510 // next weed out all moves that do not touch the promotion zone at all
6511 if((int)piece >= BlackPawn) {
6512 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6514 if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6515 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6517 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6518 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6519 if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6523 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6525 // weed out mandatory Shogi promotions
6526 if(gameInfo.variant == VariantShogi) {
6527 if(piece >= BlackPawn) {
6528 if(toY == 0 && piece == BlackPawn ||
6529 toY == 0 && piece == BlackQueen ||
6530 toY <= 1 && piece == BlackKnight) {
6535 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6536 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6537 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6544 // weed out obviously illegal Pawn moves
6545 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6546 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6547 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6548 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6549 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6550 // note we are not allowed to test for valid (non-)capture, due to premove
6553 // we either have a choice what to promote to, or (in Shogi) whether to promote
6554 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6555 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6556 ChessSquare p=BlackFerz; // no choice
6557 while(p < EmptySquare) { //but make sure we use piece that exists
6558 *promoChoice = PieceToChar(p++);
6559 if(*promoChoice != '.') break;
6563 // no sense asking what we must promote to if it is going to explode...
6564 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6565 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6568 // give caller the default choice even if we will not make it
6569 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6570 partner = piece; // pieces can promote if the pieceToCharTable says so
6571 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6572 else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6573 if( sweepSelect && gameInfo.variant != VariantGreat
6574 && gameInfo.variant != VariantGrand
6575 && gameInfo.variant != VariantSuper) return FALSE;
6576 if(autoQueen) return FALSE; // predetermined
6578 // suppress promotion popup on illegal moves that are not premoves
6579 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6580 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6581 if(appData.testLegality && !premove) {
6582 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6583 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6584 if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6585 if(moveType != WhitePromotion && moveType != BlackPromotion)
6593 InPalace (int row, int column)
6594 { /* [HGM] for Xiangqi */
6595 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6596 column < (BOARD_WIDTH + 4)/2 &&
6597 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6602 PieceForSquare (int x, int y)
6604 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6607 return boards[currentMove][y][x];
6611 OKToStartUserMove (int x, int y)
6613 ChessSquare from_piece;
6616 if (matchMode) return FALSE;
6617 if (gameMode == EditPosition) return TRUE;
6619 if (x >= 0 && y >= 0)
6620 from_piece = boards[currentMove][y][x];
6622 from_piece = EmptySquare;
6624 if (from_piece == EmptySquare) return FALSE;
6626 white_piece = (int)from_piece >= (int)WhitePawn &&
6627 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6631 case TwoMachinesPlay:
6639 case MachinePlaysWhite:
6640 case IcsPlayingBlack:
6641 if (appData.zippyPlay) return FALSE;
6643 DisplayMoveError(_("You are playing Black"));
6648 case MachinePlaysBlack:
6649 case IcsPlayingWhite:
6650 if (appData.zippyPlay) return FALSE;
6652 DisplayMoveError(_("You are playing White"));
6657 case PlayFromGameFile:
6658 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6660 if (!white_piece && WhiteOnMove(currentMove)) {
6661 DisplayMoveError(_("It is White's turn"));
6664 if (white_piece && !WhiteOnMove(currentMove)) {
6665 DisplayMoveError(_("It is Black's turn"));
6668 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6669 /* Editing correspondence game history */
6670 /* Could disallow this or prompt for confirmation */
6675 case BeginningOfGame:
6676 if (appData.icsActive) return FALSE;
6677 if (!appData.noChessProgram) {
6679 DisplayMoveError(_("You are playing White"));
6686 if (!white_piece && WhiteOnMove(currentMove)) {
6687 DisplayMoveError(_("It is White's turn"));
6690 if (white_piece && !WhiteOnMove(currentMove)) {
6691 DisplayMoveError(_("It is Black's turn"));
6700 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6701 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6702 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6703 && gameMode != AnalyzeFile && gameMode != Training) {
6704 DisplayMoveError(_("Displayed position is not current"));
6711 OnlyMove (int *x, int *y, Boolean captures)
6713 DisambiguateClosure cl;
6714 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6716 case MachinePlaysBlack:
6717 case IcsPlayingWhite:
6718 case BeginningOfGame:
6719 if(!WhiteOnMove(currentMove)) return FALSE;
6721 case MachinePlaysWhite:
6722 case IcsPlayingBlack:
6723 if(WhiteOnMove(currentMove)) return FALSE;
6730 cl.pieceIn = EmptySquare;
6735 cl.promoCharIn = NULLCHAR;
6736 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6737 if( cl.kind == NormalMove ||
6738 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6739 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6740 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6747 if(cl.kind != ImpossibleMove) return FALSE;
6748 cl.pieceIn = EmptySquare;
6753 cl.promoCharIn = NULLCHAR;
6754 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6755 if( cl.kind == NormalMove ||
6756 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6757 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6758 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6763 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6769 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6770 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6771 int lastLoadGameUseList = FALSE;
6772 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6773 ChessMove lastLoadGameStart = EndOfFile;
6777 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6781 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6783 /* Check if the user is playing in turn. This is complicated because we
6784 let the user "pick up" a piece before it is his turn. So the piece he
6785 tried to pick up may have been captured by the time he puts it down!
6786 Therefore we use the color the user is supposed to be playing in this
6787 test, not the color of the piece that is currently on the starting
6788 square---except in EditGame mode, where the user is playing both
6789 sides; fortunately there the capture race can't happen. (It can
6790 now happen in IcsExamining mode, but that's just too bad. The user
6791 will get a somewhat confusing message in that case.)
6796 case TwoMachinesPlay:
6800 /* We switched into a game mode where moves are not accepted,
6801 perhaps while the mouse button was down. */
6804 case MachinePlaysWhite:
6805 /* User is moving for Black */
6806 if (WhiteOnMove(currentMove)) {
6807 DisplayMoveError(_("It is White's turn"));
6812 case MachinePlaysBlack:
6813 /* User is moving for White */
6814 if (!WhiteOnMove(currentMove)) {
6815 DisplayMoveError(_("It is Black's turn"));
6820 case PlayFromGameFile:
6821 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6824 case BeginningOfGame:
6827 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6828 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6829 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6830 /* User is moving for Black */
6831 if (WhiteOnMove(currentMove)) {
6832 DisplayMoveError(_("It is White's turn"));
6836 /* User is moving for White */
6837 if (!WhiteOnMove(currentMove)) {
6838 DisplayMoveError(_("It is Black's turn"));
6844 case IcsPlayingBlack:
6845 /* User is moving for Black */
6846 if (WhiteOnMove(currentMove)) {
6847 if (!appData.premove) {
6848 DisplayMoveError(_("It is White'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);
6865 case IcsPlayingWhite:
6866 /* User is moving for White */
6867 if (!WhiteOnMove(currentMove)) {
6868 if (!appData.premove) {
6869 DisplayMoveError(_("It is Black's turn"));
6870 } else if (toX >= 0 && toY >= 0) {
6873 premoveFromX = fromX;
6874 premoveFromY = fromY;
6875 premovePromoChar = promoChar;
6877 if (appData.debugMode)
6878 fprintf(debugFP, "Got premove: fromX %d,"
6879 "fromY %d, toX %d, toY %d\n",
6880 fromX, fromY, toX, toY);
6890 /* EditPosition, empty square, or different color piece;
6891 click-click move is possible */
6892 if (toX == -2 || toY == -2) {
6893 boards[0][fromY][fromX] = EmptySquare;
6894 DrawPosition(FALSE, boards[currentMove]);
6896 } else if (toX >= 0 && toY >= 0) {
6897 boards[0][toY][toX] = boards[0][fromY][fromX];
6898 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6899 if(boards[0][fromY][0] != EmptySquare) {
6900 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6901 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6904 if(fromX == BOARD_RGHT+1) {
6905 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6906 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6907 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6910 boards[0][fromY][fromX] = gatingPiece;
6911 DrawPosition(FALSE, boards[currentMove]);
6917 if(toX < 0 || toY < 0) return;
6918 pup = boards[currentMove][toY][toX];
6920 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6921 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6922 if( pup != EmptySquare ) return;
6923 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6924 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6925 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6926 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6927 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6928 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6929 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6933 /* [HGM] always test for legality, to get promotion info */
6934 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6935 fromY, fromX, toY, toX, promoChar);
6937 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6939 /* [HGM] but possibly ignore an IllegalMove result */
6940 if (appData.testLegality) {
6941 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6942 DisplayMoveError(_("Illegal move"));
6947 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6948 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6949 ClearPremoveHighlights(); // was included
6950 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6954 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6957 /* Common tail of UserMoveEvent and DropMenuEvent */
6959 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6963 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6964 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6965 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6966 if(WhiteOnMove(currentMove)) {
6967 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6969 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6973 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6974 move type in caller when we know the move is a legal promotion */
6975 if(moveType == NormalMove && promoChar)
6976 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6978 /* [HGM] <popupFix> The following if has been moved here from
6979 UserMoveEvent(). Because it seemed to belong here (why not allow
6980 piece drops in training games?), and because it can only be
6981 performed after it is known to what we promote. */
6982 if (gameMode == Training) {
6983 /* compare the move played on the board to the next move in the
6984 * game. If they match, display the move and the opponent's response.
6985 * If they don't match, display an error message.
6989 CopyBoard(testBoard, boards[currentMove]);
6990 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6992 if (CompareBoards(testBoard, boards[currentMove+1])) {
6993 ForwardInner(currentMove+1);
6995 /* Autoplay the opponent's response.
6996 * if appData.animate was TRUE when Training mode was entered,
6997 * the response will be animated.
6999 saveAnimate = appData.animate;
7000 appData.animate = animateTraining;
7001 ForwardInner(currentMove+1);
7002 appData.animate = saveAnimate;
7004 /* check for the end of the game */
7005 if (currentMove >= forwardMostMove) {
7006 gameMode = PlayFromGameFile;
7008 SetTrainingModeOff();
7009 DisplayInformation(_("End of game"));
7012 DisplayError(_("Incorrect move"), 0);
7017 /* Ok, now we know that the move is good, so we can kill
7018 the previous line in Analysis Mode */
7019 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7020 && currentMove < forwardMostMove) {
7021 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7022 else forwardMostMove = currentMove;
7027 /* If we need the chess program but it's dead, restart it */
7028 ResurrectChessProgram();
7030 /* A user move restarts a paused game*/
7034 thinkOutput[0] = NULLCHAR;
7036 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7038 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7039 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7043 if (gameMode == BeginningOfGame) {
7044 if (appData.noChessProgram) {
7045 gameMode = EditGame;
7049 gameMode = MachinePlaysBlack;
7052 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7054 if (first.sendName) {
7055 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7056 SendToProgram(buf, &first);
7063 /* Relay move to ICS or chess engine */
7064 if (appData.icsActive) {
7065 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7066 gameMode == IcsExamining) {
7067 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7068 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7070 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7072 // also send plain move, in case ICS does not understand atomic claims
7073 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7077 if (first.sendTime && (gameMode == BeginningOfGame ||
7078 gameMode == MachinePlaysWhite ||
7079 gameMode == MachinePlaysBlack)) {
7080 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7082 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7083 // [HGM] book: if program might be playing, let it use book
7084 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7085 first.maybeThinking = TRUE;
7086 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7087 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7088 SendBoard(&first, currentMove+1);
7089 if(second.analyzing) {
7090 if(!second.useSetboard) SendToProgram("undo\n", &second);
7091 SendBoard(&second, currentMove+1);
7094 SendMoveToProgram(forwardMostMove-1, &first);
7095 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7097 if (currentMove == cmailOldMove + 1) {
7098 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7102 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7106 if(appData.testLegality)
7107 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7113 if (WhiteOnMove(currentMove)) {
7114 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7116 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7120 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7125 case MachinePlaysBlack:
7126 case MachinePlaysWhite:
7127 /* disable certain menu options while machine is thinking */
7128 SetMachineThinkingEnables();
7135 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7136 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7138 if(bookHit) { // [HGM] book: simulate book reply
7139 static char bookMove[MSG_SIZ]; // a bit generous?
7141 programStats.nodes = programStats.depth = programStats.time =
7142 programStats.score = programStats.got_only_move = 0;
7143 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7145 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7146 strcat(bookMove, bookHit);
7147 HandleMachineMove(bookMove, &first);
7153 MarkByFEN(char *fen)
7156 if(!appData.markers || !appData.highlightDragging) return;
7157 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7158 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7162 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7163 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7164 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7165 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7166 if(*fen == 'T') marker[r][f++] = 0; else
7167 if(*fen == 'Y') marker[r][f++] = 1; else
7168 if(*fen == 'G') marker[r][f++] = 3; else
7169 if(*fen == 'B') marker[r][f++] = 4; else
7170 if(*fen == 'C') marker[r][f++] = 5; else
7171 if(*fen == 'M') marker[r][f++] = 6; else
7172 if(*fen == 'W') marker[r][f++] = 7; else
7173 if(*fen == 'D') marker[r][f++] = 8; else
7174 if(*fen == 'R') marker[r][f++] = 2; else {
7175 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7178 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7182 DrawPosition(TRUE, NULL);
7185 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7188 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7190 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7191 Markers *m = (Markers *) closure;
7192 if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7193 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7194 || kind == WhiteCapturesEnPassant
7195 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7196 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7199 static int hoverSavedValid;
7202 MarkTargetSquares (int clear)
7205 if(clear) { // no reason to ever suppress clearing
7206 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7207 hoverSavedValid = 0;
7208 if(!sum) return; // nothing was cleared,no redraw needed
7211 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7212 !appData.testLegality || gameMode == EditPosition) return;
7213 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7214 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7215 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7217 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7220 DrawPosition(FALSE, NULL);
7224 Explode (Board board, int fromX, int fromY, int toX, int toY)
7226 if(gameInfo.variant == VariantAtomic &&
7227 (board[toY][toX] != EmptySquare || // capture?
7228 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7229 board[fromY][fromX] == BlackPawn )
7231 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7237 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7240 CanPromote (ChessSquare piece, int y)
7242 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7243 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7244 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7245 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7246 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7247 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7248 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7249 return (piece == BlackPawn && y <= zone ||
7250 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7251 piece == BlackLance && y == 1 ||
7252 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7256 HoverEvent (int xPix, int yPix, int x, int y)
7258 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7260 if(!first.highlight) return;
7261 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7262 if(x == oldX && y == oldY) return; // only do something if we enter new square
7263 oldFromX = fromX; oldFromY = fromY;
7264 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7265 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7266 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7267 hoverSavedValid = 1;
7268 } else if(oldX != x || oldY != y) {
7269 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7270 if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7271 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7272 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7273 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7275 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7276 SendToProgram(buf, &first);
7279 // SetHighlights(fromX, fromY, x, y);
7283 void ReportClick(char *action, int x, int y)
7285 char buf[MSG_SIZ]; // Inform engine of what user does
7287 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7288 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7289 if(!first.highlight || gameMode == EditPosition) return;
7290 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7291 SendToProgram(buf, &first);
7295 LeftClick (ClickType clickType, int xPix, int yPix)
7298 Boolean saveAnimate;
7299 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7300 char promoChoice = NULLCHAR;
7302 static TimeMark lastClickTime, prevClickTime;
7304 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7306 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7308 if (clickType == Press) ErrorPopDown();
7309 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7311 x = EventToSquare(xPix, BOARD_WIDTH);
7312 y = EventToSquare(yPix, BOARD_HEIGHT);
7313 if (!flipView && y >= 0) {
7314 y = BOARD_HEIGHT - 1 - y;
7316 if (flipView && x >= 0) {
7317 x = BOARD_WIDTH - 1 - x;
7320 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7321 defaultPromoChoice = promoSweep;
7322 promoSweep = EmptySquare; // terminate sweep
7323 promoDefaultAltered = TRUE;
7324 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7327 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7328 if(clickType == Release) return; // ignore upclick of click-click destination
7329 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7330 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7331 if(gameInfo.holdingsWidth &&
7332 (WhiteOnMove(currentMove)
7333 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7334 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7335 // click in right holdings, for determining promotion piece
7336 ChessSquare p = boards[currentMove][y][x];
7337 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7338 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7339 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7340 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7345 DrawPosition(FALSE, boards[currentMove]);
7349 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7350 if(clickType == Press
7351 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7352 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7353 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7356 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7357 // could be static click on premove from-square: abort premove
7359 ClearPremoveHighlights();
7362 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7363 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7365 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7366 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7367 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7368 defaultPromoChoice = DefaultPromoChoice(side);
7371 autoQueen = appData.alwaysPromoteToQueen;
7375 gatingPiece = EmptySquare;
7376 if (clickType != Press) {
7377 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7378 DragPieceEnd(xPix, yPix); dragging = 0;
7379 DrawPosition(FALSE, NULL);
7383 doubleClick = FALSE;
7384 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7385 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7387 fromX = x; fromY = y; toX = toY = killX = killY = -1;
7388 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7389 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7390 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7392 if (OKToStartUserMove(fromX, fromY)) {
7394 ReportClick("lift", x, y);
7395 MarkTargetSquares(0);
7396 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7397 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7398 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7399 promoSweep = defaultPromoChoice;
7400 selectFlag = 0; lastX = xPix; lastY = yPix;
7401 Sweep(0); // Pawn that is going to promote: preview promotion piece
7402 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7404 if (appData.highlightDragging) {
7405 SetHighlights(fromX, fromY, -1, -1);
7409 } else fromX = fromY = -1;
7415 if (clickType == Press && gameMode != EditPosition) {
7420 // ignore off-board to clicks
7421 if(y < 0 || x < 0) return;
7423 /* Check if clicking again on the same color piece */
7424 fromP = boards[currentMove][fromY][fromX];
7425 toP = boards[currentMove][y][x];
7426 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7427 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7428 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7429 WhitePawn <= toP && toP <= WhiteKing &&
7430 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7431 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7432 (BlackPawn <= fromP && fromP <= BlackKing &&
7433 BlackPawn <= toP && toP <= BlackKing &&
7434 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7435 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7436 /* Clicked again on same color piece -- changed his mind */
7437 second = (x == fromX && y == fromY);
7439 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7440 second = FALSE; // first double-click rather than scond click
7441 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7443 promoDefaultAltered = FALSE;
7444 MarkTargetSquares(1);
7445 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7446 if (appData.highlightDragging) {
7447 SetHighlights(x, y, -1, -1);
7451 if (OKToStartUserMove(x, y)) {
7452 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7453 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7454 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7455 gatingPiece = boards[currentMove][fromY][fromX];
7456 else gatingPiece = doubleClick ? fromP : EmptySquare;
7458 fromY = y; dragging = 1;
7459 ReportClick("lift", x, y);
7460 MarkTargetSquares(0);
7461 DragPieceBegin(xPix, yPix, FALSE);
7462 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7463 promoSweep = defaultPromoChoice;
7464 selectFlag = 0; lastX = xPix; lastY = yPix;
7465 Sweep(0); // Pawn that is going to promote: preview promotion piece
7469 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7472 // ignore clicks on holdings
7473 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7476 if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7477 DragPieceEnd(xPix, yPix); dragging = 0;
7479 // a deferred attempt to click-click move an empty square on top of a piece
7480 boards[currentMove][y][x] = EmptySquare;
7482 DrawPosition(FALSE, boards[currentMove]);
7483 fromX = fromY = -1; clearFlag = 0;
7486 if (appData.animateDragging) {
7487 /* Undo animation damage if any */
7488 DrawPosition(FALSE, NULL);
7490 if (second || sweepSelecting) {
7491 /* Second up/down in same square; just abort move */
7492 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7493 second = sweepSelecting = 0;
7495 gatingPiece = EmptySquare;
7496 MarkTargetSquares(1);
7499 ClearPremoveHighlights();
7501 /* First upclick in same square; start click-click mode */
7502 SetHighlights(x, y, -1, -1);
7509 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7510 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7511 DisplayMessage(_("only marked squares are legal"),"");
7512 DrawPosition(TRUE, NULL);
7513 return; // ignore to-click
7516 /* we now have a different from- and (possibly off-board) to-square */
7517 /* Completed move */
7518 if(!sweepSelecting) {
7523 piece = boards[currentMove][fromY][fromX];
7525 saveAnimate = appData.animate;
7526 if (clickType == Press) {
7527 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7528 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7529 // must be Edit Position mode with empty-square selected
7530 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7531 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7534 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7537 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7538 killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7540 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7541 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7542 if(appData.sweepSelect) {
7543 promoSweep = defaultPromoChoice;
7544 if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7545 selectFlag = 0; lastX = xPix; lastY = yPix;
7546 Sweep(0); // Pawn that is going to promote: preview promotion piece
7548 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7549 MarkTargetSquares(1);
7551 return; // promo popup appears on up-click
7553 /* Finish clickclick move */
7554 if (appData.animate || appData.highlightLastMove) {
7555 SetHighlights(fromX, fromY, toX, toY);
7559 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7560 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7561 if (appData.animate || appData.highlightLastMove) {
7562 SetHighlights(fromX, fromY, toX, toY);
7568 // [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
7569 /* Finish drag move */
7570 if (appData.highlightLastMove) {
7571 SetHighlights(fromX, fromY, toX, toY);
7576 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7577 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7578 dragging *= 2; // flag button-less dragging if we are dragging
7579 MarkTargetSquares(1);
7580 if(x == killX && y == killY) killX = killY = -1; else {
7581 killX = x; killY = y; //remeber this square as intermediate
7582 ReportClick("put", x, y); // and inform engine
7583 ReportClick("lift", x, y);
7584 MarkTargetSquares(0);
7588 DragPieceEnd(xPix, yPix); dragging = 0;
7589 /* Don't animate move and drag both */
7590 appData.animate = FALSE;
7593 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7594 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7595 ChessSquare piece = boards[currentMove][fromY][fromX];
7596 if(gameMode == EditPosition && piece != EmptySquare &&
7597 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7600 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7601 n = PieceToNumber(piece - (int)BlackPawn);
7602 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7603 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7604 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7606 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7607 n = PieceToNumber(piece);
7608 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7609 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7610 boards[currentMove][n][BOARD_WIDTH-2]++;
7612 boards[currentMove][fromY][fromX] = EmptySquare;
7616 MarkTargetSquares(1);
7617 DrawPosition(TRUE, boards[currentMove]);
7621 // off-board moves should not be highlighted
7622 if(x < 0 || y < 0) ClearHighlights();
7623 else ReportClick("put", x, y);
7625 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7627 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7628 SetHighlights(fromX, fromY, toX, toY);
7629 MarkTargetSquares(1);
7630 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7631 // [HGM] super: promotion to captured piece selected from holdings
7632 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7633 promotionChoice = TRUE;
7634 // kludge follows to temporarily execute move on display, without promoting yet
7635 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7636 boards[currentMove][toY][toX] = p;
7637 DrawPosition(FALSE, boards[currentMove]);
7638 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7639 boards[currentMove][toY][toX] = q;
7640 DisplayMessage("Click in holdings to choose piece", "");
7643 PromotionPopUp(promoChoice);
7645 int oldMove = currentMove;
7646 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7647 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7648 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7649 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7650 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7651 DrawPosition(TRUE, boards[currentMove]);
7652 MarkTargetSquares(1);
7655 appData.animate = saveAnimate;
7656 if (appData.animate || appData.animateDragging) {
7657 /* Undo animation damage if needed */
7658 DrawPosition(FALSE, NULL);
7663 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7664 { // front-end-free part taken out of PieceMenuPopup
7665 int whichMenu; int xSqr, ySqr;
7667 if(seekGraphUp) { // [HGM] seekgraph
7668 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7669 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7673 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7674 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7675 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7676 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7677 if(action == Press) {
7678 originalFlip = flipView;
7679 flipView = !flipView; // temporarily flip board to see game from partners perspective
7680 DrawPosition(TRUE, partnerBoard);
7681 DisplayMessage(partnerStatus, "");
7683 } else if(action == Release) {
7684 flipView = originalFlip;
7685 DrawPosition(TRUE, boards[currentMove]);
7691 xSqr = EventToSquare(x, BOARD_WIDTH);
7692 ySqr = EventToSquare(y, BOARD_HEIGHT);
7693 if (action == Release) {
7694 if(pieceSweep != EmptySquare) {
7695 EditPositionMenuEvent(pieceSweep, toX, toY);
7696 pieceSweep = EmptySquare;
7697 } else UnLoadPV(); // [HGM] pv
7699 if (action != Press) return -2; // return code to be ignored
7702 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7704 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7705 if (xSqr < 0 || ySqr < 0) return -1;
7706 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7707 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7708 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7709 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7713 if(!appData.icsEngineAnalyze) return -1;
7714 case IcsPlayingWhite:
7715 case IcsPlayingBlack:
7716 if(!appData.zippyPlay) goto noZip;
7719 case MachinePlaysWhite:
7720 case MachinePlaysBlack:
7721 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7722 if (!appData.dropMenu) {
7724 return 2; // flag front-end to grab mouse events
7726 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7727 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7730 if (xSqr < 0 || ySqr < 0) return -1;
7731 if (!appData.dropMenu || appData.testLegality &&
7732 gameInfo.variant != VariantBughouse &&
7733 gameInfo.variant != VariantCrazyhouse) return -1;
7734 whichMenu = 1; // drop menu
7740 if (((*fromX = xSqr) < 0) ||
7741 ((*fromY = ySqr) < 0)) {
7742 *fromX = *fromY = -1;
7746 *fromX = BOARD_WIDTH - 1 - *fromX;
7748 *fromY = BOARD_HEIGHT - 1 - *fromY;
7754 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7756 // char * hint = lastHint;
7757 FrontEndProgramStats stats;
7759 stats.which = cps == &first ? 0 : 1;
7760 stats.depth = cpstats->depth;
7761 stats.nodes = cpstats->nodes;
7762 stats.score = cpstats->score;
7763 stats.time = cpstats->time;
7764 stats.pv = cpstats->movelist;
7765 stats.hint = lastHint;
7766 stats.an_move_index = 0;
7767 stats.an_move_count = 0;
7769 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7770 stats.hint = cpstats->move_name;
7771 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7772 stats.an_move_count = cpstats->nr_moves;
7775 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
7777 SetProgramStats( &stats );
7781 ClearEngineOutputPane (int which)
7783 static FrontEndProgramStats dummyStats;
7784 dummyStats.which = which;
7785 dummyStats.pv = "#";
7786 SetProgramStats( &dummyStats );
7789 #define MAXPLAYERS 500
7792 TourneyStandings (int display)
7794 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7795 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7796 char result, *p, *names[MAXPLAYERS];
7798 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7799 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7800 names[0] = p = strdup(appData.participants);
7801 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7803 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7805 while(result = appData.results[nr]) {
7806 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7807 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7808 wScore = bScore = 0;
7810 case '+': wScore = 2; break;
7811 case '-': bScore = 2; break;
7812 case '=': wScore = bScore = 1; break;
7814 case '*': return strdup("busy"); // tourney not finished
7822 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7823 for(w=0; w<nPlayers; w++) {
7825 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7826 ranking[w] = b; points[w] = bScore; score[b] = -2;
7828 p = malloc(nPlayers*34+1);
7829 for(w=0; w<nPlayers && w<display; w++)
7830 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7836 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7837 { // count all piece types
7839 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7840 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7841 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7844 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7845 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7846 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7847 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7848 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7849 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7854 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7856 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7857 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7859 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7860 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7861 if(myPawns == 2 && nMine == 3) // KPP
7862 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7863 if(myPawns == 1 && nMine == 2) // KP
7864 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7865 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7866 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7867 if(myPawns) return FALSE;
7868 if(pCnt[WhiteRook+side])
7869 return pCnt[BlackRook-side] ||
7870 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7871 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7872 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7873 if(pCnt[WhiteCannon+side]) {
7874 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7875 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7877 if(pCnt[WhiteKnight+side])
7878 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7883 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7885 VariantClass v = gameInfo.variant;
7887 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7888 if(v == VariantShatranj) return TRUE; // always winnable through baring
7889 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7890 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7892 if(v == VariantXiangqi) {
7893 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7895 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7896 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7897 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7898 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7899 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7900 if(stale) // we have at least one last-rank P plus perhaps C
7901 return majors // KPKX
7902 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7904 return pCnt[WhiteFerz+side] // KCAK
7905 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7906 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7907 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7909 } else if(v == VariantKnightmate) {
7910 if(nMine == 1) return FALSE;
7911 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7912 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7913 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7915 if(nMine == 1) return FALSE; // bare King
7916 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
7917 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7918 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7919 // by now we have King + 1 piece (or multiple Bishops on the same color)
7920 if(pCnt[WhiteKnight+side])
7921 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7922 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7923 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7925 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7926 if(pCnt[WhiteAlfil+side])
7927 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7928 if(pCnt[WhiteWazir+side])
7929 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7936 CompareWithRights (Board b1, Board b2)
7939 if(!CompareBoards(b1, b2)) return FALSE;
7940 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7941 /* compare castling rights */
7942 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7943 rights++; /* King lost rights, while rook still had them */
7944 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7945 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7946 rights++; /* but at least one rook lost them */
7948 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7950 if( b1[CASTLING][5] != NoRights ) {
7951 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7958 Adjudicate (ChessProgramState *cps)
7959 { // [HGM] some adjudications useful with buggy engines
7960 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7961 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7962 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7963 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7964 int k, drop, count = 0; static int bare = 1;
7965 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7966 Boolean canAdjudicate = !appData.icsActive;
7968 // most tests only when we understand the game, i.e. legality-checking on
7969 if( appData.testLegality )
7970 { /* [HGM] Some more adjudications for obstinate engines */
7971 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7972 static int moveCount = 6;
7974 char *reason = NULL;
7976 /* Count what is on board. */
7977 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7979 /* Some material-based adjudications that have to be made before stalemate test */
7980 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7981 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7982 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7983 if(canAdjudicate && appData.checkMates) {
7985 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7986 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7987 "Xboard adjudication: King destroyed", GE_XBOARD );
7992 /* Bare King in Shatranj (loses) or Losers (wins) */
7993 if( nrW == 1 || nrB == 1) {
7994 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7995 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7996 if(canAdjudicate && appData.checkMates) {
7998 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7999 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8000 "Xboard adjudication: Bare king", GE_XBOARD );
8004 if( gameInfo.variant == VariantShatranj && --bare < 0)
8006 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8007 if(canAdjudicate && appData.checkMates) {
8008 /* but only adjudicate if adjudication enabled */
8010 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8011 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8012 "Xboard adjudication: Bare king", GE_XBOARD );
8019 // don't wait for engine to announce game end if we can judge ourselves
8020 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8022 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8023 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
8024 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8025 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8028 reason = "Xboard adjudication: 3rd check";
8029 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8039 reason = "Xboard adjudication: Stalemate";
8040 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8041 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8042 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8043 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8044 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8045 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8046 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8047 EP_CHECKMATE : EP_WINS);
8048 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8049 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8053 reason = "Xboard adjudication: Checkmate";
8054 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8055 if(gameInfo.variant == VariantShogi) {
8056 if(forwardMostMove > backwardMostMove
8057 && moveList[forwardMostMove-1][1] == '@'
8058 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8059 reason = "XBoard adjudication: pawn-drop mate";
8060 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8066 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8068 result = GameIsDrawn; break;
8070 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8072 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8076 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8078 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8079 GameEnds( result, reason, GE_XBOARD );
8083 /* Next absolutely insufficient mating material. */
8084 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8085 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8086 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8088 /* always flag draws, for judging claims */
8089 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8091 if(canAdjudicate && appData.materialDraws) {
8092 /* but only adjudicate them if adjudication enabled */
8093 if(engineOpponent) {
8094 SendToProgram("force\n", engineOpponent); // suppress reply
8095 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8097 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8102 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8103 if(gameInfo.variant == VariantXiangqi ?
8104 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8106 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8107 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8108 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8109 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8111 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8112 { /* if the first 3 moves do not show a tactical win, declare draw */
8113 if(engineOpponent) {
8114 SendToProgram("force\n", engineOpponent); // suppress reply
8115 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8117 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8120 } else moveCount = 6;
8123 // Repetition draws and 50-move rule can be applied independently of legality testing
8125 /* Check for rep-draws */
8127 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8128 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8129 for(k = forwardMostMove-2;
8130 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8131 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8132 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8135 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8136 /* compare castling rights */
8137 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8138 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8139 rights++; /* King lost rights, while rook still had them */
8140 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8141 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8142 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8143 rights++; /* but at least one rook lost them */
8145 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8146 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8148 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8149 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8150 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8153 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8154 && appData.drawRepeats > 1) {
8155 /* adjudicate after user-specified nr of repeats */
8156 int result = GameIsDrawn;
8157 char *details = "XBoard adjudication: repetition draw";
8158 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8159 // [HGM] xiangqi: check for forbidden perpetuals
8160 int m, ourPerpetual = 1, hisPerpetual = 1;
8161 for(m=forwardMostMove; m>k; m-=2) {
8162 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8163 ourPerpetual = 0; // the current mover did not always check
8164 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8165 hisPerpetual = 0; // the opponent did not always check
8167 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8168 ourPerpetual, hisPerpetual);
8169 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8170 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8171 details = "Xboard adjudication: perpetual checking";
8173 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8174 break; // (or we would have caught him before). Abort repetition-checking loop.
8176 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8177 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8179 details = "Xboard adjudication: repetition";
8181 } else // it must be XQ
8182 // Now check for perpetual chases
8183 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8184 hisPerpetual = PerpetualChase(k, forwardMostMove);
8185 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8186 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8187 static char resdet[MSG_SIZ];
8188 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8190 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8192 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8193 break; // Abort repetition-checking loop.
8195 // if neither of us is checking or chasing all the time, or both are, it is draw
8197 if(engineOpponent) {
8198 SendToProgram("force\n", engineOpponent); // suppress reply
8199 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8201 GameEnds( result, details, GE_XBOARD );
8204 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8205 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8209 /* Now we test for 50-move draws. Determine ply count */
8210 count = forwardMostMove;
8211 /* look for last irreversble move */
8212 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8214 /* if we hit starting position, add initial plies */
8215 if( count == backwardMostMove )
8216 count -= initialRulePlies;
8217 count = forwardMostMove - count;
8218 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8219 // adjust reversible move counter for checks in Xiangqi
8220 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8221 if(i < backwardMostMove) i = backwardMostMove;
8222 while(i <= forwardMostMove) {
8223 lastCheck = inCheck; // check evasion does not count
8224 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8225 if(inCheck || lastCheck) count--; // check does not count
8230 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8231 /* this is used to judge if draw claims are legal */
8232 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8233 if(engineOpponent) {
8234 SendToProgram("force\n", engineOpponent); // suppress reply
8235 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8237 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8241 /* if draw offer is pending, treat it as a draw claim
8242 * when draw condition present, to allow engines a way to
8243 * claim draws before making their move to avoid a race
8244 * condition occurring after their move
8246 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8248 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8249 p = "Draw claim: 50-move rule";
8250 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8251 p = "Draw claim: 3-fold repetition";
8252 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8253 p = "Draw claim: insufficient mating material";
8254 if( p != NULL && canAdjudicate) {
8255 if(engineOpponent) {
8256 SendToProgram("force\n", engineOpponent); // suppress reply
8257 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8259 GameEnds( GameIsDrawn, p, GE_XBOARD );
8264 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8265 if(engineOpponent) {
8266 SendToProgram("force\n", engineOpponent); // suppress reply
8267 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8269 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8276 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8277 { // [HGM] book: this routine intercepts moves to simulate book replies
8278 char *bookHit = NULL;
8280 //first determine if the incoming move brings opponent into his book
8281 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8282 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8283 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8284 if(bookHit != NULL && !cps->bookSuspend) {
8285 // make sure opponent is not going to reply after receiving move to book position
8286 SendToProgram("force\n", cps);
8287 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8289 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8290 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8291 // now arrange restart after book miss
8293 // after a book hit we never send 'go', and the code after the call to this routine
8294 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8295 char buf[MSG_SIZ], *move = bookHit;
8297 int fromX, fromY, toX, toY;
8301 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8302 &fromX, &fromY, &toX, &toY, &promoChar)) {
8303 (void) CoordsToAlgebraic(boards[forwardMostMove],
8304 PosFlags(forwardMostMove),
8305 fromY, fromX, toY, toX, promoChar, move);
8307 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8311 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8312 SendToProgram(buf, cps);
8313 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8314 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8315 SendToProgram("go\n", cps);
8316 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8317 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8318 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8319 SendToProgram("go\n", cps);
8320 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8322 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8326 LoadError (char *errmess, ChessProgramState *cps)
8327 { // unloads engine and switches back to -ncp mode if it was first
8328 if(cps->initDone) return FALSE;
8329 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8330 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8333 appData.noChessProgram = TRUE;
8334 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8335 gameMode = BeginningOfGame; ModeHighlight();
8338 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8339 DisplayMessage("", ""); // erase waiting message
8340 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8345 ChessProgramState *savedState;
8347 DeferredBookMove (void)
8349 if(savedState->lastPing != savedState->lastPong)
8350 ScheduleDelayedEvent(DeferredBookMove, 10);
8352 HandleMachineMove(savedMessage, savedState);
8355 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8356 static ChessProgramState *stalledEngine;
8357 static char stashedInputMove[MSG_SIZ];
8360 HandleMachineMove (char *message, ChessProgramState *cps)
8362 static char firstLeg[20];
8363 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8364 char realname[MSG_SIZ];
8365 int fromX, fromY, toX, toY;
8367 char promoChar, roar;
8369 int machineWhite, oldError;
8372 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8373 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8374 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8375 DisplayError(_("Invalid pairing from pairing engine"), 0);
8378 pairingReceived = 1;
8380 return; // Skim the pairing messages here.
8383 oldError = cps->userError; cps->userError = 0;
8385 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8387 * Kludge to ignore BEL characters
8389 while (*message == '\007') message++;
8392 * [HGM] engine debug message: ignore lines starting with '#' character
8394 if(cps->debug && *message == '#') return;
8397 * Look for book output
8399 if (cps == &first && bookRequested) {
8400 if (message[0] == '\t' || message[0] == ' ') {
8401 /* Part of the book output is here; append it */
8402 strcat(bookOutput, message);
8403 strcat(bookOutput, " \n");
8405 } else if (bookOutput[0] != NULLCHAR) {
8406 /* All of book output has arrived; display it */
8407 char *p = bookOutput;
8408 while (*p != NULLCHAR) {
8409 if (*p == '\t') *p = ' ';
8412 DisplayInformation(bookOutput);
8413 bookRequested = FALSE;
8414 /* Fall through to parse the current output */
8419 * Look for machine move.
8421 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8422 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8424 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8425 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8426 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8427 stalledEngine = cps;
8428 if(appData.ponderNextMove) { // bring opponent out of ponder
8429 if(gameMode == TwoMachinesPlay) {
8430 if(cps->other->pause)
8431 PauseEngine(cps->other);
8433 SendToProgram("easy\n", cps->other);
8440 /* This method is only useful on engines that support ping */
8441 if (cps->lastPing != cps->lastPong) {
8442 if (gameMode == BeginningOfGame) {
8443 /* Extra move from before last new; ignore */
8444 if (appData.debugMode) {
8445 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8448 if (appData.debugMode) {
8449 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8450 cps->which, gameMode);
8453 SendToProgram("undo\n", cps);
8459 case BeginningOfGame:
8460 /* Extra move from before last reset; ignore */
8461 if (appData.debugMode) {
8462 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8469 /* Extra move after we tried to stop. The mode test is
8470 not a reliable way of detecting this problem, but it's
8471 the best we can do on engines that don't support ping.
8473 if (appData.debugMode) {
8474 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8475 cps->which, gameMode);
8477 SendToProgram("undo\n", cps);
8480 case MachinePlaysWhite:
8481 case IcsPlayingWhite:
8482 machineWhite = TRUE;
8485 case MachinePlaysBlack:
8486 case IcsPlayingBlack:
8487 machineWhite = FALSE;
8490 case TwoMachinesPlay:
8491 machineWhite = (cps->twoMachinesColor[0] == 'w');
8494 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8495 if (appData.debugMode) {
8497 "Ignoring move out of turn by %s, gameMode %d"
8498 ", forwardMost %d\n",
8499 cps->which, gameMode, forwardMostMove);
8504 if(cps->alphaRank) AlphaRank(machineMove, 4);
8506 // [HGM] lion: (some very limited) support for Alien protocol
8508 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8509 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8511 } else if(firstLeg[0]) { // there was a previous leg;
8512 // only support case where same piece makes two step (and don't even test that!)
8513 char buf[20], *p = machineMove+1, *q = buf+1, f;
8514 safeStrCpy(buf, machineMove, 20);
8515 while(isdigit(*q)) q++; // find start of to-square
8516 safeStrCpy(machineMove, firstLeg, 20);
8517 while(isdigit(*p)) p++;
8518 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8519 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8520 firstLeg[0] = NULLCHAR;
8523 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8524 &fromX, &fromY, &toX, &toY, &promoChar)) {
8525 /* Machine move could not be parsed; ignore it. */
8526 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8527 machineMove, _(cps->which));
8528 DisplayMoveError(buf1);
8529 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8530 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8531 if (gameMode == TwoMachinesPlay) {
8532 GameEnds(machineWhite ? BlackWins : WhiteWins,
8538 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8539 /* So we have to redo legality test with true e.p. status here, */
8540 /* to make sure an illegal e.p. capture does not slip through, */
8541 /* to cause a forfeit on a justified illegal-move complaint */
8542 /* of the opponent. */
8543 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8545 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8546 fromY, fromX, toY, toX, promoChar);
8547 if(moveType == IllegalMove) {
8548 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8549 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8550 GameEnds(machineWhite ? BlackWins : WhiteWins,
8553 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8554 /* [HGM] Kludge to handle engines that send FRC-style castling
8555 when they shouldn't (like TSCP-Gothic) */
8557 case WhiteASideCastleFR:
8558 case BlackASideCastleFR:
8560 currentMoveString[2]++;
8562 case WhiteHSideCastleFR:
8563 case BlackHSideCastleFR:
8565 currentMoveString[2]--;
8567 default: ; // nothing to do, but suppresses warning of pedantic compilers
8570 hintRequested = FALSE;
8571 lastHint[0] = NULLCHAR;
8572 bookRequested = FALSE;
8573 /* Program may be pondering now */
8574 cps->maybeThinking = TRUE;
8575 if (cps->sendTime == 2) cps->sendTime = 1;
8576 if (cps->offeredDraw) cps->offeredDraw--;
8578 /* [AS] Save move info*/
8579 pvInfoList[ forwardMostMove ].score = programStats.score;
8580 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8581 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8583 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8585 /* Test suites abort the 'game' after one move */
8586 if(*appData.finger) {
8588 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8589 if(!f) f = fopen(appData.finger, "w");
8590 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8591 else { DisplayFatalError("Bad output file", errno, 0); return; }
8593 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8596 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8597 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8600 while( count < adjudicateLossPlies ) {
8601 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8604 score = -score; /* Flip score for winning side */
8607 if( score > adjudicateLossThreshold ) {
8614 if( count >= adjudicateLossPlies ) {
8615 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8617 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8618 "Xboard adjudication",
8625 if(Adjudicate(cps)) {
8626 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8627 return; // [HGM] adjudicate: for all automatic game ends
8631 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8633 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8634 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8636 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8638 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8640 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8641 char buf[3*MSG_SIZ];
8643 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8644 programStats.score / 100.,
8646 programStats.time / 100.,
8647 (unsigned int)programStats.nodes,
8648 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8649 programStats.movelist);
8651 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8656 /* [AS] Clear stats for next move */
8657 ClearProgramStats();
8658 thinkOutput[0] = NULLCHAR;
8659 hiddenThinkOutputState = 0;
8662 if (gameMode == TwoMachinesPlay) {
8663 /* [HGM] relaying draw offers moved to after reception of move */
8664 /* and interpreting offer as claim if it brings draw condition */
8665 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8666 SendToProgram("draw\n", cps->other);
8668 if (cps->other->sendTime) {
8669 SendTimeRemaining(cps->other,
8670 cps->other->twoMachinesColor[0] == 'w');
8672 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8673 if (firstMove && !bookHit) {
8675 if (cps->other->useColors) {
8676 SendToProgram(cps->other->twoMachinesColor, cps->other);
8678 SendToProgram("go\n", cps->other);
8680 cps->other->maybeThinking = TRUE;
8683 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8685 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8687 if (!pausing && appData.ringBellAfterMoves) {
8688 if(!roar) RingBell();
8692 * Reenable menu items that were disabled while
8693 * machine was thinking
8695 if (gameMode != TwoMachinesPlay)
8696 SetUserThinkingEnables();
8698 // [HGM] book: after book hit opponent has received move and is now in force mode
8699 // force the book reply into it, and then fake that it outputted this move by jumping
8700 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8702 static char bookMove[MSG_SIZ]; // a bit generous?
8704 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8705 strcat(bookMove, bookHit);
8708 programStats.nodes = programStats.depth = programStats.time =
8709 programStats.score = programStats.got_only_move = 0;
8710 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8712 if(cps->lastPing != cps->lastPong) {
8713 savedMessage = message; // args for deferred call
8715 ScheduleDelayedEvent(DeferredBookMove, 10);
8724 /* Set special modes for chess engines. Later something general
8725 * could be added here; for now there is just one kludge feature,
8726 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8727 * when "xboard" is given as an interactive command.
8729 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8730 cps->useSigint = FALSE;
8731 cps->useSigterm = FALSE;
8733 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8734 ParseFeatures(message+8, cps);
8735 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8738 if (!strncmp(message, "setup ", 6) &&
8739 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8740 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8741 ) { // [HGM] allow first engine to define opening position
8742 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8743 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8745 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8746 if(startedFromSetupPosition) return;
8747 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8749 while(message[s] && message[s++] != ' ');
8750 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8751 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8752 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8753 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8754 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8755 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8758 ParseFEN(boards[0], &dummy, message+s, FALSE);
8759 DrawPosition(TRUE, boards[0]);
8760 startedFromSetupPosition = TRUE;
8763 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8764 * want this, I was asked to put it in, and obliged.
8766 if (!strncmp(message, "setboard ", 9)) {
8767 Board initial_position;
8769 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8771 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8772 DisplayError(_("Bad FEN received from engine"), 0);
8776 CopyBoard(boards[0], initial_position);
8777 initialRulePlies = FENrulePlies;
8778 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8779 else gameMode = MachinePlaysBlack;
8780 DrawPosition(FALSE, boards[currentMove]);
8786 * Look for communication commands
8788 if (!strncmp(message, "telluser ", 9)) {
8789 if(message[9] == '\\' && message[10] == '\\')
8790 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8792 DisplayNote(message + 9);
8795 if (!strncmp(message, "tellusererror ", 14)) {
8797 if(message[14] == '\\' && message[15] == '\\')
8798 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8800 DisplayError(message + 14, 0);
8803 if (!strncmp(message, "tellopponent ", 13)) {
8804 if (appData.icsActive) {
8806 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8810 DisplayNote(message + 13);
8814 if (!strncmp(message, "tellothers ", 11)) {
8815 if (appData.icsActive) {
8817 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8820 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8823 if (!strncmp(message, "tellall ", 8)) {
8824 if (appData.icsActive) {
8826 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8830 DisplayNote(message + 8);
8834 if (strncmp(message, "warning", 7) == 0) {
8835 /* Undocumented feature, use tellusererror in new code */
8836 DisplayError(message, 0);
8839 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8840 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8841 strcat(realname, " query");
8842 AskQuestion(realname, buf2, buf1, cps->pr);
8845 /* Commands from the engine directly to ICS. We don't allow these to be
8846 * sent until we are logged on. Crafty kibitzes have been known to
8847 * interfere with the login process.
8850 if (!strncmp(message, "tellics ", 8)) {
8851 SendToICS(message + 8);
8855 if (!strncmp(message, "tellicsnoalias ", 15)) {
8856 SendToICS(ics_prefix);
8857 SendToICS(message + 15);
8861 /* The following are for backward compatibility only */
8862 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8863 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8864 SendToICS(ics_prefix);
8870 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8871 if(initPing == cps->lastPong) {
8872 if(gameInfo.variant == VariantUnknown) {
8873 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8874 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8875 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8881 if(!strncmp(message, "highlight ", 10)) {
8882 if(appData.testLegality && appData.markers) return;
8883 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8886 if(!strncmp(message, "click ", 6)) {
8887 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8888 if(appData.testLegality || !appData.oneClick) return;
8889 sscanf(message+6, "%c%d%c", &f, &y, &c);
8890 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8891 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8892 x = x*squareSize + (x+1)*lineGap + squareSize/2;
8893 y = y*squareSize + (y+1)*lineGap + squareSize/2;
8894 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8895 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8896 LeftClick(Release, lastLeftX, lastLeftY);
8897 controlKey = (c == ',');
8898 LeftClick(Press, x, y);
8899 LeftClick(Release, x, y);
8900 first.highlight = f;
8904 * If the move is illegal, cancel it and redraw the board.
8905 * Also deal with other error cases. Matching is rather loose
8906 * here to accommodate engines written before the spec.
8908 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8909 strncmp(message, "Error", 5) == 0) {
8910 if (StrStr(message, "name") ||
8911 StrStr(message, "rating") || StrStr(message, "?") ||
8912 StrStr(message, "result") || StrStr(message, "board") ||
8913 StrStr(message, "bk") || StrStr(message, "computer") ||
8914 StrStr(message, "variant") || StrStr(message, "hint") ||
8915 StrStr(message, "random") || StrStr(message, "depth") ||
8916 StrStr(message, "accepted")) {
8919 if (StrStr(message, "protover")) {
8920 /* Program is responding to input, so it's apparently done
8921 initializing, and this error message indicates it is
8922 protocol version 1. So we don't need to wait any longer
8923 for it to initialize and send feature commands. */
8924 FeatureDone(cps, 1);
8925 cps->protocolVersion = 1;
8928 cps->maybeThinking = FALSE;
8930 if (StrStr(message, "draw")) {
8931 /* Program doesn't have "draw" command */
8932 cps->sendDrawOffers = 0;
8935 if (cps->sendTime != 1 &&
8936 (StrStr(message, "time") || StrStr(message, "otim"))) {
8937 /* Program apparently doesn't have "time" or "otim" command */
8941 if (StrStr(message, "analyze")) {
8942 cps->analysisSupport = FALSE;
8943 cps->analyzing = FALSE;
8944 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8945 EditGameEvent(); // [HGM] try to preserve loaded game
8946 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8947 DisplayError(buf2, 0);
8950 if (StrStr(message, "(no matching move)st")) {
8951 /* Special kludge for GNU Chess 4 only */
8952 cps->stKludge = TRUE;
8953 SendTimeControl(cps, movesPerSession, timeControl,
8954 timeIncrement, appData.searchDepth,
8958 if (StrStr(message, "(no matching move)sd")) {
8959 /* Special kludge for GNU Chess 4 only */
8960 cps->sdKludge = TRUE;
8961 SendTimeControl(cps, movesPerSession, timeControl,
8962 timeIncrement, appData.searchDepth,
8966 if (!StrStr(message, "llegal")) {
8969 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8970 gameMode == IcsIdle) return;
8971 if (forwardMostMove <= backwardMostMove) return;
8972 if (pausing) PauseEvent();
8973 if(appData.forceIllegal) {
8974 // [HGM] illegal: machine refused move; force position after move into it
8975 SendToProgram("force\n", cps);
8976 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8977 // we have a real problem now, as SendBoard will use the a2a3 kludge
8978 // when black is to move, while there might be nothing on a2 or black
8979 // might already have the move. So send the board as if white has the move.
8980 // But first we must change the stm of the engine, as it refused the last move
8981 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8982 if(WhiteOnMove(forwardMostMove)) {
8983 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8984 SendBoard(cps, forwardMostMove); // kludgeless board
8986 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8987 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8988 SendBoard(cps, forwardMostMove+1); // kludgeless board
8990 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8991 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8992 gameMode == TwoMachinesPlay)
8993 SendToProgram("go\n", cps);
8996 if (gameMode == PlayFromGameFile) {
8997 /* Stop reading this game file */
8998 gameMode = EditGame;
9001 /* [HGM] illegal-move claim should forfeit game when Xboard */
9002 /* only passes fully legal moves */
9003 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9004 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9005 "False illegal-move claim", GE_XBOARD );
9006 return; // do not take back move we tested as valid
9008 currentMove = forwardMostMove-1;
9009 DisplayMove(currentMove-1); /* before DisplayMoveError */
9010 SwitchClocks(forwardMostMove-1); // [HGM] race
9011 DisplayBothClocks();
9012 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9013 parseList[currentMove], _(cps->which));
9014 DisplayMoveError(buf1);
9015 DrawPosition(FALSE, boards[currentMove]);
9017 SetUserThinkingEnables();
9020 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9021 /* Program has a broken "time" command that
9022 outputs a string not ending in newline.
9028 * If chess program startup fails, exit with an error message.
9029 * Attempts to recover here are futile. [HGM] Well, we try anyway
9031 if ((StrStr(message, "unknown host") != NULL)
9032 || (StrStr(message, "No remote directory") != NULL)
9033 || (StrStr(message, "not found") != NULL)
9034 || (StrStr(message, "No such file") != NULL)
9035 || (StrStr(message, "can't alloc") != NULL)
9036 || (StrStr(message, "Permission denied") != NULL)) {
9038 cps->maybeThinking = FALSE;
9039 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9040 _(cps->which), cps->program, cps->host, message);
9041 RemoveInputSource(cps->isr);
9042 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9043 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9044 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9050 * Look for hint output
9052 if (sscanf(message, "Hint: %s", buf1) == 1) {
9053 if (cps == &first && hintRequested) {
9054 hintRequested = FALSE;
9055 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9056 &fromX, &fromY, &toX, &toY, &promoChar)) {
9057 (void) CoordsToAlgebraic(boards[forwardMostMove],
9058 PosFlags(forwardMostMove),
9059 fromY, fromX, toY, toX, promoChar, buf1);
9060 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9061 DisplayInformation(buf2);
9063 /* Hint move could not be parsed!? */
9064 snprintf(buf2, sizeof(buf2),
9065 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9066 buf1, _(cps->which));
9067 DisplayError(buf2, 0);
9070 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9076 * Ignore other messages if game is not in progress
9078 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9079 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9082 * look for win, lose, draw, or draw offer
9084 if (strncmp(message, "1-0", 3) == 0) {
9085 char *p, *q, *r = "";
9086 p = strchr(message, '{');
9094 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9096 } else if (strncmp(message, "0-1", 3) == 0) {
9097 char *p, *q, *r = "";
9098 p = strchr(message, '{');
9106 /* Kludge for Arasan 4.1 bug */
9107 if (strcmp(r, "Black resigns") == 0) {
9108 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9111 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9113 } else if (strncmp(message, "1/2", 3) == 0) {
9114 char *p, *q, *r = "";
9115 p = strchr(message, '{');
9124 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9127 } else if (strncmp(message, "White resign", 12) == 0) {
9128 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9130 } else if (strncmp(message, "Black resign", 12) == 0) {
9131 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9133 } else if (strncmp(message, "White matches", 13) == 0 ||
9134 strncmp(message, "Black matches", 13) == 0 ) {
9135 /* [HGM] ignore GNUShogi noises */
9137 } else if (strncmp(message, "White", 5) == 0 &&
9138 message[5] != '(' &&
9139 StrStr(message, "Black") == NULL) {
9140 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9142 } else if (strncmp(message, "Black", 5) == 0 &&
9143 message[5] != '(') {
9144 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9146 } else if (strcmp(message, "resign") == 0 ||
9147 strcmp(message, "computer resigns") == 0) {
9149 case MachinePlaysBlack:
9150 case IcsPlayingBlack:
9151 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9153 case MachinePlaysWhite:
9154 case IcsPlayingWhite:
9155 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9157 case TwoMachinesPlay:
9158 if (cps->twoMachinesColor[0] == 'w')
9159 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9161 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9168 } else if (strncmp(message, "opponent mates", 14) == 0) {
9170 case MachinePlaysBlack:
9171 case IcsPlayingBlack:
9172 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9174 case MachinePlaysWhite:
9175 case IcsPlayingWhite:
9176 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9178 case TwoMachinesPlay:
9179 if (cps->twoMachinesColor[0] == 'w')
9180 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9182 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9189 } else if (strncmp(message, "computer mates", 14) == 0) {
9191 case MachinePlaysBlack:
9192 case IcsPlayingBlack:
9193 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9195 case MachinePlaysWhite:
9196 case IcsPlayingWhite:
9197 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9199 case TwoMachinesPlay:
9200 if (cps->twoMachinesColor[0] == 'w')
9201 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9203 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9210 } else if (strncmp(message, "checkmate", 9) == 0) {
9211 if (WhiteOnMove(forwardMostMove)) {
9212 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9214 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9217 } else if (strstr(message, "Draw") != NULL ||
9218 strstr(message, "game is a draw") != NULL) {
9219 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9221 } else if (strstr(message, "offer") != NULL &&
9222 strstr(message, "draw") != NULL) {
9224 if (appData.zippyPlay && first.initDone) {
9225 /* Relay offer to ICS */
9226 SendToICS(ics_prefix);
9227 SendToICS("draw\n");
9230 cps->offeredDraw = 2; /* valid until this engine moves twice */
9231 if (gameMode == TwoMachinesPlay) {
9232 if (cps->other->offeredDraw) {
9233 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9234 /* [HGM] in two-machine mode we delay relaying draw offer */
9235 /* until after we also have move, to see if it is really claim */
9237 } else if (gameMode == MachinePlaysWhite ||
9238 gameMode == MachinePlaysBlack) {
9239 if (userOfferedDraw) {
9240 DisplayInformation(_("Machine accepts your draw offer"));
9241 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9243 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9250 * Look for thinking output
9252 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9253 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9255 int plylev, mvleft, mvtot, curscore, time;
9256 char mvname[MOVE_LEN];
9260 int prefixHint = FALSE;
9261 mvname[0] = NULLCHAR;
9264 case MachinePlaysBlack:
9265 case IcsPlayingBlack:
9266 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9268 case MachinePlaysWhite:
9269 case IcsPlayingWhite:
9270 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9275 case IcsObserving: /* [DM] icsEngineAnalyze */
9276 if (!appData.icsEngineAnalyze) ignore = TRUE;
9278 case TwoMachinesPlay:
9279 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9289 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9291 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9292 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9294 if (plyext != ' ' && plyext != '\t') {
9298 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9299 if( cps->scoreIsAbsolute &&
9300 ( gameMode == MachinePlaysBlack ||
9301 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9302 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9303 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9304 !WhiteOnMove(currentMove)
9307 curscore = -curscore;
9310 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9312 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9315 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9316 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9317 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9318 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9319 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9320 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9324 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9325 DisplayError(_("failed writing PV"), 0);
9328 tempStats.depth = plylev;
9329 tempStats.nodes = nodes;
9330 tempStats.time = time;
9331 tempStats.score = curscore;
9332 tempStats.got_only_move = 0;
9334 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9337 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9338 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9339 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9340 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9341 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9342 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9343 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9344 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9347 /* Buffer overflow protection */
9348 if (pv[0] != NULLCHAR) {
9349 if (strlen(pv) >= sizeof(tempStats.movelist)
9350 && appData.debugMode) {
9352 "PV is too long; using the first %u bytes.\n",
9353 (unsigned) sizeof(tempStats.movelist) - 1);
9356 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9358 sprintf(tempStats.movelist, " no PV\n");
9361 if (tempStats.seen_stat) {
9362 tempStats.ok_to_send = 1;
9365 if (strchr(tempStats.movelist, '(') != NULL) {
9366 tempStats.line_is_book = 1;
9367 tempStats.nr_moves = 0;
9368 tempStats.moves_left = 0;
9370 tempStats.line_is_book = 0;
9373 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9374 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9376 SendProgramStatsToFrontend( cps, &tempStats );
9379 [AS] Protect the thinkOutput buffer from overflow... this
9380 is only useful if buf1 hasn't overflowed first!
9382 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9384 (gameMode == TwoMachinesPlay ?
9385 ToUpper(cps->twoMachinesColor[0]) : ' '),
9386 ((double) curscore) / 100.0,
9387 prefixHint ? lastHint : "",
9388 prefixHint ? " " : "" );
9390 if( buf1[0] != NULLCHAR ) {
9391 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9393 if( strlen(pv) > max_len ) {
9394 if( appData.debugMode) {
9395 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9397 pv[max_len+1] = '\0';
9400 strcat( thinkOutput, pv);
9403 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9404 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9405 DisplayMove(currentMove - 1);
9409 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9410 /* crafty (9.25+) says "(only move) <move>"
9411 * if there is only 1 legal move
9413 sscanf(p, "(only move) %s", buf1);
9414 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9415 sprintf(programStats.movelist, "%s (only move)", buf1);
9416 programStats.depth = 1;
9417 programStats.nr_moves = 1;
9418 programStats.moves_left = 1;
9419 programStats.nodes = 1;
9420 programStats.time = 1;
9421 programStats.got_only_move = 1;
9423 /* Not really, but we also use this member to
9424 mean "line isn't going to change" (Crafty
9425 isn't searching, so stats won't change) */
9426 programStats.line_is_book = 1;
9428 SendProgramStatsToFrontend( cps, &programStats );
9430 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9431 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9432 DisplayMove(currentMove - 1);
9435 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9436 &time, &nodes, &plylev, &mvleft,
9437 &mvtot, mvname) >= 5) {
9438 /* The stat01: line is from Crafty (9.29+) in response
9439 to the "." command */
9440 programStats.seen_stat = 1;
9441 cps->maybeThinking = TRUE;
9443 if (programStats.got_only_move || !appData.periodicUpdates)
9446 programStats.depth = plylev;
9447 programStats.time = time;
9448 programStats.nodes = nodes;
9449 programStats.moves_left = mvleft;
9450 programStats.nr_moves = mvtot;
9451 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9452 programStats.ok_to_send = 1;
9453 programStats.movelist[0] = '\0';
9455 SendProgramStatsToFrontend( cps, &programStats );
9459 } else if (strncmp(message,"++",2) == 0) {
9460 /* Crafty 9.29+ outputs this */
9461 programStats.got_fail = 2;
9464 } else if (strncmp(message,"--",2) == 0) {
9465 /* Crafty 9.29+ outputs this */
9466 programStats.got_fail = 1;
9469 } else if (thinkOutput[0] != NULLCHAR &&
9470 strncmp(message, " ", 4) == 0) {
9471 unsigned message_len;
9474 while (*p && *p == ' ') p++;
9476 message_len = strlen( p );
9478 /* [AS] Avoid buffer overflow */
9479 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9480 strcat(thinkOutput, " ");
9481 strcat(thinkOutput, p);
9484 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9485 strcat(programStats.movelist, " ");
9486 strcat(programStats.movelist, p);
9489 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9490 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9491 DisplayMove(currentMove - 1);
9499 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9500 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9502 ChessProgramStats cpstats;
9504 if (plyext != ' ' && plyext != '\t') {
9508 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9509 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9510 curscore = -curscore;
9513 cpstats.depth = plylev;
9514 cpstats.nodes = nodes;
9515 cpstats.time = time;
9516 cpstats.score = curscore;
9517 cpstats.got_only_move = 0;
9518 cpstats.movelist[0] = '\0';
9520 if (buf1[0] != NULLCHAR) {
9521 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9524 cpstats.ok_to_send = 0;
9525 cpstats.line_is_book = 0;
9526 cpstats.nr_moves = 0;
9527 cpstats.moves_left = 0;
9529 SendProgramStatsToFrontend( cps, &cpstats );
9536 /* Parse a game score from the character string "game", and
9537 record it as the history of the current game. The game
9538 score is NOT assumed to start from the standard position.
9539 The display is not updated in any way.
9542 ParseGameHistory (char *game)
9545 int fromX, fromY, toX, toY, boardIndex;
9550 if (appData.debugMode)
9551 fprintf(debugFP, "Parsing game history: %s\n", game);
9553 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9554 gameInfo.site = StrSave(appData.icsHost);
9555 gameInfo.date = PGNDate();
9556 gameInfo.round = StrSave("-");
9558 /* Parse out names of players */
9559 while (*game == ' ') game++;
9561 while (*game != ' ') *p++ = *game++;
9563 gameInfo.white = StrSave(buf);
9564 while (*game == ' ') game++;
9566 while (*game != ' ' && *game != '\n') *p++ = *game++;
9568 gameInfo.black = StrSave(buf);
9571 boardIndex = blackPlaysFirst ? 1 : 0;
9574 yyboardindex = boardIndex;
9575 moveType = (ChessMove) Myylex();
9577 case IllegalMove: /* maybe suicide chess, etc. */
9578 if (appData.debugMode) {
9579 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9580 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9581 setbuf(debugFP, NULL);
9583 case WhitePromotion:
9584 case BlackPromotion:
9585 case WhiteNonPromotion:
9586 case BlackNonPromotion:
9589 case WhiteCapturesEnPassant:
9590 case BlackCapturesEnPassant:
9591 case WhiteKingSideCastle:
9592 case WhiteQueenSideCastle:
9593 case BlackKingSideCastle:
9594 case BlackQueenSideCastle:
9595 case WhiteKingSideCastleWild:
9596 case WhiteQueenSideCastleWild:
9597 case BlackKingSideCastleWild:
9598 case BlackQueenSideCastleWild:
9600 case WhiteHSideCastleFR:
9601 case WhiteASideCastleFR:
9602 case BlackHSideCastleFR:
9603 case BlackASideCastleFR:
9605 fromX = currentMoveString[0] - AAA;
9606 fromY = currentMoveString[1] - ONE;
9607 toX = currentMoveString[2] - AAA;
9608 toY = currentMoveString[3] - ONE;
9609 promoChar = currentMoveString[4];
9613 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9614 fromX = moveType == WhiteDrop ?
9615 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9616 (int) CharToPiece(ToLower(currentMoveString[0]));
9618 toX = currentMoveString[2] - AAA;
9619 toY = currentMoveString[3] - ONE;
9620 promoChar = NULLCHAR;
9624 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9625 if (appData.debugMode) {
9626 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9627 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9628 setbuf(debugFP, NULL);
9630 DisplayError(buf, 0);
9632 case ImpossibleMove:
9634 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9635 if (appData.debugMode) {
9636 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9637 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9638 setbuf(debugFP, NULL);
9640 DisplayError(buf, 0);
9643 if (boardIndex < backwardMostMove) {
9644 /* Oops, gap. How did that happen? */
9645 DisplayError(_("Gap in move list"), 0);
9648 backwardMostMove = blackPlaysFirst ? 1 : 0;
9649 if (boardIndex > forwardMostMove) {
9650 forwardMostMove = boardIndex;
9654 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9655 strcat(parseList[boardIndex-1], " ");
9656 strcat(parseList[boardIndex-1], yy_text);
9668 case GameUnfinished:
9669 if (gameMode == IcsExamining) {
9670 if (boardIndex < backwardMostMove) {
9671 /* Oops, gap. How did that happen? */
9674 backwardMostMove = blackPlaysFirst ? 1 : 0;
9677 gameInfo.result = moveType;
9678 p = strchr(yy_text, '{');
9679 if (p == NULL) p = strchr(yy_text, '(');
9682 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9684 q = strchr(p, *p == '{' ? '}' : ')');
9685 if (q != NULL) *q = NULLCHAR;
9688 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9689 gameInfo.resultDetails = StrSave(p);
9692 if (boardIndex >= forwardMostMove &&
9693 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9694 backwardMostMove = blackPlaysFirst ? 1 : 0;
9697 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9698 fromY, fromX, toY, toX, promoChar,
9699 parseList[boardIndex]);
9700 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9701 /* currentMoveString is set as a side-effect of yylex */
9702 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9703 strcat(moveList[boardIndex], "\n");
9705 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9706 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9712 if(!IS_SHOGI(gameInfo.variant))
9713 strcat(parseList[boardIndex - 1], "+");
9717 strcat(parseList[boardIndex - 1], "#");
9724 /* Apply a move to the given board */
9726 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9728 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9729 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9731 /* [HGM] compute & store e.p. status and castling rights for new position */
9732 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9734 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9735 oldEP = (signed char)board[EP_STATUS];
9736 board[EP_STATUS] = EP_NONE;
9738 if (fromY == DROP_RANK) {
9740 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9741 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9744 piece = board[toY][toX] = (ChessSquare) fromX;
9749 if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9750 victim = board[killY][killX],
9751 board[killY][killX] = EmptySquare,
9752 board[EP_STATUS] = EP_CAPTURE;
9754 if( board[toY][toX] != EmptySquare ) {
9755 board[EP_STATUS] = EP_CAPTURE;
9756 if( (fromX != toX || fromY != toY) && // not igui!
9757 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9758 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
9759 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9763 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9764 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9765 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9767 if( board[fromY][fromX] == WhitePawn ) {
9768 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9769 board[EP_STATUS] = EP_PAWN_MOVE;
9771 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9772 gameInfo.variant != VariantBerolina || toX < fromX)
9773 board[EP_STATUS] = toX | berolina;
9774 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9775 gameInfo.variant != VariantBerolina || toX > fromX)
9776 board[EP_STATUS] = toX;
9779 if( board[fromY][fromX] == BlackPawn ) {
9780 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9781 board[EP_STATUS] = EP_PAWN_MOVE;
9782 if( toY-fromY== -2) {
9783 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9784 gameInfo.variant != VariantBerolina || toX < fromX)
9785 board[EP_STATUS] = toX | berolina;
9786 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9787 gameInfo.variant != VariantBerolina || toX > fromX)
9788 board[EP_STATUS] = toX;
9792 for(i=0; i<nrCastlingRights; i++) {
9793 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9794 board[CASTLING][i] == toX && castlingRank[i] == toY
9795 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9798 if(gameInfo.variant == VariantSChess) { // update virginity
9799 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9800 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9801 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9802 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9805 if (fromX == toX && fromY == toY) return;
9807 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9808 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9809 if(gameInfo.variant == VariantKnightmate)
9810 king += (int) WhiteUnicorn - (int) WhiteKing;
9812 /* Code added by Tord: */
9813 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9814 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9815 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9816 board[fromY][fromX] = EmptySquare;
9817 board[toY][toX] = EmptySquare;
9818 if((toX > fromX) != (piece == WhiteRook)) {
9819 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9821 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9823 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9824 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9825 board[fromY][fromX] = EmptySquare;
9826 board[toY][toX] = EmptySquare;
9827 if((toX > fromX) != (piece == BlackRook)) {
9828 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9830 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9832 /* End of code added by Tord */
9834 } else if (board[fromY][fromX] == king
9835 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9836 && toY == fromY && toX > fromX+1) {
9837 board[fromY][fromX] = EmptySquare;
9838 board[toY][toX] = king;
9839 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9840 board[fromY][BOARD_RGHT-1] = EmptySquare;
9841 } else if (board[fromY][fromX] == king
9842 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9843 && toY == fromY && toX < fromX-1) {
9844 board[fromY][fromX] = EmptySquare;
9845 board[toY][toX] = king;
9846 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9847 board[fromY][BOARD_LEFT] = EmptySquare;
9848 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9849 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9850 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9852 /* white pawn promotion */
9853 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9854 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9855 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9856 board[fromY][fromX] = EmptySquare;
9857 } else if ((fromY >= BOARD_HEIGHT>>1)
9858 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9860 && gameInfo.variant != VariantXiangqi
9861 && gameInfo.variant != VariantBerolina
9862 && (board[fromY][fromX] == WhitePawn)
9863 && (board[toY][toX] == EmptySquare)) {
9864 board[fromY][fromX] = EmptySquare;
9865 board[toY][toX] = WhitePawn;
9866 captured = board[toY - 1][toX];
9867 board[toY - 1][toX] = EmptySquare;
9868 } else if ((fromY == BOARD_HEIGHT-4)
9870 && gameInfo.variant == VariantBerolina
9871 && (board[fromY][fromX] == WhitePawn)
9872 && (board[toY][toX] == EmptySquare)) {
9873 board[fromY][fromX] = EmptySquare;
9874 board[toY][toX] = WhitePawn;
9875 if(oldEP & EP_BEROLIN_A) {
9876 captured = board[fromY][fromX-1];
9877 board[fromY][fromX-1] = EmptySquare;
9878 }else{ captured = board[fromY][fromX+1];
9879 board[fromY][fromX+1] = EmptySquare;
9881 } else if (board[fromY][fromX] == king
9882 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9883 && toY == fromY && toX > fromX+1) {
9884 board[fromY][fromX] = EmptySquare;
9885 board[toY][toX] = king;
9886 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9887 board[fromY][BOARD_RGHT-1] = EmptySquare;
9888 } else if (board[fromY][fromX] == king
9889 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9890 && toY == fromY && toX < fromX-1) {
9891 board[fromY][fromX] = EmptySquare;
9892 board[toY][toX] = king;
9893 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9894 board[fromY][BOARD_LEFT] = EmptySquare;
9895 } else if (fromY == 7 && fromX == 3
9896 && board[fromY][fromX] == BlackKing
9897 && toY == 7 && toX == 5) {
9898 board[fromY][fromX] = EmptySquare;
9899 board[toY][toX] = BlackKing;
9900 board[fromY][7] = EmptySquare;
9901 board[toY][4] = BlackRook;
9902 } else if (fromY == 7 && fromX == 3
9903 && board[fromY][fromX] == BlackKing
9904 && toY == 7 && toX == 1) {
9905 board[fromY][fromX] = EmptySquare;
9906 board[toY][toX] = BlackKing;
9907 board[fromY][0] = EmptySquare;
9908 board[toY][2] = BlackRook;
9909 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9910 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9911 && toY < promoRank && promoChar
9913 /* black pawn promotion */
9914 board[toY][toX] = CharToPiece(ToLower(promoChar));
9915 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9916 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9917 board[fromY][fromX] = EmptySquare;
9918 } else if ((fromY < BOARD_HEIGHT>>1)
9919 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9921 && gameInfo.variant != VariantXiangqi
9922 && gameInfo.variant != VariantBerolina
9923 && (board[fromY][fromX] == BlackPawn)
9924 && (board[toY][toX] == EmptySquare)) {
9925 board[fromY][fromX] = EmptySquare;
9926 board[toY][toX] = BlackPawn;
9927 captured = board[toY + 1][toX];
9928 board[toY + 1][toX] = EmptySquare;
9929 } else if ((fromY == 3)
9931 && gameInfo.variant == VariantBerolina
9932 && (board[fromY][fromX] == BlackPawn)
9933 && (board[toY][toX] == EmptySquare)) {
9934 board[fromY][fromX] = EmptySquare;
9935 board[toY][toX] = BlackPawn;
9936 if(oldEP & EP_BEROLIN_A) {
9937 captured = board[fromY][fromX-1];
9938 board[fromY][fromX-1] = EmptySquare;
9939 }else{ captured = board[fromY][fromX+1];
9940 board[fromY][fromX+1] = EmptySquare;
9943 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9944 board[fromY][fromX] = EmptySquare;
9945 board[toY][toX] = piece;
9949 if (gameInfo.holdingsWidth != 0) {
9951 /* !!A lot more code needs to be written to support holdings */
9952 /* [HGM] OK, so I have written it. Holdings are stored in the */
9953 /* penultimate board files, so they are automaticlly stored */
9954 /* in the game history. */
9955 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9956 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9957 /* Delete from holdings, by decreasing count */
9958 /* and erasing image if necessary */
9959 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9960 if(p < (int) BlackPawn) { /* white drop */
9961 p -= (int)WhitePawn;
9962 p = PieceToNumber((ChessSquare)p);
9963 if(p >= gameInfo.holdingsSize) p = 0;
9964 if(--board[p][BOARD_WIDTH-2] <= 0)
9965 board[p][BOARD_WIDTH-1] = EmptySquare;
9966 if((int)board[p][BOARD_WIDTH-2] < 0)
9967 board[p][BOARD_WIDTH-2] = 0;
9968 } else { /* black drop */
9969 p -= (int)BlackPawn;
9970 p = PieceToNumber((ChessSquare)p);
9971 if(p >= gameInfo.holdingsSize) p = 0;
9972 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9973 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9974 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9975 board[BOARD_HEIGHT-1-p][1] = 0;
9978 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9979 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9980 /* [HGM] holdings: Add to holdings, if holdings exist */
9981 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9982 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9983 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9986 if (p >= (int) BlackPawn) {
9987 p -= (int)BlackPawn;
9988 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9989 /* in Shogi restore piece to its original first */
9990 captured = (ChessSquare) (DEMOTED captured);
9993 p = PieceToNumber((ChessSquare)p);
9994 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9995 board[p][BOARD_WIDTH-2]++;
9996 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9998 p -= (int)WhitePawn;
9999 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10000 captured = (ChessSquare) (DEMOTED captured);
10003 p = PieceToNumber((ChessSquare)p);
10004 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10005 board[BOARD_HEIGHT-1-p][1]++;
10006 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10009 } else if (gameInfo.variant == VariantAtomic) {
10010 if (captured != EmptySquare) {
10012 for (y = toY-1; y <= toY+1; y++) {
10013 for (x = toX-1; x <= toX+1; x++) {
10014 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10015 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10016 board[y][x] = EmptySquare;
10020 board[toY][toX] = EmptySquare;
10024 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10025 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10027 if(promoChar == '+') {
10028 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10029 board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10030 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10031 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10032 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10033 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10034 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10035 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10036 board[toY][toX] = newPiece;
10038 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10039 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10040 // [HGM] superchess: take promotion piece out of holdings
10041 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10042 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10043 if(!--board[k][BOARD_WIDTH-2])
10044 board[k][BOARD_WIDTH-1] = EmptySquare;
10046 if(!--board[BOARD_HEIGHT-1-k][1])
10047 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10052 /* Updates forwardMostMove */
10054 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10056 int x = toX, y = toY;
10057 char *s = parseList[forwardMostMove];
10058 ChessSquare p = boards[forwardMostMove][toY][toX];
10059 // forwardMostMove++; // [HGM] bare: moved downstream
10061 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10062 (void) CoordsToAlgebraic(boards[forwardMostMove],
10063 PosFlags(forwardMostMove),
10064 fromY, fromX, y, x, promoChar,
10066 if(killX >= 0 && killY >= 0)
10067 sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10069 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10070 int timeLeft; static int lastLoadFlag=0; int king, piece;
10071 piece = boards[forwardMostMove][fromY][fromX];
10072 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10073 if(gameInfo.variant == VariantKnightmate)
10074 king += (int) WhiteUnicorn - (int) WhiteKing;
10075 if(forwardMostMove == 0) {
10076 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10077 fprintf(serverMoves, "%s;", UserName());
10078 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10079 fprintf(serverMoves, "%s;", second.tidy);
10080 fprintf(serverMoves, "%s;", first.tidy);
10081 if(gameMode == MachinePlaysWhite)
10082 fprintf(serverMoves, "%s;", UserName());
10083 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10084 fprintf(serverMoves, "%s;", second.tidy);
10085 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10086 lastLoadFlag = loadFlag;
10088 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10089 // print castling suffix
10090 if( toY == fromY && piece == king ) {
10092 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10094 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10097 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10098 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10099 boards[forwardMostMove][toY][toX] == EmptySquare
10100 && fromX != toX && fromY != toY)
10101 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10102 // promotion suffix
10103 if(promoChar != NULLCHAR) {
10104 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10105 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10106 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10107 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10110 char buf[MOVE_LEN*2], *p; int len;
10111 fprintf(serverMoves, "/%d/%d",
10112 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10113 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10114 else timeLeft = blackTimeRemaining/1000;
10115 fprintf(serverMoves, "/%d", timeLeft);
10116 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10117 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10118 if(p = strchr(buf, '=')) *p = NULLCHAR;
10119 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10120 fprintf(serverMoves, "/%s", buf);
10122 fflush(serverMoves);
10125 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10126 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10129 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10130 if (commentList[forwardMostMove+1] != NULL) {
10131 free(commentList[forwardMostMove+1]);
10132 commentList[forwardMostMove+1] = NULL;
10134 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10135 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10136 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10137 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10138 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10139 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10140 adjustedClock = FALSE;
10141 gameInfo.result = GameUnfinished;
10142 if (gameInfo.resultDetails != NULL) {
10143 free(gameInfo.resultDetails);
10144 gameInfo.resultDetails = NULL;
10146 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10147 moveList[forwardMostMove - 1]);
10148 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10154 if(!IS_SHOGI(gameInfo.variant))
10155 strcat(parseList[forwardMostMove - 1], "+");
10159 strcat(parseList[forwardMostMove - 1], "#");
10164 /* Updates currentMove if not pausing */
10166 ShowMove (int fromX, int fromY, int toX, int toY)
10168 int instant = (gameMode == PlayFromGameFile) ?
10169 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10170 if(appData.noGUI) return;
10171 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10173 if (forwardMostMove == currentMove + 1) {
10174 AnimateMove(boards[forwardMostMove - 1],
10175 fromX, fromY, toX, toY);
10178 currentMove = forwardMostMove;
10181 killX = killY = -1; // [HGM] lion: used up
10183 if (instant) return;
10185 DisplayMove(currentMove - 1);
10186 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10187 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10188 SetHighlights(fromX, fromY, toX, toY);
10191 DrawPosition(FALSE, boards[currentMove]);
10192 DisplayBothClocks();
10193 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10197 SendEgtPath (ChessProgramState *cps)
10198 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10199 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10201 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10204 char c, *q = name+1, *r, *s;
10206 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10207 while(*p && *p != ',') *q++ = *p++;
10208 *q++ = ':'; *q = 0;
10209 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10210 strcmp(name, ",nalimov:") == 0 ) {
10211 // take nalimov path from the menu-changeable option first, if it is defined
10212 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10213 SendToProgram(buf,cps); // send egtbpath command for nalimov
10215 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10216 (s = StrStr(appData.egtFormats, name)) != NULL) {
10217 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10218 s = r = StrStr(s, ":") + 1; // beginning of path info
10219 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10220 c = *r; *r = 0; // temporarily null-terminate path info
10221 *--q = 0; // strip of trailig ':' from name
10222 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10224 SendToProgram(buf,cps); // send egtbpath command for this format
10226 if(*p == ',') p++; // read away comma to position for next format name
10231 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10233 int width = 8, height = 8, holdings = 0; // most common sizes
10234 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10235 // correct the deviations default for each variant
10236 if( v == VariantXiangqi ) width = 9, height = 10;
10237 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10238 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10239 if( v == VariantCapablanca || v == VariantCapaRandom ||
10240 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10242 if( v == VariantCourier ) width = 12;
10243 if( v == VariantSuper ) holdings = 8;
10244 if( v == VariantGreat ) width = 10, holdings = 8;
10245 if( v == VariantSChess ) holdings = 7;
10246 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10247 if( v == VariantChuChess) width = 10, height = 10;
10248 if( v == VariantChu ) width = 12, height = 12;
10249 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10250 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10251 holdingsSize >= 0 && holdingsSize != holdings;
10254 char variantError[MSG_SIZ];
10257 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10258 { // returns error message (recognizable by upper-case) if engine does not support the variant
10259 char *p, *variant = VariantName(v);
10260 static char b[MSG_SIZ];
10261 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10262 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10263 holdingsSize, variant); // cook up sized variant name
10264 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10265 if(StrStr(list, b) == NULL) {
10266 // specific sized variant not known, check if general sizing allowed
10267 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10268 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10269 boardWidth, boardHeight, holdingsSize, engine);
10272 /* [HGM] here we really should compare with the maximum supported board size */
10274 } else snprintf(b, MSG_SIZ,"%s", variant);
10275 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10276 p = StrStr(list, b);
10277 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10279 // occurs not at all in list, or only as sub-string
10280 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10281 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10282 int l = strlen(variantError);
10284 while(p != list && p[-1] != ',') p--;
10285 q = strchr(p, ',');
10286 if(q) *q = NULLCHAR;
10287 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10296 InitChessProgram (ChessProgramState *cps, int setup)
10297 /* setup needed to setup FRC opening position */
10299 char buf[MSG_SIZ], *b;
10300 if (appData.noChessProgram) return;
10301 hintRequested = FALSE;
10302 bookRequested = FALSE;
10304 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10305 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10306 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10307 if(cps->memSize) { /* [HGM] memory */
10308 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10309 SendToProgram(buf, cps);
10311 SendEgtPath(cps); /* [HGM] EGT */
10312 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10313 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10314 SendToProgram(buf, cps);
10317 SendToProgram(cps->initString, cps);
10318 if (gameInfo.variant != VariantNormal &&
10319 gameInfo.variant != VariantLoadable
10320 /* [HGM] also send variant if board size non-standard */
10321 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10323 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10324 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10326 DisplayFatalError(variantError, 0, 1);
10330 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10331 SendToProgram(buf, cps);
10333 currentlyInitializedVariant = gameInfo.variant;
10335 /* [HGM] send opening position in FRC to first engine */
10337 SendToProgram("force\n", cps);
10339 /* engine is now in force mode! Set flag to wake it up after first move. */
10340 setboardSpoiledMachineBlack = 1;
10343 if (cps->sendICS) {
10344 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10345 SendToProgram(buf, cps);
10347 cps->maybeThinking = FALSE;
10348 cps->offeredDraw = 0;
10349 if (!appData.icsActive) {
10350 SendTimeControl(cps, movesPerSession, timeControl,
10351 timeIncrement, appData.searchDepth,
10354 if (appData.showThinking
10355 // [HGM] thinking: four options require thinking output to be sent
10356 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10358 SendToProgram("post\n", cps);
10360 SendToProgram("hard\n", cps);
10361 if (!appData.ponderNextMove) {
10362 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10363 it without being sure what state we are in first. "hard"
10364 is not a toggle, so that one is OK.
10366 SendToProgram("easy\n", cps);
10368 if (cps->usePing) {
10369 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10370 SendToProgram(buf, cps);
10372 cps->initDone = TRUE;
10373 ClearEngineOutputPane(cps == &second);
10378 ResendOptions (ChessProgramState *cps)
10379 { // send the stored value of the options
10382 Option *opt = cps->option;
10383 for(i=0; i<cps->nrOptions; i++, opt++) {
10384 switch(opt->type) {
10388 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10391 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10394 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10400 SendToProgram(buf, cps);
10405 StartChessProgram (ChessProgramState *cps)
10410 if (appData.noChessProgram) return;
10411 cps->initDone = FALSE;
10413 if (strcmp(cps->host, "localhost") == 0) {
10414 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10415 } else if (*appData.remoteShell == NULLCHAR) {
10416 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10418 if (*appData.remoteUser == NULLCHAR) {
10419 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10422 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10423 cps->host, appData.remoteUser, cps->program);
10425 err = StartChildProcess(buf, "", &cps->pr);
10429 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10430 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10431 if(cps != &first) return;
10432 appData.noChessProgram = TRUE;
10435 // DisplayFatalError(buf, err, 1);
10436 // cps->pr = NoProc;
10437 // cps->isr = NULL;
10441 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10442 if (cps->protocolVersion > 1) {
10443 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10444 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10445 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10446 cps->comboCnt = 0; // and values of combo boxes
10448 SendToProgram(buf, cps);
10449 if(cps->reload) ResendOptions(cps);
10451 SendToProgram("xboard\n", cps);
10456 TwoMachinesEventIfReady P((void))
10458 static int curMess = 0;
10459 if (first.lastPing != first.lastPong) {
10460 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10461 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10464 if (second.lastPing != second.lastPong) {
10465 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10466 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10469 DisplayMessage("", ""); curMess = 0;
10470 TwoMachinesEvent();
10474 MakeName (char *template)
10478 static char buf[MSG_SIZ];
10482 clock = time((time_t *)NULL);
10483 tm = localtime(&clock);
10485 while(*p++ = *template++) if(p[-1] == '%') {
10486 switch(*template++) {
10487 case 0: *p = 0; return buf;
10488 case 'Y': i = tm->tm_year+1900; break;
10489 case 'y': i = tm->tm_year-100; break;
10490 case 'M': i = tm->tm_mon+1; break;
10491 case 'd': i = tm->tm_mday; break;
10492 case 'h': i = tm->tm_hour; break;
10493 case 'm': i = tm->tm_min; break;
10494 case 's': i = tm->tm_sec; break;
10497 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10503 CountPlayers (char *p)
10506 while(p = strchr(p, '\n')) p++, n++; // count participants
10511 WriteTourneyFile (char *results, FILE *f)
10512 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10513 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10514 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10515 // create a file with tournament description
10516 fprintf(f, "-participants {%s}\n", appData.participants);
10517 fprintf(f, "-seedBase %d\n", appData.seedBase);
10518 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10519 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10520 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10521 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10522 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10523 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10524 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10525 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10526 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10527 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10528 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10529 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10530 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10531 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10532 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10533 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10534 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10535 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10536 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10537 fprintf(f, "-smpCores %d\n", appData.smpCores);
10539 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10541 fprintf(f, "-mps %d\n", appData.movesPerSession);
10542 fprintf(f, "-tc %s\n", appData.timeControl);
10543 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10545 fprintf(f, "-results \"%s\"\n", results);
10550 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10553 Substitute (char *participants, int expunge)
10555 int i, changed, changes=0, nPlayers=0;
10556 char *p, *q, *r, buf[MSG_SIZ];
10557 if(participants == NULL) return;
10558 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10559 r = p = participants; q = appData.participants;
10560 while(*p && *p == *q) {
10561 if(*p == '\n') r = p+1, nPlayers++;
10564 if(*p) { // difference
10565 while(*p && *p++ != '\n');
10566 while(*q && *q++ != '\n');
10567 changed = nPlayers;
10568 changes = 1 + (strcmp(p, q) != 0);
10570 if(changes == 1) { // a single engine mnemonic was changed
10571 q = r; while(*q) nPlayers += (*q++ == '\n');
10572 p = buf; while(*r && (*p = *r++) != '\n') p++;
10574 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10575 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10576 if(mnemonic[i]) { // The substitute is valid
10578 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10579 flock(fileno(f), LOCK_EX);
10580 ParseArgsFromFile(f);
10581 fseek(f, 0, SEEK_SET);
10582 FREE(appData.participants); appData.participants = participants;
10583 if(expunge) { // erase results of replaced engine
10584 int len = strlen(appData.results), w, b, dummy;
10585 for(i=0; i<len; i++) {
10586 Pairing(i, nPlayers, &w, &b, &dummy);
10587 if((w == changed || b == changed) && appData.results[i] == '*') {
10588 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10593 for(i=0; i<len; i++) {
10594 Pairing(i, nPlayers, &w, &b, &dummy);
10595 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10598 WriteTourneyFile(appData.results, f);
10599 fclose(f); // release lock
10602 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10604 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10605 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10606 free(participants);
10611 CheckPlayers (char *participants)
10614 char buf[MSG_SIZ], *p;
10615 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10616 while(p = strchr(participants, '\n')) {
10618 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10620 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10622 DisplayError(buf, 0);
10626 participants = p + 1;
10632 CreateTourney (char *name)
10635 if(matchMode && strcmp(name, appData.tourneyFile)) {
10636 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10638 if(name[0] == NULLCHAR) {
10639 if(appData.participants[0])
10640 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10643 f = fopen(name, "r");
10644 if(f) { // file exists
10645 ASSIGN(appData.tourneyFile, name);
10646 ParseArgsFromFile(f); // parse it
10648 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10649 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10650 DisplayError(_("Not enough participants"), 0);
10653 if(CheckPlayers(appData.participants)) return 0;
10654 ASSIGN(appData.tourneyFile, name);
10655 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10656 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10659 appData.noChessProgram = FALSE;
10660 appData.clockMode = TRUE;
10666 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10668 char buf[MSG_SIZ], *p, *q;
10669 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10670 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10671 skip = !all && group[0]; // if group requested, we start in skip mode
10672 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10673 p = names; q = buf; header = 0;
10674 while(*p && *p != '\n') *q++ = *p++;
10676 if(*p == '\n') p++;
10677 if(buf[0] == '#') {
10678 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10679 depth++; // we must be entering a new group
10680 if(all) continue; // suppress printing group headers when complete list requested
10682 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10684 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10685 if(engineList[i]) free(engineList[i]);
10686 engineList[i] = strdup(buf);
10687 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10688 if(engineMnemonic[i]) free(engineMnemonic[i]);
10689 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10691 sscanf(q + 8, "%s", buf + strlen(buf));
10694 engineMnemonic[i] = strdup(buf);
10697 engineList[i] = engineMnemonic[i] = NULL;
10701 // following implemented as macro to avoid type limitations
10702 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10705 SwapEngines (int n)
10706 { // swap settings for first engine and other engine (so far only some selected options)
10711 SWAP(chessProgram, p)
10713 SWAP(hasOwnBookUCI, h)
10714 SWAP(protocolVersion, h)
10716 SWAP(scoreIsAbsolute, h)
10721 SWAP(engOptions, p)
10722 SWAP(engInitString, p)
10723 SWAP(computerString, p)
10725 SWAP(fenOverride, p)
10727 SWAP(accumulateTC, h)
10732 GetEngineLine (char *s, int n)
10736 extern char *icsNames;
10737 if(!s || !*s) return 0;
10738 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10739 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10740 if(!mnemonic[i]) return 0;
10741 if(n == 11) return 1; // just testing if there was a match
10742 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10743 if(n == 1) SwapEngines(n);
10744 ParseArgsFromString(buf);
10745 if(n == 1) SwapEngines(n);
10746 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10747 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10748 ParseArgsFromString(buf);
10754 SetPlayer (int player, char *p)
10755 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10757 char buf[MSG_SIZ], *engineName;
10758 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10759 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10760 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10762 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10763 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10764 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10765 ParseArgsFromString(buf);
10766 } else { // no engine with this nickname is installed!
10767 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10768 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10769 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10771 DisplayError(buf, 0);
10778 char *recentEngines;
10781 RecentEngineEvent (int nr)
10784 // SwapEngines(1); // bump first to second
10785 // ReplaceEngine(&second, 1); // and load it there
10786 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10787 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10788 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10789 ReplaceEngine(&first, 0);
10790 FloatToFront(&appData.recentEngineList, command[n]);
10795 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10796 { // determine players from game number
10797 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10799 if(appData.tourneyType == 0) {
10800 roundsPerCycle = (nPlayers - 1) | 1;
10801 pairingsPerRound = nPlayers / 2;
10802 } else if(appData.tourneyType > 0) {
10803 roundsPerCycle = nPlayers - appData.tourneyType;
10804 pairingsPerRound = appData.tourneyType;
10806 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10807 gamesPerCycle = gamesPerRound * roundsPerCycle;
10808 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10809 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10810 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10811 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10812 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10813 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10815 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10816 if(appData.roundSync) *syncInterval = gamesPerRound;
10818 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10820 if(appData.tourneyType == 0) {
10821 if(curPairing == (nPlayers-1)/2 ) {
10822 *whitePlayer = curRound;
10823 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10825 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10826 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10827 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10828 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10830 } else if(appData.tourneyType > 1) {
10831 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10832 *whitePlayer = curRound + appData.tourneyType;
10833 } else if(appData.tourneyType > 0) {
10834 *whitePlayer = curPairing;
10835 *blackPlayer = curRound + appData.tourneyType;
10838 // take care of white/black alternation per round.
10839 // For cycles and games this is already taken care of by default, derived from matchGame!
10840 return curRound & 1;
10844 NextTourneyGame (int nr, int *swapColors)
10845 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10847 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10849 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10850 tf = fopen(appData.tourneyFile, "r");
10851 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10852 ParseArgsFromFile(tf); fclose(tf);
10853 InitTimeControls(); // TC might be altered from tourney file
10855 nPlayers = CountPlayers(appData.participants); // count participants
10856 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10857 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10860 p = q = appData.results;
10861 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10862 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10863 DisplayMessage(_("Waiting for other game(s)"),"");
10864 waitingForGame = TRUE;
10865 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10868 waitingForGame = FALSE;
10871 if(appData.tourneyType < 0) {
10872 if(nr>=0 && !pairingReceived) {
10874 if(pairing.pr == NoProc) {
10875 if(!appData.pairingEngine[0]) {
10876 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10879 StartChessProgram(&pairing); // starts the pairing engine
10881 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10882 SendToProgram(buf, &pairing);
10883 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10884 SendToProgram(buf, &pairing);
10885 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10887 pairingReceived = 0; // ... so we continue here
10889 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10890 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10891 matchGame = 1; roundNr = nr / syncInterval + 1;
10894 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10896 // redefine engines, engine dir, etc.
10897 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10898 if(first.pr == NoProc) {
10899 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10900 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10902 if(second.pr == NoProc) {
10904 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10905 SwapEngines(1); // and make that valid for second engine by swapping
10906 InitEngine(&second, 1);
10908 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10909 UpdateLogos(FALSE); // leave display to ModeHiglight()
10915 { // performs game initialization that does not invoke engines, and then tries to start the game
10916 int res, firstWhite, swapColors = 0;
10917 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10918 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
10920 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10921 if(strcmp(buf, currentDebugFile)) { // name has changed
10922 FILE *f = fopen(buf, "w");
10923 if(f) { // if opening the new file failed, just keep using the old one
10924 ASSIGN(currentDebugFile, buf);
10928 if(appData.serverFileName) {
10929 if(serverFP) fclose(serverFP);
10930 serverFP = fopen(appData.serverFileName, "w");
10931 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10932 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10936 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10937 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10938 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10939 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10940 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10941 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10942 Reset(FALSE, first.pr != NoProc);
10943 res = LoadGameOrPosition(matchGame); // setup game
10944 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10945 if(!res) return; // abort when bad game/pos file
10946 TwoMachinesEvent();
10950 UserAdjudicationEvent (int result)
10952 ChessMove gameResult = GameIsDrawn;
10955 gameResult = WhiteWins;
10957 else if( result < 0 ) {
10958 gameResult = BlackWins;
10961 if( gameMode == TwoMachinesPlay ) {
10962 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10967 // [HGM] save: calculate checksum of game to make games easily identifiable
10969 StringCheckSum (char *s)
10972 if(s==NULL) return 0;
10973 while(*s) i = i*259 + *s++;
10981 for(i=backwardMostMove; i<forwardMostMove; i++) {
10982 sum += pvInfoList[i].depth;
10983 sum += StringCheckSum(parseList[i]);
10984 sum += StringCheckSum(commentList[i]);
10987 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10988 return sum + StringCheckSum(commentList[i]);
10989 } // end of save patch
10992 GameEnds (ChessMove result, char *resultDetails, int whosays)
10994 GameMode nextGameMode;
10996 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10998 if(endingGame) return; /* [HGM] crash: forbid recursion */
11000 if(twoBoards) { // [HGM] dual: switch back to one board
11001 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11002 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11004 if (appData.debugMode) {
11005 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11006 result, resultDetails ? resultDetails : "(null)", whosays);
11009 fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11011 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11013 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11014 /* If we are playing on ICS, the server decides when the
11015 game is over, but the engine can offer to draw, claim
11019 if (appData.zippyPlay && first.initDone) {
11020 if (result == GameIsDrawn) {
11021 /* In case draw still needs to be claimed */
11022 SendToICS(ics_prefix);
11023 SendToICS("draw\n");
11024 } else if (StrCaseStr(resultDetails, "resign")) {
11025 SendToICS(ics_prefix);
11026 SendToICS("resign\n");
11030 endingGame = 0; /* [HGM] crash */
11034 /* If we're loading the game from a file, stop */
11035 if (whosays == GE_FILE) {
11036 (void) StopLoadGameTimer();
11040 /* Cancel draw offers */
11041 first.offeredDraw = second.offeredDraw = 0;
11043 /* If this is an ICS game, only ICS can really say it's done;
11044 if not, anyone can. */
11045 isIcsGame = (gameMode == IcsPlayingWhite ||
11046 gameMode == IcsPlayingBlack ||
11047 gameMode == IcsObserving ||
11048 gameMode == IcsExamining);
11050 if (!isIcsGame || whosays == GE_ICS) {
11051 /* OK -- not an ICS game, or ICS said it was done */
11053 if (!isIcsGame && !appData.noChessProgram)
11054 SetUserThinkingEnables();
11056 /* [HGM] if a machine claims the game end we verify this claim */
11057 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11058 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11060 ChessMove trueResult = (ChessMove) -1;
11062 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11063 first.twoMachinesColor[0] :
11064 second.twoMachinesColor[0] ;
11066 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11067 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11068 /* [HGM] verify: engine mate claims accepted if they were flagged */
11069 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11071 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11072 /* [HGM] verify: engine mate claims accepted if they were flagged */
11073 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11075 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11076 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11079 // now verify win claims, but not in drop games, as we don't understand those yet
11080 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11081 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11082 (result == WhiteWins && claimer == 'w' ||
11083 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11084 if (appData.debugMode) {
11085 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11086 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11088 if(result != trueResult) {
11089 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11090 result = claimer == 'w' ? BlackWins : WhiteWins;
11091 resultDetails = buf;
11094 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11095 && (forwardMostMove <= backwardMostMove ||
11096 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11097 (claimer=='b')==(forwardMostMove&1))
11099 /* [HGM] verify: draws that were not flagged are false claims */
11100 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11101 result = claimer == 'w' ? BlackWins : WhiteWins;
11102 resultDetails = buf;
11104 /* (Claiming a loss is accepted no questions asked!) */
11105 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11106 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11107 result = GameUnfinished;
11108 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11110 /* [HGM] bare: don't allow bare King to win */
11111 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11112 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11113 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11114 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11115 && result != GameIsDrawn)
11116 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11117 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11118 int p = (signed char)boards[forwardMostMove][i][j] - color;
11119 if(p >= 0 && p <= (int)WhiteKing) k++;
11121 if (appData.debugMode) {
11122 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11123 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11126 result = GameIsDrawn;
11127 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11128 resultDetails = buf;
11134 if(serverMoves != NULL && !loadFlag) { char c = '=';
11135 if(result==WhiteWins) c = '+';
11136 if(result==BlackWins) c = '-';
11137 if(resultDetails != NULL)
11138 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11140 if (resultDetails != NULL) {
11141 gameInfo.result = result;
11142 gameInfo.resultDetails = StrSave(resultDetails);
11144 /* display last move only if game was not loaded from file */
11145 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11146 DisplayMove(currentMove - 1);
11148 if (forwardMostMove != 0) {
11149 if (gameMode != PlayFromGameFile && gameMode != EditGame
11150 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11152 if (*appData.saveGameFile != NULLCHAR) {
11153 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11154 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11156 SaveGameToFile(appData.saveGameFile, TRUE);
11157 } else if (appData.autoSaveGames) {
11158 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11160 if (*appData.savePositionFile != NULLCHAR) {
11161 SavePositionToFile(appData.savePositionFile);
11163 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11167 /* Tell program how game ended in case it is learning */
11168 /* [HGM] Moved this to after saving the PGN, just in case */
11169 /* engine died and we got here through time loss. In that */
11170 /* case we will get a fatal error writing the pipe, which */
11171 /* would otherwise lose us the PGN. */
11172 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11173 /* output during GameEnds should never be fatal anymore */
11174 if (gameMode == MachinePlaysWhite ||
11175 gameMode == MachinePlaysBlack ||
11176 gameMode == TwoMachinesPlay ||
11177 gameMode == IcsPlayingWhite ||
11178 gameMode == IcsPlayingBlack ||
11179 gameMode == BeginningOfGame) {
11181 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11183 if (first.pr != NoProc) {
11184 SendToProgram(buf, &first);
11186 if (second.pr != NoProc &&
11187 gameMode == TwoMachinesPlay) {
11188 SendToProgram(buf, &second);
11193 if (appData.icsActive) {
11194 if (appData.quietPlay &&
11195 (gameMode == IcsPlayingWhite ||
11196 gameMode == IcsPlayingBlack)) {
11197 SendToICS(ics_prefix);
11198 SendToICS("set shout 1\n");
11200 nextGameMode = IcsIdle;
11201 ics_user_moved = FALSE;
11202 /* clean up premove. It's ugly when the game has ended and the
11203 * premove highlights are still on the board.
11206 gotPremove = FALSE;
11207 ClearPremoveHighlights();
11208 DrawPosition(FALSE, boards[currentMove]);
11210 if (whosays == GE_ICS) {
11213 if (gameMode == IcsPlayingWhite)
11215 else if(gameMode == IcsPlayingBlack)
11216 PlayIcsLossSound();
11219 if (gameMode == IcsPlayingBlack)
11221 else if(gameMode == IcsPlayingWhite)
11222 PlayIcsLossSound();
11225 PlayIcsDrawSound();
11228 PlayIcsUnfinishedSound();
11231 if(appData.quitNext) { ExitEvent(0); return; }
11232 } else if (gameMode == EditGame ||
11233 gameMode == PlayFromGameFile ||
11234 gameMode == AnalyzeMode ||
11235 gameMode == AnalyzeFile) {
11236 nextGameMode = gameMode;
11238 nextGameMode = EndOfGame;
11243 nextGameMode = gameMode;
11246 if (appData.noChessProgram) {
11247 gameMode = nextGameMode;
11249 endingGame = 0; /* [HGM] crash */
11254 /* Put first chess program into idle state */
11255 if (first.pr != NoProc &&
11256 (gameMode == MachinePlaysWhite ||
11257 gameMode == MachinePlaysBlack ||
11258 gameMode == TwoMachinesPlay ||
11259 gameMode == IcsPlayingWhite ||
11260 gameMode == IcsPlayingBlack ||
11261 gameMode == BeginningOfGame)) {
11262 SendToProgram("force\n", &first);
11263 if (first.usePing) {
11265 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11266 SendToProgram(buf, &first);
11269 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11270 /* Kill off first chess program */
11271 if (first.isr != NULL)
11272 RemoveInputSource(first.isr);
11275 if (first.pr != NoProc) {
11277 DoSleep( appData.delayBeforeQuit );
11278 SendToProgram("quit\n", &first);
11279 DoSleep( appData.delayAfterQuit );
11280 DestroyChildProcess(first.pr, first.useSigterm);
11281 first.reload = TRUE;
11285 if (second.reuse) {
11286 /* Put second chess program into idle state */
11287 if (second.pr != NoProc &&
11288 gameMode == TwoMachinesPlay) {
11289 SendToProgram("force\n", &second);
11290 if (second.usePing) {
11292 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11293 SendToProgram(buf, &second);
11296 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11297 /* Kill off second chess program */
11298 if (second.isr != NULL)
11299 RemoveInputSource(second.isr);
11302 if (second.pr != NoProc) {
11303 DoSleep( appData.delayBeforeQuit );
11304 SendToProgram("quit\n", &second);
11305 DoSleep( appData.delayAfterQuit );
11306 DestroyChildProcess(second.pr, second.useSigterm);
11307 second.reload = TRUE;
11309 second.pr = NoProc;
11312 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11313 char resChar = '=';
11317 if (first.twoMachinesColor[0] == 'w') {
11320 second.matchWins++;
11325 if (first.twoMachinesColor[0] == 'b') {
11328 second.matchWins++;
11331 case GameUnfinished:
11337 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11338 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11339 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11340 ReserveGame(nextGame, resChar); // sets nextGame
11341 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11342 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11343 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11345 if (nextGame <= appData.matchGames && !abortMatch) {
11346 gameMode = nextGameMode;
11347 matchGame = nextGame; // this will be overruled in tourney mode!
11348 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11349 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11350 endingGame = 0; /* [HGM] crash */
11353 gameMode = nextGameMode;
11354 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11355 first.tidy, second.tidy,
11356 first.matchWins, second.matchWins,
11357 appData.matchGames - (first.matchWins + second.matchWins));
11358 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11359 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11360 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11361 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11362 first.twoMachinesColor = "black\n";
11363 second.twoMachinesColor = "white\n";
11365 first.twoMachinesColor = "white\n";
11366 second.twoMachinesColor = "black\n";
11370 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11371 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11373 gameMode = nextGameMode;
11375 endingGame = 0; /* [HGM] crash */
11376 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11377 if(matchMode == TRUE) { // match through command line: exit with or without popup
11379 ToNrEvent(forwardMostMove);
11380 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11382 } else DisplayFatalError(buf, 0, 0);
11383 } else { // match through menu; just stop, with or without popup
11384 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11387 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11388 } else DisplayNote(buf);
11390 if(ranking) free(ranking);
11394 /* Assumes program was just initialized (initString sent).
11395 Leaves program in force mode. */
11397 FeedMovesToProgram (ChessProgramState *cps, int upto)
11401 if (appData.debugMode)
11402 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11403 startedFromSetupPosition ? "position and " : "",
11404 backwardMostMove, upto, cps->which);
11405 if(currentlyInitializedVariant != gameInfo.variant) {
11407 // [HGM] variantswitch: make engine aware of new variant
11408 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11409 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11410 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11411 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11412 SendToProgram(buf, cps);
11413 currentlyInitializedVariant = gameInfo.variant;
11415 SendToProgram("force\n", cps);
11416 if (startedFromSetupPosition) {
11417 SendBoard(cps, backwardMostMove);
11418 if (appData.debugMode) {
11419 fprintf(debugFP, "feedMoves\n");
11422 for (i = backwardMostMove; i < upto; i++) {
11423 SendMoveToProgram(i, cps);
11429 ResurrectChessProgram ()
11431 /* The chess program may have exited.
11432 If so, restart it and feed it all the moves made so far. */
11433 static int doInit = 0;
11435 if (appData.noChessProgram) return 1;
11437 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11438 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11439 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11440 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11442 if (first.pr != NoProc) return 1;
11443 StartChessProgram(&first);
11445 InitChessProgram(&first, FALSE);
11446 FeedMovesToProgram(&first, currentMove);
11448 if (!first.sendTime) {
11449 /* can't tell gnuchess what its clock should read,
11450 so we bow to its notion. */
11452 timeRemaining[0][currentMove] = whiteTimeRemaining;
11453 timeRemaining[1][currentMove] = blackTimeRemaining;
11456 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11457 appData.icsEngineAnalyze) && first.analysisSupport) {
11458 SendToProgram("analyze\n", &first);
11459 first.analyzing = TRUE;
11465 * Button procedures
11468 Reset (int redraw, int init)
11472 if (appData.debugMode) {
11473 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11474 redraw, init, gameMode);
11476 CleanupTail(); // [HGM] vari: delete any stored variations
11477 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11478 pausing = pauseExamInvalid = FALSE;
11479 startedFromSetupPosition = blackPlaysFirst = FALSE;
11481 whiteFlag = blackFlag = FALSE;
11482 userOfferedDraw = FALSE;
11483 hintRequested = bookRequested = FALSE;
11484 first.maybeThinking = FALSE;
11485 second.maybeThinking = FALSE;
11486 first.bookSuspend = FALSE; // [HGM] book
11487 second.bookSuspend = FALSE;
11488 thinkOutput[0] = NULLCHAR;
11489 lastHint[0] = NULLCHAR;
11490 ClearGameInfo(&gameInfo);
11491 gameInfo.variant = StringToVariant(appData.variant);
11492 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11493 ics_user_moved = ics_clock_paused = FALSE;
11494 ics_getting_history = H_FALSE;
11496 white_holding[0] = black_holding[0] = NULLCHAR;
11497 ClearProgramStats();
11498 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11502 flipView = appData.flipView;
11503 ClearPremoveHighlights();
11504 gotPremove = FALSE;
11505 alarmSounded = FALSE;
11506 killX = killY = -1; // [HGM] lion
11508 GameEnds(EndOfFile, NULL, GE_PLAYER);
11509 if(appData.serverMovesName != NULL) {
11510 /* [HGM] prepare to make moves file for broadcasting */
11511 clock_t t = clock();
11512 if(serverMoves != NULL) fclose(serverMoves);
11513 serverMoves = fopen(appData.serverMovesName, "r");
11514 if(serverMoves != NULL) {
11515 fclose(serverMoves);
11516 /* delay 15 sec before overwriting, so all clients can see end */
11517 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11519 serverMoves = fopen(appData.serverMovesName, "w");
11523 gameMode = BeginningOfGame;
11525 if(appData.icsActive) gameInfo.variant = VariantNormal;
11526 currentMove = forwardMostMove = backwardMostMove = 0;
11527 MarkTargetSquares(1);
11528 InitPosition(redraw);
11529 for (i = 0; i < MAX_MOVES; i++) {
11530 if (commentList[i] != NULL) {
11531 free(commentList[i]);
11532 commentList[i] = NULL;
11536 timeRemaining[0][0] = whiteTimeRemaining;
11537 timeRemaining[1][0] = blackTimeRemaining;
11539 if (first.pr == NoProc) {
11540 StartChessProgram(&first);
11543 InitChessProgram(&first, startedFromSetupPosition);
11546 DisplayMessage("", "");
11547 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11548 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11549 ClearMap(); // [HGM] exclude: invalidate map
11553 AutoPlayGameLoop ()
11556 if (!AutoPlayOneMove())
11558 if (matchMode || appData.timeDelay == 0)
11560 if (appData.timeDelay < 0)
11562 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11570 ReloadGame(1); // next game
11576 int fromX, fromY, toX, toY;
11578 if (appData.debugMode) {
11579 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11582 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11585 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11586 pvInfoList[currentMove].depth = programStats.depth;
11587 pvInfoList[currentMove].score = programStats.score;
11588 pvInfoList[currentMove].time = 0;
11589 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11590 else { // append analysis of final position as comment
11592 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11593 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11595 programStats.depth = 0;
11598 if (currentMove >= forwardMostMove) {
11599 if(gameMode == AnalyzeFile) {
11600 if(appData.loadGameIndex == -1) {
11601 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11602 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11604 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11607 // gameMode = EndOfGame;
11608 // ModeHighlight();
11610 /* [AS] Clear current move marker at the end of a game */
11611 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11616 toX = moveList[currentMove][2] - AAA;
11617 toY = moveList[currentMove][3] - ONE;
11619 if (moveList[currentMove][1] == '@') {
11620 if (appData.highlightLastMove) {
11621 SetHighlights(-1, -1, toX, toY);
11624 fromX = moveList[currentMove][0] - AAA;
11625 fromY = moveList[currentMove][1] - ONE;
11627 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11629 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11631 if (appData.highlightLastMove) {
11632 SetHighlights(fromX, fromY, toX, toY);
11635 DisplayMove(currentMove);
11636 SendMoveToProgram(currentMove++, &first);
11637 DisplayBothClocks();
11638 DrawPosition(FALSE, boards[currentMove]);
11639 // [HGM] PV info: always display, routine tests if empty
11640 DisplayComment(currentMove - 1, commentList[currentMove]);
11646 LoadGameOneMove (ChessMove readAhead)
11648 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11649 char promoChar = NULLCHAR;
11650 ChessMove moveType;
11651 char move[MSG_SIZ];
11654 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11655 gameMode != AnalyzeMode && gameMode != Training) {
11660 yyboardindex = forwardMostMove;
11661 if (readAhead != EndOfFile) {
11662 moveType = readAhead;
11664 if (gameFileFP == NULL)
11666 moveType = (ChessMove) Myylex();
11670 switch (moveType) {
11672 if (appData.debugMode)
11673 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11676 /* append the comment but don't display it */
11677 AppendComment(currentMove, p, FALSE);
11680 case WhiteCapturesEnPassant:
11681 case BlackCapturesEnPassant:
11682 case WhitePromotion:
11683 case BlackPromotion:
11684 case WhiteNonPromotion:
11685 case BlackNonPromotion:
11688 case WhiteKingSideCastle:
11689 case WhiteQueenSideCastle:
11690 case BlackKingSideCastle:
11691 case BlackQueenSideCastle:
11692 case WhiteKingSideCastleWild:
11693 case WhiteQueenSideCastleWild:
11694 case BlackKingSideCastleWild:
11695 case BlackQueenSideCastleWild:
11697 case WhiteHSideCastleFR:
11698 case WhiteASideCastleFR:
11699 case BlackHSideCastleFR:
11700 case BlackASideCastleFR:
11702 if (appData.debugMode)
11703 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11704 fromX = currentMoveString[0] - AAA;
11705 fromY = currentMoveString[1] - ONE;
11706 toX = currentMoveString[2] - AAA;
11707 toY = currentMoveString[3] - ONE;
11708 promoChar = currentMoveString[4];
11709 if(promoChar == ';') promoChar = NULLCHAR;
11714 if (appData.debugMode)
11715 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11716 fromX = moveType == WhiteDrop ?
11717 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11718 (int) CharToPiece(ToLower(currentMoveString[0]));
11720 toX = currentMoveString[2] - AAA;
11721 toY = currentMoveString[3] - ONE;
11727 case GameUnfinished:
11728 if (appData.debugMode)
11729 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11730 p = strchr(yy_text, '{');
11731 if (p == NULL) p = strchr(yy_text, '(');
11734 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11736 q = strchr(p, *p == '{' ? '}' : ')');
11737 if (q != NULL) *q = NULLCHAR;
11740 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11741 GameEnds(moveType, p, GE_FILE);
11743 if (cmailMsgLoaded) {
11745 flipView = WhiteOnMove(currentMove);
11746 if (moveType == GameUnfinished) flipView = !flipView;
11747 if (appData.debugMode)
11748 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11753 if (appData.debugMode)
11754 fprintf(debugFP, "Parser hit end of file\n");
11755 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11761 if (WhiteOnMove(currentMove)) {
11762 GameEnds(BlackWins, "Black mates", GE_FILE);
11764 GameEnds(WhiteWins, "White mates", GE_FILE);
11768 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11774 case MoveNumberOne:
11775 if (lastLoadGameStart == GNUChessGame) {
11776 /* GNUChessGames have numbers, but they aren't move numbers */
11777 if (appData.debugMode)
11778 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11779 yy_text, (int) moveType);
11780 return LoadGameOneMove(EndOfFile); /* tail recursion */
11782 /* else fall thru */
11787 /* Reached start of next game in file */
11788 if (appData.debugMode)
11789 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11790 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11796 if (WhiteOnMove(currentMove)) {
11797 GameEnds(BlackWins, "Black mates", GE_FILE);
11799 GameEnds(WhiteWins, "White mates", GE_FILE);
11803 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11809 case PositionDiagram: /* should not happen; ignore */
11810 case ElapsedTime: /* ignore */
11811 case NAG: /* ignore */
11812 if (appData.debugMode)
11813 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11814 yy_text, (int) moveType);
11815 return LoadGameOneMove(EndOfFile); /* tail recursion */
11818 if (appData.testLegality) {
11819 if (appData.debugMode)
11820 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11821 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11822 (forwardMostMove / 2) + 1,
11823 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11824 DisplayError(move, 0);
11827 if (appData.debugMode)
11828 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11829 yy_text, currentMoveString);
11830 fromX = currentMoveString[0] - AAA;
11831 fromY = currentMoveString[1] - ONE;
11832 toX = currentMoveString[2] - AAA;
11833 toY = currentMoveString[3] - ONE;
11834 promoChar = currentMoveString[4];
11838 case AmbiguousMove:
11839 if (appData.debugMode)
11840 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11841 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11842 (forwardMostMove / 2) + 1,
11843 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11844 DisplayError(move, 0);
11849 case ImpossibleMove:
11850 if (appData.debugMode)
11851 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11852 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11853 (forwardMostMove / 2) + 1,
11854 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11855 DisplayError(move, 0);
11861 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11862 DrawPosition(FALSE, boards[currentMove]);
11863 DisplayBothClocks();
11864 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11865 DisplayComment(currentMove - 1, commentList[currentMove]);
11867 (void) StopLoadGameTimer();
11869 cmailOldMove = forwardMostMove;
11872 /* currentMoveString is set as a side-effect of yylex */
11874 thinkOutput[0] = NULLCHAR;
11875 MakeMove(fromX, fromY, toX, toY, promoChar);
11876 killX = killY = -1; // [HGM] lion: used up
11877 currentMove = forwardMostMove;
11882 /* Load the nth game from the given file */
11884 LoadGameFromFile (char *filename, int n, char *title, int useList)
11889 if (strcmp(filename, "-") == 0) {
11893 f = fopen(filename, "rb");
11895 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11896 DisplayError(buf, errno);
11900 if (fseek(f, 0, 0) == -1) {
11901 /* f is not seekable; probably a pipe */
11904 if (useList && n == 0) {
11905 int error = GameListBuild(f);
11907 DisplayError(_("Cannot build game list"), error);
11908 } else if (!ListEmpty(&gameList) &&
11909 ((ListGame *) gameList.tailPred)->number > 1) {
11910 GameListPopUp(f, title);
11917 return LoadGame(f, n, title, FALSE);
11922 MakeRegisteredMove ()
11924 int fromX, fromY, toX, toY;
11926 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11927 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11930 if (appData.debugMode)
11931 fprintf(debugFP, "Restoring %s for game %d\n",
11932 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11934 thinkOutput[0] = NULLCHAR;
11935 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11936 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11937 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11938 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11939 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11940 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11941 MakeMove(fromX, fromY, toX, toY, promoChar);
11942 ShowMove(fromX, fromY, toX, toY);
11944 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11951 if (WhiteOnMove(currentMove)) {
11952 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11954 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11959 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11966 if (WhiteOnMove(currentMove)) {
11967 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11969 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11974 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11985 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11987 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11991 if (gameNumber > nCmailGames) {
11992 DisplayError(_("No more games in this message"), 0);
11995 if (f == lastLoadGameFP) {
11996 int offset = gameNumber - lastLoadGameNumber;
11998 cmailMsg[0] = NULLCHAR;
11999 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12000 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12001 nCmailMovesRegistered--;
12003 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12004 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12005 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12008 if (! RegisterMove()) return FALSE;
12012 retVal = LoadGame(f, gameNumber, title, useList);
12014 /* Make move registered during previous look at this game, if any */
12015 MakeRegisteredMove();
12017 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12018 commentList[currentMove]
12019 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12020 DisplayComment(currentMove - 1, commentList[currentMove]);
12026 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12028 ReloadGame (int offset)
12030 int gameNumber = lastLoadGameNumber + offset;
12031 if (lastLoadGameFP == NULL) {
12032 DisplayError(_("No game has been loaded yet"), 0);
12035 if (gameNumber <= 0) {
12036 DisplayError(_("Can't back up any further"), 0);
12039 if (cmailMsgLoaded) {
12040 return CmailLoadGame(lastLoadGameFP, gameNumber,
12041 lastLoadGameTitle, lastLoadGameUseList);
12043 return LoadGame(lastLoadGameFP, gameNumber,
12044 lastLoadGameTitle, lastLoadGameUseList);
12048 int keys[EmptySquare+1];
12051 PositionMatches (Board b1, Board b2)
12054 switch(appData.searchMode) {
12055 case 1: return CompareWithRights(b1, b2);
12057 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12058 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12062 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12063 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12064 sum += keys[b1[r][f]] - keys[b2[r][f]];
12068 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12069 sum += keys[b1[r][f]] - keys[b2[r][f]];
12081 int pieceList[256], quickBoard[256];
12082 ChessSquare pieceType[256] = { EmptySquare };
12083 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12084 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12085 int soughtTotal, turn;
12086 Boolean epOK, flipSearch;
12089 unsigned char piece, to;
12092 #define DSIZE (250000)
12094 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12095 Move *moveDatabase = initialSpace;
12096 unsigned int movePtr, dataSize = DSIZE;
12099 MakePieceList (Board board, int *counts)
12101 int r, f, n=Q_PROMO, total=0;
12102 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12103 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12104 int sq = f + (r<<4);
12105 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12106 quickBoard[sq] = ++n;
12108 pieceType[n] = board[r][f];
12109 counts[board[r][f]]++;
12110 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12111 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12115 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12120 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12122 int sq = fromX + (fromY<<4);
12123 int piece = quickBoard[sq];
12124 quickBoard[sq] = 0;
12125 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12126 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12127 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12128 moveDatabase[movePtr++].piece = Q_WCASTL;
12129 quickBoard[sq] = piece;
12130 piece = quickBoard[from]; quickBoard[from] = 0;
12131 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12133 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12134 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12135 moveDatabase[movePtr++].piece = Q_BCASTL;
12136 quickBoard[sq] = piece;
12137 piece = quickBoard[from]; quickBoard[from] = 0;
12138 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12140 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12141 quickBoard[(fromY<<4)+toX] = 0;
12142 moveDatabase[movePtr].piece = Q_EP;
12143 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12144 moveDatabase[movePtr].to = sq;
12146 if(promoPiece != pieceType[piece]) {
12147 moveDatabase[movePtr++].piece = Q_PROMO;
12148 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12150 moveDatabase[movePtr].piece = piece;
12151 quickBoard[sq] = piece;
12156 PackGame (Board board)
12158 Move *newSpace = NULL;
12159 moveDatabase[movePtr].piece = 0; // terminate previous game
12160 if(movePtr > dataSize) {
12161 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12162 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12163 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12166 Move *p = moveDatabase, *q = newSpace;
12167 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12168 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12169 moveDatabase = newSpace;
12170 } else { // calloc failed, we must be out of memory. Too bad...
12171 dataSize = 0; // prevent calloc events for all subsequent games
12172 return 0; // and signal this one isn't cached
12176 MakePieceList(board, counts);
12181 QuickCompare (Board board, int *minCounts, int *maxCounts)
12182 { // compare according to search mode
12184 switch(appData.searchMode)
12186 case 1: // exact position match
12187 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12188 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12189 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12192 case 2: // can have extra material on empty squares
12193 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12194 if(board[r][f] == EmptySquare) continue;
12195 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12198 case 3: // material with exact Pawn structure
12199 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12200 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12201 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12202 } // fall through to material comparison
12203 case 4: // exact material
12204 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12206 case 6: // material range with given imbalance
12207 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12208 // fall through to range comparison
12209 case 5: // material range
12210 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12216 QuickScan (Board board, Move *move)
12217 { // reconstruct game,and compare all positions in it
12218 int cnt=0, stretch=0, total = MakePieceList(board, counts);
12220 int piece = move->piece;
12221 int to = move->to, from = pieceList[piece];
12222 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12223 if(!piece) return -1;
12224 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12225 piece = (++move)->piece;
12226 from = pieceList[piece];
12227 counts[pieceType[piece]]--;
12228 pieceType[piece] = (ChessSquare) move->to;
12229 counts[move->to]++;
12230 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12231 counts[pieceType[quickBoard[to]]]--;
12232 quickBoard[to] = 0; total--;
12235 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12236 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12237 from = pieceList[piece]; // so this must be King
12238 quickBoard[from] = 0;
12239 pieceList[piece] = to;
12240 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12241 quickBoard[from] = 0; // rook
12242 quickBoard[to] = piece;
12243 to = move->to; piece = move->piece;
12247 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12248 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12249 quickBoard[from] = 0;
12251 quickBoard[to] = piece;
12252 pieceList[piece] = to;
12254 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12255 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12256 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12257 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12259 static int lastCounts[EmptySquare+1];
12261 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12262 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12263 } else stretch = 0;
12264 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12273 flipSearch = FALSE;
12274 CopyBoard(soughtBoard, boards[currentMove]);
12275 soughtTotal = MakePieceList(soughtBoard, maxSought);
12276 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12277 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12278 CopyBoard(reverseBoard, boards[currentMove]);
12279 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12280 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12281 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12282 reverseBoard[r][f] = piece;
12284 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12285 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12286 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12287 || (boards[currentMove][CASTLING][2] == NoRights ||
12288 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12289 && (boards[currentMove][CASTLING][5] == NoRights ||
12290 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12293 CopyBoard(flipBoard, soughtBoard);
12294 CopyBoard(rotateBoard, reverseBoard);
12295 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12296 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12297 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12300 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12301 if(appData.searchMode >= 5) {
12302 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12303 MakePieceList(soughtBoard, minSought);
12304 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12306 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12307 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12310 GameInfo dummyInfo;
12311 static int creatingBook;
12314 GameContainsPosition (FILE *f, ListGame *lg)
12316 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12317 int fromX, fromY, toX, toY;
12319 static int initDone=FALSE;
12321 // weed out games based on numerical tag comparison
12322 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12323 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12324 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12325 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12327 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12330 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12331 else CopyBoard(boards[scratch], initialPosition); // default start position
12334 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12335 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12338 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12339 fseek(f, lg->offset, 0);
12342 yyboardindex = scratch;
12343 quickFlag = plyNr+1;
12348 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12354 if(plyNr) return -1; // after we have seen moves, this is for new game
12357 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12358 case ImpossibleMove:
12359 case WhiteWins: // game ends here with these four
12362 case GameUnfinished:
12366 if(appData.testLegality) return -1;
12367 case WhiteCapturesEnPassant:
12368 case BlackCapturesEnPassant:
12369 case WhitePromotion:
12370 case BlackPromotion:
12371 case WhiteNonPromotion:
12372 case BlackNonPromotion:
12375 case WhiteKingSideCastle:
12376 case WhiteQueenSideCastle:
12377 case BlackKingSideCastle:
12378 case BlackQueenSideCastle:
12379 case WhiteKingSideCastleWild:
12380 case WhiteQueenSideCastleWild:
12381 case BlackKingSideCastleWild:
12382 case BlackQueenSideCastleWild:
12383 case WhiteHSideCastleFR:
12384 case WhiteASideCastleFR:
12385 case BlackHSideCastleFR:
12386 case BlackASideCastleFR:
12387 fromX = currentMoveString[0] - AAA;
12388 fromY = currentMoveString[1] - ONE;
12389 toX = currentMoveString[2] - AAA;
12390 toY = currentMoveString[3] - ONE;
12391 promoChar = currentMoveString[4];
12395 fromX = next == WhiteDrop ?
12396 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12397 (int) CharToPiece(ToLower(currentMoveString[0]));
12399 toX = currentMoveString[2] - AAA;
12400 toY = currentMoveString[3] - ONE;
12404 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12406 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12407 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12408 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12409 if(appData.findMirror) {
12410 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12411 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12416 /* Load the nth game from open file f */
12418 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12422 int gn = gameNumber;
12423 ListGame *lg = NULL;
12424 int numPGNTags = 0;
12426 GameMode oldGameMode;
12427 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12429 if (appData.debugMode)
12430 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12432 if (gameMode == Training )
12433 SetTrainingModeOff();
12435 oldGameMode = gameMode;
12436 if (gameMode != BeginningOfGame) {
12437 Reset(FALSE, TRUE);
12439 killX = killY = -1; // [HGM] lion: in case we did not Reset
12442 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12443 fclose(lastLoadGameFP);
12447 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12450 fseek(f, lg->offset, 0);
12451 GameListHighlight(gameNumber);
12452 pos = lg->position;
12456 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12457 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12459 DisplayError(_("Game number out of range"), 0);
12464 if (fseek(f, 0, 0) == -1) {
12465 if (f == lastLoadGameFP ?
12466 gameNumber == lastLoadGameNumber + 1 :
12470 DisplayError(_("Can't seek on game file"), 0);
12475 lastLoadGameFP = f;
12476 lastLoadGameNumber = gameNumber;
12477 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12478 lastLoadGameUseList = useList;
12482 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12483 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12484 lg->gameInfo.black);
12486 } else if (*title != NULLCHAR) {
12487 if (gameNumber > 1) {
12488 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12491 DisplayTitle(title);
12495 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12496 gameMode = PlayFromGameFile;
12500 currentMove = forwardMostMove = backwardMostMove = 0;
12501 CopyBoard(boards[0], initialPosition);
12505 * Skip the first gn-1 games in the file.
12506 * Also skip over anything that precedes an identifiable
12507 * start of game marker, to avoid being confused by
12508 * garbage at the start of the file. Currently
12509 * recognized start of game markers are the move number "1",
12510 * the pattern "gnuchess .* game", the pattern
12511 * "^[#;%] [^ ]* game file", and a PGN tag block.
12512 * A game that starts with one of the latter two patterns
12513 * will also have a move number 1, possibly
12514 * following a position diagram.
12515 * 5-4-02: Let's try being more lenient and allowing a game to
12516 * start with an unnumbered move. Does that break anything?
12518 cm = lastLoadGameStart = EndOfFile;
12520 yyboardindex = forwardMostMove;
12521 cm = (ChessMove) Myylex();
12524 if (cmailMsgLoaded) {
12525 nCmailGames = CMAIL_MAX_GAMES - gn;
12528 DisplayError(_("Game not found in file"), 0);
12535 lastLoadGameStart = cm;
12538 case MoveNumberOne:
12539 switch (lastLoadGameStart) {
12544 case MoveNumberOne:
12546 gn--; /* count this game */
12547 lastLoadGameStart = cm;
12556 switch (lastLoadGameStart) {
12559 case MoveNumberOne:
12561 gn--; /* count this game */
12562 lastLoadGameStart = cm;
12565 lastLoadGameStart = cm; /* game counted already */
12573 yyboardindex = forwardMostMove;
12574 cm = (ChessMove) Myylex();
12575 } while (cm == PGNTag || cm == Comment);
12582 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12583 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12584 != CMAIL_OLD_RESULT) {
12586 cmailResult[ CMAIL_MAX_GAMES
12587 - gn - 1] = CMAIL_OLD_RESULT;
12594 /* Only a NormalMove can be at the start of a game
12595 * without a position diagram. */
12596 if (lastLoadGameStart == EndOfFile ) {
12598 lastLoadGameStart = MoveNumberOne;
12607 if (appData.debugMode)
12608 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12610 if (cm == XBoardGame) {
12611 /* Skip any header junk before position diagram and/or move 1 */
12613 yyboardindex = forwardMostMove;
12614 cm = (ChessMove) Myylex();
12616 if (cm == EndOfFile ||
12617 cm == GNUChessGame || cm == XBoardGame) {
12618 /* Empty game; pretend end-of-file and handle later */
12623 if (cm == MoveNumberOne || cm == PositionDiagram ||
12624 cm == PGNTag || cm == Comment)
12627 } else if (cm == GNUChessGame) {
12628 if (gameInfo.event != NULL) {
12629 free(gameInfo.event);
12631 gameInfo.event = StrSave(yy_text);
12634 startedFromSetupPosition = FALSE;
12635 while (cm == PGNTag) {
12636 if (appData.debugMode)
12637 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12638 err = ParsePGNTag(yy_text, &gameInfo);
12639 if (!err) numPGNTags++;
12641 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12642 if(gameInfo.variant != oldVariant) {
12643 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12644 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12645 InitPosition(TRUE);
12646 oldVariant = gameInfo.variant;
12647 if (appData.debugMode)
12648 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12652 if (gameInfo.fen != NULL) {
12653 Board initial_position;
12654 startedFromSetupPosition = TRUE;
12655 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12657 DisplayError(_("Bad FEN position in file"), 0);
12660 CopyBoard(boards[0], initial_position);
12661 if (blackPlaysFirst) {
12662 currentMove = forwardMostMove = backwardMostMove = 1;
12663 CopyBoard(boards[1], initial_position);
12664 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12665 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12666 timeRemaining[0][1] = whiteTimeRemaining;
12667 timeRemaining[1][1] = blackTimeRemaining;
12668 if (commentList[0] != NULL) {
12669 commentList[1] = commentList[0];
12670 commentList[0] = NULL;
12673 currentMove = forwardMostMove = backwardMostMove = 0;
12675 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12677 initialRulePlies = FENrulePlies;
12678 for( i=0; i< nrCastlingRights; i++ )
12679 initialRights[i] = initial_position[CASTLING][i];
12681 yyboardindex = forwardMostMove;
12682 free(gameInfo.fen);
12683 gameInfo.fen = NULL;
12686 yyboardindex = forwardMostMove;
12687 cm = (ChessMove) Myylex();
12689 /* Handle comments interspersed among the tags */
12690 while (cm == Comment) {
12692 if (appData.debugMode)
12693 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12695 AppendComment(currentMove, p, FALSE);
12696 yyboardindex = forwardMostMove;
12697 cm = (ChessMove) Myylex();
12701 /* don't rely on existence of Event tag since if game was
12702 * pasted from clipboard the Event tag may not exist
12704 if (numPGNTags > 0){
12706 if (gameInfo.variant == VariantNormal) {
12707 VariantClass v = StringToVariant(gameInfo.event);
12708 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12709 if(v < VariantShogi) gameInfo.variant = v;
12712 if( appData.autoDisplayTags ) {
12713 tags = PGNTags(&gameInfo);
12714 TagsPopUp(tags, CmailMsg());
12719 /* Make something up, but don't display it now */
12724 if (cm == PositionDiagram) {
12727 Board initial_position;
12729 if (appData.debugMode)
12730 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12732 if (!startedFromSetupPosition) {
12734 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12735 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12746 initial_position[i][j++] = CharToPiece(*p);
12749 while (*p == ' ' || *p == '\t' ||
12750 *p == '\n' || *p == '\r') p++;
12752 if (strncmp(p, "black", strlen("black"))==0)
12753 blackPlaysFirst = TRUE;
12755 blackPlaysFirst = FALSE;
12756 startedFromSetupPosition = TRUE;
12758 CopyBoard(boards[0], initial_position);
12759 if (blackPlaysFirst) {
12760 currentMove = forwardMostMove = backwardMostMove = 1;
12761 CopyBoard(boards[1], initial_position);
12762 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12763 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12764 timeRemaining[0][1] = whiteTimeRemaining;
12765 timeRemaining[1][1] = blackTimeRemaining;
12766 if (commentList[0] != NULL) {
12767 commentList[1] = commentList[0];
12768 commentList[0] = NULL;
12771 currentMove = forwardMostMove = backwardMostMove = 0;
12774 yyboardindex = forwardMostMove;
12775 cm = (ChessMove) Myylex();
12778 if(!creatingBook) {
12779 if (first.pr == NoProc) {
12780 StartChessProgram(&first);
12782 InitChessProgram(&first, FALSE);
12783 SendToProgram("force\n", &first);
12784 if (startedFromSetupPosition) {
12785 SendBoard(&first, forwardMostMove);
12786 if (appData.debugMode) {
12787 fprintf(debugFP, "Load Game\n");
12789 DisplayBothClocks();
12793 /* [HGM] server: flag to write setup moves in broadcast file as one */
12794 loadFlag = appData.suppressLoadMoves;
12796 while (cm == Comment) {
12798 if (appData.debugMode)
12799 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12801 AppendComment(currentMove, p, FALSE);
12802 yyboardindex = forwardMostMove;
12803 cm = (ChessMove) Myylex();
12806 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12807 cm == WhiteWins || cm == BlackWins ||
12808 cm == GameIsDrawn || cm == GameUnfinished) {
12809 DisplayMessage("", _("No moves in game"));
12810 if (cmailMsgLoaded) {
12811 if (appData.debugMode)
12812 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12816 DrawPosition(FALSE, boards[currentMove]);
12817 DisplayBothClocks();
12818 gameMode = EditGame;
12825 // [HGM] PV info: routine tests if comment empty
12826 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12827 DisplayComment(currentMove - 1, commentList[currentMove]);
12829 if (!matchMode && appData.timeDelay != 0)
12830 DrawPosition(FALSE, boards[currentMove]);
12832 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12833 programStats.ok_to_send = 1;
12836 /* if the first token after the PGN tags is a move
12837 * and not move number 1, retrieve it from the parser
12839 if (cm != MoveNumberOne)
12840 LoadGameOneMove(cm);
12842 /* load the remaining moves from the file */
12843 while (LoadGameOneMove(EndOfFile)) {
12844 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12845 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12848 /* rewind to the start of the game */
12849 currentMove = backwardMostMove;
12851 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12853 if (oldGameMode == AnalyzeFile) {
12854 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12855 AnalyzeFileEvent();
12857 if (oldGameMode == AnalyzeMode) {
12858 AnalyzeFileEvent();
12861 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12862 long int w, b; // [HGM] adjourn: restore saved clock times
12863 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12864 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12865 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12866 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12870 if(creatingBook) return TRUE;
12871 if (!matchMode && pos > 0) {
12872 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12874 if (matchMode || appData.timeDelay == 0) {
12876 } else if (appData.timeDelay > 0) {
12877 AutoPlayGameLoop();
12880 if (appData.debugMode)
12881 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12883 loadFlag = 0; /* [HGM] true game starts */
12887 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12889 ReloadPosition (int offset)
12891 int positionNumber = lastLoadPositionNumber + offset;
12892 if (lastLoadPositionFP == NULL) {
12893 DisplayError(_("No position has been loaded yet"), 0);
12896 if (positionNumber <= 0) {
12897 DisplayError(_("Can't back up any further"), 0);
12900 return LoadPosition(lastLoadPositionFP, positionNumber,
12901 lastLoadPositionTitle);
12904 /* Load the nth position from the given file */
12906 LoadPositionFromFile (char *filename, int n, char *title)
12911 if (strcmp(filename, "-") == 0) {
12912 return LoadPosition(stdin, n, "stdin");
12914 f = fopen(filename, "rb");
12916 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12917 DisplayError(buf, errno);
12920 return LoadPosition(f, n, title);
12925 /* Load the nth position from the given open file, and close it */
12927 LoadPosition (FILE *f, int positionNumber, char *title)
12929 char *p, line[MSG_SIZ];
12930 Board initial_position;
12931 int i, j, fenMode, pn;
12933 if (gameMode == Training )
12934 SetTrainingModeOff();
12936 if (gameMode != BeginningOfGame) {
12937 Reset(FALSE, TRUE);
12939 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12940 fclose(lastLoadPositionFP);
12942 if (positionNumber == 0) positionNumber = 1;
12943 lastLoadPositionFP = f;
12944 lastLoadPositionNumber = positionNumber;
12945 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12946 if (first.pr == NoProc && !appData.noChessProgram) {
12947 StartChessProgram(&first);
12948 InitChessProgram(&first, FALSE);
12950 pn = positionNumber;
12951 if (positionNumber < 0) {
12952 /* Negative position number means to seek to that byte offset */
12953 if (fseek(f, -positionNumber, 0) == -1) {
12954 DisplayError(_("Can't seek on position file"), 0);
12959 if (fseek(f, 0, 0) == -1) {
12960 if (f == lastLoadPositionFP ?
12961 positionNumber == lastLoadPositionNumber + 1 :
12962 positionNumber == 1) {
12965 DisplayError(_("Can't seek on position file"), 0);
12970 /* See if this file is FEN or old-style xboard */
12971 if (fgets(line, MSG_SIZ, f) == NULL) {
12972 DisplayError(_("Position not found in file"), 0);
12975 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12976 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12979 if (fenMode || line[0] == '#') pn--;
12981 /* skip positions before number pn */
12982 if (fgets(line, MSG_SIZ, f) == NULL) {
12984 DisplayError(_("Position not found in file"), 0);
12987 if (fenMode || line[0] == '#') pn--;
12992 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12993 DisplayError(_("Bad FEN position in file"), 0);
12997 (void) fgets(line, MSG_SIZ, f);
12998 (void) fgets(line, MSG_SIZ, f);
13000 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13001 (void) fgets(line, MSG_SIZ, f);
13002 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13005 initial_position[i][j++] = CharToPiece(*p);
13009 blackPlaysFirst = FALSE;
13011 (void) fgets(line, MSG_SIZ, f);
13012 if (strncmp(line, "black", strlen("black"))==0)
13013 blackPlaysFirst = TRUE;
13016 startedFromSetupPosition = TRUE;
13018 CopyBoard(boards[0], initial_position);
13019 if (blackPlaysFirst) {
13020 currentMove = forwardMostMove = backwardMostMove = 1;
13021 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13022 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13023 CopyBoard(boards[1], initial_position);
13024 DisplayMessage("", _("Black to play"));
13026 currentMove = forwardMostMove = backwardMostMove = 0;
13027 DisplayMessage("", _("White to play"));
13029 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13030 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13031 SendToProgram("force\n", &first);
13032 SendBoard(&first, forwardMostMove);
13034 if (appData.debugMode) {
13036 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13037 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13038 fprintf(debugFP, "Load Position\n");
13041 if (positionNumber > 1) {
13042 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13043 DisplayTitle(line);
13045 DisplayTitle(title);
13047 gameMode = EditGame;
13050 timeRemaining[0][1] = whiteTimeRemaining;
13051 timeRemaining[1][1] = blackTimeRemaining;
13052 DrawPosition(FALSE, boards[currentMove]);
13059 CopyPlayerNameIntoFileName (char **dest, char *src)
13061 while (*src != NULLCHAR && *src != ',') {
13066 *(*dest)++ = *src++;
13072 DefaultFileName (char *ext)
13074 static char def[MSG_SIZ];
13077 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13079 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13081 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13083 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13090 /* Save the current game to the given file */
13092 SaveGameToFile (char *filename, int append)
13096 int result, i, t,tot=0;
13098 if (strcmp(filename, "-") == 0) {
13099 return SaveGame(stdout, 0, NULL);
13101 for(i=0; i<10; i++) { // upto 10 tries
13102 f = fopen(filename, append ? "a" : "w");
13103 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13104 if(f || errno != 13) break;
13105 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13109 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13110 DisplayError(buf, errno);
13113 safeStrCpy(buf, lastMsg, MSG_SIZ);
13114 DisplayMessage(_("Waiting for access to save file"), "");
13115 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13116 DisplayMessage(_("Saving game"), "");
13117 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13118 result = SaveGame(f, 0, NULL);
13119 DisplayMessage(buf, "");
13126 SavePart (char *str)
13128 static char buf[MSG_SIZ];
13131 p = strchr(str, ' ');
13132 if (p == NULL) return str;
13133 strncpy(buf, str, p - str);
13134 buf[p - str] = NULLCHAR;
13138 #define PGN_MAX_LINE 75
13140 #define PGN_SIDE_WHITE 0
13141 #define PGN_SIDE_BLACK 1
13144 FindFirstMoveOutOfBook (int side)
13148 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13149 int index = backwardMostMove;
13150 int has_book_hit = 0;
13152 if( (index % 2) != side ) {
13156 while( index < forwardMostMove ) {
13157 /* Check to see if engine is in book */
13158 int depth = pvInfoList[index].depth;
13159 int score = pvInfoList[index].score;
13165 else if( score == 0 && depth == 63 ) {
13166 in_book = 1; /* Zappa */
13168 else if( score == 2 && depth == 99 ) {
13169 in_book = 1; /* Abrok */
13172 has_book_hit += in_book;
13188 GetOutOfBookInfo (char * buf)
13192 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13194 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13195 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13199 if( oob[0] >= 0 || oob[1] >= 0 ) {
13200 for( i=0; i<2; i++ ) {
13204 if( i > 0 && oob[0] >= 0 ) {
13205 strcat( buf, " " );
13208 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13209 sprintf( buf+strlen(buf), "%s%.2f",
13210 pvInfoList[idx].score >= 0 ? "+" : "",
13211 pvInfoList[idx].score / 100.0 );
13217 /* Save game in PGN style and close the file */
13219 SaveGamePGN (FILE *f)
13221 int i, offset, linelen, newblock;
13224 int movelen, numlen, blank;
13225 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13227 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13229 PrintPGNTags(f, &gameInfo);
13231 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13233 if (backwardMostMove > 0 || startedFromSetupPosition) {
13234 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13235 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13236 fprintf(f, "\n{--------------\n");
13237 PrintPosition(f, backwardMostMove);
13238 fprintf(f, "--------------}\n");
13242 /* [AS] Out of book annotation */
13243 if( appData.saveOutOfBookInfo ) {
13246 GetOutOfBookInfo( buf );
13248 if( buf[0] != '\0' ) {
13249 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13256 i = backwardMostMove;
13260 while (i < forwardMostMove) {
13261 /* Print comments preceding this move */
13262 if (commentList[i] != NULL) {
13263 if (linelen > 0) fprintf(f, "\n");
13264 fprintf(f, "%s", commentList[i]);
13269 /* Format move number */
13271 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13274 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13276 numtext[0] = NULLCHAR;
13278 numlen = strlen(numtext);
13281 /* Print move number */
13282 blank = linelen > 0 && numlen > 0;
13283 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13292 fprintf(f, "%s", numtext);
13296 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13297 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13300 blank = linelen > 0 && movelen > 0;
13301 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13310 fprintf(f, "%s", move_buffer);
13311 linelen += movelen;
13313 /* [AS] Add PV info if present */
13314 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13315 /* [HGM] add time */
13316 char buf[MSG_SIZ]; int seconds;
13318 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13324 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13327 seconds = (seconds + 4)/10; // round to full seconds
13329 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13331 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13334 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13335 pvInfoList[i].score >= 0 ? "+" : "",
13336 pvInfoList[i].score / 100.0,
13337 pvInfoList[i].depth,
13340 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13342 /* Print score/depth */
13343 blank = linelen > 0 && movelen > 0;
13344 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13353 fprintf(f, "%s", move_buffer);
13354 linelen += movelen;
13360 /* Start a new line */
13361 if (linelen > 0) fprintf(f, "\n");
13363 /* Print comments after last move */
13364 if (commentList[i] != NULL) {
13365 fprintf(f, "%s\n", commentList[i]);
13369 if (gameInfo.resultDetails != NULL &&
13370 gameInfo.resultDetails[0] != NULLCHAR) {
13371 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13372 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13373 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13374 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13375 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13377 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13381 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13385 /* Save game in old style and close the file */
13387 SaveGameOldStyle (FILE *f)
13392 tm = time((time_t *) NULL);
13394 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13397 if (backwardMostMove > 0 || startedFromSetupPosition) {
13398 fprintf(f, "\n[--------------\n");
13399 PrintPosition(f, backwardMostMove);
13400 fprintf(f, "--------------]\n");
13405 i = backwardMostMove;
13406 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13408 while (i < forwardMostMove) {
13409 if (commentList[i] != NULL) {
13410 fprintf(f, "[%s]\n", commentList[i]);
13413 if ((i % 2) == 1) {
13414 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13417 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13419 if (commentList[i] != NULL) {
13423 if (i >= forwardMostMove) {
13427 fprintf(f, "%s\n", parseList[i]);
13432 if (commentList[i] != NULL) {
13433 fprintf(f, "[%s]\n", commentList[i]);
13436 /* This isn't really the old style, but it's close enough */
13437 if (gameInfo.resultDetails != NULL &&
13438 gameInfo.resultDetails[0] != NULLCHAR) {
13439 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13440 gameInfo.resultDetails);
13442 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13449 /* Save the current game to open file f and close the file */
13451 SaveGame (FILE *f, int dummy, char *dummy2)
13453 if (gameMode == EditPosition) EditPositionDone(TRUE);
13454 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13455 if (appData.oldSaveStyle)
13456 return SaveGameOldStyle(f);
13458 return SaveGamePGN(f);
13461 /* Save the current position to the given file */
13463 SavePositionToFile (char *filename)
13468 if (strcmp(filename, "-") == 0) {
13469 return SavePosition(stdout, 0, NULL);
13471 f = fopen(filename, "a");
13473 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13474 DisplayError(buf, errno);
13477 safeStrCpy(buf, lastMsg, MSG_SIZ);
13478 DisplayMessage(_("Waiting for access to save file"), "");
13479 flock(fileno(f), LOCK_EX); // [HGM] lock
13480 DisplayMessage(_("Saving position"), "");
13481 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13482 SavePosition(f, 0, NULL);
13483 DisplayMessage(buf, "");
13489 /* Save the current position to the given open file and close the file */
13491 SavePosition (FILE *f, int dummy, char *dummy2)
13496 if (gameMode == EditPosition) EditPositionDone(TRUE);
13497 if (appData.oldSaveStyle) {
13498 tm = time((time_t *) NULL);
13500 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13502 fprintf(f, "[--------------\n");
13503 PrintPosition(f, currentMove);
13504 fprintf(f, "--------------]\n");
13506 fen = PositionToFEN(currentMove, NULL, 1);
13507 fprintf(f, "%s\n", fen);
13515 ReloadCmailMsgEvent (int unregister)
13518 static char *inFilename = NULL;
13519 static char *outFilename;
13521 struct stat inbuf, outbuf;
13524 /* Any registered moves are unregistered if unregister is set, */
13525 /* i.e. invoked by the signal handler */
13527 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13528 cmailMoveRegistered[i] = FALSE;
13529 if (cmailCommentList[i] != NULL) {
13530 free(cmailCommentList[i]);
13531 cmailCommentList[i] = NULL;
13534 nCmailMovesRegistered = 0;
13537 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13538 cmailResult[i] = CMAIL_NOT_RESULT;
13542 if (inFilename == NULL) {
13543 /* Because the filenames are static they only get malloced once */
13544 /* and they never get freed */
13545 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13546 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13548 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13549 sprintf(outFilename, "%s.out", appData.cmailGameName);
13552 status = stat(outFilename, &outbuf);
13554 cmailMailedMove = FALSE;
13556 status = stat(inFilename, &inbuf);
13557 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13560 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13561 counts the games, notes how each one terminated, etc.
13563 It would be nice to remove this kludge and instead gather all
13564 the information while building the game list. (And to keep it
13565 in the game list nodes instead of having a bunch of fixed-size
13566 parallel arrays.) Note this will require getting each game's
13567 termination from the PGN tags, as the game list builder does
13568 not process the game moves. --mann
13570 cmailMsgLoaded = TRUE;
13571 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13573 /* Load first game in the file or popup game menu */
13574 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13576 #endif /* !WIN32 */
13584 char string[MSG_SIZ];
13586 if ( cmailMailedMove
13587 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13588 return TRUE; /* Allow free viewing */
13591 /* Unregister move to ensure that we don't leave RegisterMove */
13592 /* with the move registered when the conditions for registering no */
13594 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13595 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13596 nCmailMovesRegistered --;
13598 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13600 free(cmailCommentList[lastLoadGameNumber - 1]);
13601 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13605 if (cmailOldMove == -1) {
13606 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13610 if (currentMove > cmailOldMove + 1) {
13611 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13615 if (currentMove < cmailOldMove) {
13616 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13620 if (forwardMostMove > currentMove) {
13621 /* Silently truncate extra moves */
13625 if ( (currentMove == cmailOldMove + 1)
13626 || ( (currentMove == cmailOldMove)
13627 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13628 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13629 if (gameInfo.result != GameUnfinished) {
13630 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13633 if (commentList[currentMove] != NULL) {
13634 cmailCommentList[lastLoadGameNumber - 1]
13635 = StrSave(commentList[currentMove]);
13637 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13639 if (appData.debugMode)
13640 fprintf(debugFP, "Saving %s for game %d\n",
13641 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13643 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13645 f = fopen(string, "w");
13646 if (appData.oldSaveStyle) {
13647 SaveGameOldStyle(f); /* also closes the file */
13649 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13650 f = fopen(string, "w");
13651 SavePosition(f, 0, NULL); /* also closes the file */
13653 fprintf(f, "{--------------\n");
13654 PrintPosition(f, currentMove);
13655 fprintf(f, "--------------}\n\n");
13657 SaveGame(f, 0, NULL); /* also closes the file*/
13660 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13661 nCmailMovesRegistered ++;
13662 } else if (nCmailGames == 1) {
13663 DisplayError(_("You have not made a move yet"), 0);
13674 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13675 FILE *commandOutput;
13676 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13677 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13683 if (! cmailMsgLoaded) {
13684 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13688 if (nCmailGames == nCmailResults) {
13689 DisplayError(_("No unfinished games"), 0);
13693 #if CMAIL_PROHIBIT_REMAIL
13694 if (cmailMailedMove) {
13695 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);
13696 DisplayError(msg, 0);
13701 if (! (cmailMailedMove || RegisterMove())) return;
13703 if ( cmailMailedMove
13704 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13705 snprintf(string, MSG_SIZ, partCommandString,
13706 appData.debugMode ? " -v" : "", appData.cmailGameName);
13707 commandOutput = popen(string, "r");
13709 if (commandOutput == NULL) {
13710 DisplayError(_("Failed to invoke cmail"), 0);
13712 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13713 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13715 if (nBuffers > 1) {
13716 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13717 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13718 nBytes = MSG_SIZ - 1;
13720 (void) memcpy(msg, buffer, nBytes);
13722 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13724 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13725 cmailMailedMove = TRUE; /* Prevent >1 moves */
13728 for (i = 0; i < nCmailGames; i ++) {
13729 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13734 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13736 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13738 appData.cmailGameName,
13740 LoadGameFromFile(buffer, 1, buffer, FALSE);
13741 cmailMsgLoaded = FALSE;
13745 DisplayInformation(msg);
13746 pclose(commandOutput);
13749 if ((*cmailMsg) != '\0') {
13750 DisplayInformation(cmailMsg);
13755 #endif /* !WIN32 */
13764 int prependComma = 0;
13766 char string[MSG_SIZ]; /* Space for game-list */
13769 if (!cmailMsgLoaded) return "";
13771 if (cmailMailedMove) {
13772 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13774 /* Create a list of games left */
13775 snprintf(string, MSG_SIZ, "[");
13776 for (i = 0; i < nCmailGames; i ++) {
13777 if (! ( cmailMoveRegistered[i]
13778 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13779 if (prependComma) {
13780 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13782 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13786 strcat(string, number);
13789 strcat(string, "]");
13791 if (nCmailMovesRegistered + nCmailResults == 0) {
13792 switch (nCmailGames) {
13794 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13798 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13802 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13807 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13809 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13814 if (nCmailResults == nCmailGames) {
13815 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13817 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13822 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13834 if (gameMode == Training)
13835 SetTrainingModeOff();
13838 cmailMsgLoaded = FALSE;
13839 if (appData.icsActive) {
13840 SendToICS(ics_prefix);
13841 SendToICS("refresh\n");
13846 ExitEvent (int status)
13850 /* Give up on clean exit */
13854 /* Keep trying for clean exit */
13858 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13860 if (telnetISR != NULL) {
13861 RemoveInputSource(telnetISR);
13863 if (icsPR != NoProc) {
13864 DestroyChildProcess(icsPR, TRUE);
13867 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13868 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13870 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13871 /* make sure this other one finishes before killing it! */
13872 if(endingGame) { int count = 0;
13873 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13874 while(endingGame && count++ < 10) DoSleep(1);
13875 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13878 /* Kill off chess programs */
13879 if (first.pr != NoProc) {
13882 DoSleep( appData.delayBeforeQuit );
13883 SendToProgram("quit\n", &first);
13884 DoSleep( appData.delayAfterQuit );
13885 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13887 if (second.pr != NoProc) {
13888 DoSleep( appData.delayBeforeQuit );
13889 SendToProgram("quit\n", &second);
13890 DoSleep( appData.delayAfterQuit );
13891 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13893 if (first.isr != NULL) {
13894 RemoveInputSource(first.isr);
13896 if (second.isr != NULL) {
13897 RemoveInputSource(second.isr);
13900 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13901 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13903 ShutDownFrontEnd();
13908 PauseEngine (ChessProgramState *cps)
13910 SendToProgram("pause\n", cps);
13915 UnPauseEngine (ChessProgramState *cps)
13917 SendToProgram("resume\n", cps);
13924 if (appData.debugMode)
13925 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13929 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13931 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13932 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13933 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13935 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13936 HandleMachineMove(stashedInputMove, stalledEngine);
13937 stalledEngine = NULL;
13940 if (gameMode == MachinePlaysWhite ||
13941 gameMode == TwoMachinesPlay ||
13942 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13943 if(first.pause) UnPauseEngine(&first);
13944 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13945 if(second.pause) UnPauseEngine(&second);
13946 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13949 DisplayBothClocks();
13951 if (gameMode == PlayFromGameFile) {
13952 if (appData.timeDelay >= 0)
13953 AutoPlayGameLoop();
13954 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13955 Reset(FALSE, TRUE);
13956 SendToICS(ics_prefix);
13957 SendToICS("refresh\n");
13958 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13959 ForwardInner(forwardMostMove);
13961 pauseExamInvalid = FALSE;
13963 switch (gameMode) {
13967 pauseExamForwardMostMove = forwardMostMove;
13968 pauseExamInvalid = FALSE;
13971 case IcsPlayingWhite:
13972 case IcsPlayingBlack:
13976 case PlayFromGameFile:
13977 (void) StopLoadGameTimer();
13981 case BeginningOfGame:
13982 if (appData.icsActive) return;
13983 /* else fall through */
13984 case MachinePlaysWhite:
13985 case MachinePlaysBlack:
13986 case TwoMachinesPlay:
13987 if (forwardMostMove == 0)
13988 return; /* don't pause if no one has moved */
13989 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13990 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13991 if(onMove->pause) { // thinking engine can be paused
13992 PauseEngine(onMove); // do it
13993 if(onMove->other->pause) // pondering opponent can always be paused immediately
13994 PauseEngine(onMove->other);
13996 SendToProgram("easy\n", onMove->other);
13998 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13999 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14001 PauseEngine(&first);
14003 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14004 } else { // human on move, pause pondering by either method
14006 PauseEngine(&first);
14007 else if(appData.ponderNextMove)
14008 SendToProgram("easy\n", &first);
14011 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14021 EditCommentEvent ()
14023 char title[MSG_SIZ];
14025 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14026 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14028 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14029 WhiteOnMove(currentMove - 1) ? " " : ".. ",
14030 parseList[currentMove - 1]);
14033 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14040 char *tags = PGNTags(&gameInfo);
14042 EditTagsPopUp(tags, NULL);
14049 if(second.analyzing) {
14050 SendToProgram("exit\n", &second);
14051 second.analyzing = FALSE;
14053 if (second.pr == NoProc) StartChessProgram(&second);
14054 InitChessProgram(&second, FALSE);
14055 FeedMovesToProgram(&second, currentMove);
14057 SendToProgram("analyze\n", &second);
14058 second.analyzing = TRUE;
14062 /* Toggle ShowThinking */
14064 ToggleShowThinking()
14066 appData.showThinking = !appData.showThinking;
14067 ShowThinkingEvent();
14071 AnalyzeModeEvent ()
14075 if (!first.analysisSupport) {
14076 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14077 DisplayError(buf, 0);
14080 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14081 if (appData.icsActive) {
14082 if (gameMode != IcsObserving) {
14083 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14084 DisplayError(buf, 0);
14086 if (appData.icsEngineAnalyze) {
14087 if (appData.debugMode)
14088 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14094 /* if enable, user wants to disable icsEngineAnalyze */
14095 if (appData.icsEngineAnalyze) {
14100 appData.icsEngineAnalyze = TRUE;
14101 if (appData.debugMode)
14102 fprintf(debugFP, "ICS engine analyze starting... \n");
14105 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14106 if (appData.noChessProgram || gameMode == AnalyzeMode)
14109 if (gameMode != AnalyzeFile) {
14110 if (!appData.icsEngineAnalyze) {
14112 if (gameMode != EditGame) return 0;
14114 if (!appData.showThinking) ToggleShowThinking();
14115 ResurrectChessProgram();
14116 SendToProgram("analyze\n", &first);
14117 first.analyzing = TRUE;
14118 /*first.maybeThinking = TRUE;*/
14119 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14120 EngineOutputPopUp();
14122 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14127 StartAnalysisClock();
14128 GetTimeMark(&lastNodeCountTime);
14134 AnalyzeFileEvent ()
14136 if (appData.noChessProgram || gameMode == AnalyzeFile)
14139 if (!first.analysisSupport) {
14141 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14142 DisplayError(buf, 0);
14146 if (gameMode != AnalyzeMode) {
14147 keepInfo = 1; // mere annotating should not alter PGN tags
14150 if (gameMode != EditGame) return;
14151 if (!appData.showThinking) ToggleShowThinking();
14152 ResurrectChessProgram();
14153 SendToProgram("analyze\n", &first);
14154 first.analyzing = TRUE;
14155 /*first.maybeThinking = TRUE;*/
14156 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14157 EngineOutputPopUp();
14159 gameMode = AnalyzeFile;
14163 StartAnalysisClock();
14164 GetTimeMark(&lastNodeCountTime);
14166 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14167 AnalysisPeriodicEvent(1);
14171 MachineWhiteEvent ()
14174 char *bookHit = NULL;
14176 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14180 if (gameMode == PlayFromGameFile ||
14181 gameMode == TwoMachinesPlay ||
14182 gameMode == Training ||
14183 gameMode == AnalyzeMode ||
14184 gameMode == EndOfGame)
14187 if (gameMode == EditPosition)
14188 EditPositionDone(TRUE);
14190 if (!WhiteOnMove(currentMove)) {
14191 DisplayError(_("It is not White's turn"), 0);
14195 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14198 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14199 gameMode == AnalyzeFile)
14202 ResurrectChessProgram(); /* in case it isn't running */
14203 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14204 gameMode = MachinePlaysWhite;
14207 gameMode = MachinePlaysWhite;
14211 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14213 if (first.sendName) {
14214 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14215 SendToProgram(buf, &first);
14217 if (first.sendTime) {
14218 if (first.useColors) {
14219 SendToProgram("black\n", &first); /*gnu kludge*/
14221 SendTimeRemaining(&first, TRUE);
14223 if (first.useColors) {
14224 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14226 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14227 SetMachineThinkingEnables();
14228 first.maybeThinking = TRUE;
14232 if (appData.autoFlipView && !flipView) {
14233 flipView = !flipView;
14234 DrawPosition(FALSE, NULL);
14235 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14238 if(bookHit) { // [HGM] book: simulate book reply
14239 static char bookMove[MSG_SIZ]; // a bit generous?
14241 programStats.nodes = programStats.depth = programStats.time =
14242 programStats.score = programStats.got_only_move = 0;
14243 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14245 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14246 strcat(bookMove, bookHit);
14247 HandleMachineMove(bookMove, &first);
14252 MachineBlackEvent ()
14255 char *bookHit = NULL;
14257 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14261 if (gameMode == PlayFromGameFile ||
14262 gameMode == TwoMachinesPlay ||
14263 gameMode == Training ||
14264 gameMode == AnalyzeMode ||
14265 gameMode == EndOfGame)
14268 if (gameMode == EditPosition)
14269 EditPositionDone(TRUE);
14271 if (WhiteOnMove(currentMove)) {
14272 DisplayError(_("It is not Black's turn"), 0);
14276 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14279 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14280 gameMode == AnalyzeFile)
14283 ResurrectChessProgram(); /* in case it isn't running */
14284 gameMode = MachinePlaysBlack;
14288 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14290 if (first.sendName) {
14291 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14292 SendToProgram(buf, &first);
14294 if (first.sendTime) {
14295 if (first.useColors) {
14296 SendToProgram("white\n", &first); /*gnu kludge*/
14298 SendTimeRemaining(&first, FALSE);
14300 if (first.useColors) {
14301 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14303 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14304 SetMachineThinkingEnables();
14305 first.maybeThinking = TRUE;
14308 if (appData.autoFlipView && flipView) {
14309 flipView = !flipView;
14310 DrawPosition(FALSE, NULL);
14311 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14313 if(bookHit) { // [HGM] book: simulate book reply
14314 static char bookMove[MSG_SIZ]; // a bit generous?
14316 programStats.nodes = programStats.depth = programStats.time =
14317 programStats.score = programStats.got_only_move = 0;
14318 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14320 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14321 strcat(bookMove, bookHit);
14322 HandleMachineMove(bookMove, &first);
14328 DisplayTwoMachinesTitle ()
14331 if (appData.matchGames > 0) {
14332 if(appData.tourneyFile[0]) {
14333 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14334 gameInfo.white, _("vs."), gameInfo.black,
14335 nextGame+1, appData.matchGames+1,
14336 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14338 if (first.twoMachinesColor[0] == 'w') {
14339 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14340 gameInfo.white, _("vs."), gameInfo.black,
14341 first.matchWins, second.matchWins,
14342 matchGame - 1 - (first.matchWins + second.matchWins));
14344 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14345 gameInfo.white, _("vs."), gameInfo.black,
14346 second.matchWins, first.matchWins,
14347 matchGame - 1 - (first.matchWins + second.matchWins));
14350 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14356 SettingsMenuIfReady ()
14358 if (second.lastPing != second.lastPong) {
14359 DisplayMessage("", _("Waiting for second chess program"));
14360 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14364 DisplayMessage("", "");
14365 SettingsPopUp(&second);
14369 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14372 if (cps->pr == NoProc) {
14373 StartChessProgram(cps);
14374 if (cps->protocolVersion == 1) {
14376 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14378 /* kludge: allow timeout for initial "feature" command */
14379 if(retry != TwoMachinesEventIfReady) FreezeUI();
14380 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14381 DisplayMessage("", buf);
14382 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14390 TwoMachinesEvent P((void))
14394 ChessProgramState *onmove;
14395 char *bookHit = NULL;
14396 static int stalling = 0;
14400 if (appData.noChessProgram) return;
14402 switch (gameMode) {
14403 case TwoMachinesPlay:
14405 case MachinePlaysWhite:
14406 case MachinePlaysBlack:
14407 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14408 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14412 case BeginningOfGame:
14413 case PlayFromGameFile:
14416 if (gameMode != EditGame) return;
14419 EditPositionDone(TRUE);
14430 // forwardMostMove = currentMove;
14431 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14432 startingEngine = TRUE;
14434 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14436 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14437 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14438 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14441 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14443 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14444 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14445 startingEngine = FALSE;
14446 DisplayError("second engine does not play this", 0);
14451 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14452 SendToProgram("force\n", &second);
14454 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14457 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14458 if(appData.matchPause>10000 || appData.matchPause<10)
14459 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14460 wait = SubtractTimeMarks(&now, &pauseStart);
14461 if(wait < appData.matchPause) {
14462 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14465 // we are now committed to starting the game
14467 DisplayMessage("", "");
14468 if (startedFromSetupPosition) {
14469 SendBoard(&second, backwardMostMove);
14470 if (appData.debugMode) {
14471 fprintf(debugFP, "Two Machines\n");
14474 for (i = backwardMostMove; i < forwardMostMove; i++) {
14475 SendMoveToProgram(i, &second);
14478 gameMode = TwoMachinesPlay;
14479 pausing = startingEngine = FALSE;
14480 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14482 DisplayTwoMachinesTitle();
14484 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14489 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14490 SendToProgram(first.computerString, &first);
14491 if (first.sendName) {
14492 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14493 SendToProgram(buf, &first);
14495 SendToProgram(second.computerString, &second);
14496 if (second.sendName) {
14497 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14498 SendToProgram(buf, &second);
14502 if (!first.sendTime || !second.sendTime) {
14503 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14504 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14506 if (onmove->sendTime) {
14507 if (onmove->useColors) {
14508 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14510 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14512 if (onmove->useColors) {
14513 SendToProgram(onmove->twoMachinesColor, onmove);
14515 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14516 // SendToProgram("go\n", onmove);
14517 onmove->maybeThinking = TRUE;
14518 SetMachineThinkingEnables();
14522 if(bookHit) { // [HGM] book: simulate book reply
14523 static char bookMove[MSG_SIZ]; // a bit generous?
14525 programStats.nodes = programStats.depth = programStats.time =
14526 programStats.score = programStats.got_only_move = 0;
14527 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14529 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14530 strcat(bookMove, bookHit);
14531 savedMessage = bookMove; // args for deferred call
14532 savedState = onmove;
14533 ScheduleDelayedEvent(DeferredBookMove, 1);
14540 if (gameMode == Training) {
14541 SetTrainingModeOff();
14542 gameMode = PlayFromGameFile;
14543 DisplayMessage("", _("Training mode off"));
14545 gameMode = Training;
14546 animateTraining = appData.animate;
14548 /* make sure we are not already at the end of the game */
14549 if (currentMove < forwardMostMove) {
14550 SetTrainingModeOn();
14551 DisplayMessage("", _("Training mode on"));
14553 gameMode = PlayFromGameFile;
14554 DisplayError(_("Already at end of game"), 0);
14563 if (!appData.icsActive) return;
14564 switch (gameMode) {
14565 case IcsPlayingWhite:
14566 case IcsPlayingBlack:
14569 case BeginningOfGame:
14577 EditPositionDone(TRUE);
14590 gameMode = IcsIdle;
14600 switch (gameMode) {
14602 SetTrainingModeOff();
14604 case MachinePlaysWhite:
14605 case MachinePlaysBlack:
14606 case BeginningOfGame:
14607 SendToProgram("force\n", &first);
14608 SetUserThinkingEnables();
14610 case PlayFromGameFile:
14611 (void) StopLoadGameTimer();
14612 if (gameFileFP != NULL) {
14617 EditPositionDone(TRUE);
14622 SendToProgram("force\n", &first);
14624 case TwoMachinesPlay:
14625 GameEnds(EndOfFile, NULL, GE_PLAYER);
14626 ResurrectChessProgram();
14627 SetUserThinkingEnables();
14630 ResurrectChessProgram();
14632 case IcsPlayingBlack:
14633 case IcsPlayingWhite:
14634 DisplayError(_("Warning: You are still playing a game"), 0);
14637 DisplayError(_("Warning: You are still observing a game"), 0);
14640 DisplayError(_("Warning: You are still examining a game"), 0);
14651 first.offeredDraw = second.offeredDraw = 0;
14653 if (gameMode == PlayFromGameFile) {
14654 whiteTimeRemaining = timeRemaining[0][currentMove];
14655 blackTimeRemaining = timeRemaining[1][currentMove];
14659 if (gameMode == MachinePlaysWhite ||
14660 gameMode == MachinePlaysBlack ||
14661 gameMode == TwoMachinesPlay ||
14662 gameMode == EndOfGame) {
14663 i = forwardMostMove;
14664 while (i > currentMove) {
14665 SendToProgram("undo\n", &first);
14668 if(!adjustedClock) {
14669 whiteTimeRemaining = timeRemaining[0][currentMove];
14670 blackTimeRemaining = timeRemaining[1][currentMove];
14671 DisplayBothClocks();
14673 if (whiteFlag || blackFlag) {
14674 whiteFlag = blackFlag = 0;
14679 gameMode = EditGame;
14686 EditPositionEvent ()
14688 if (gameMode == EditPosition) {
14694 if (gameMode != EditGame) return;
14696 gameMode = EditPosition;
14699 if (currentMove > 0)
14700 CopyBoard(boards[0], boards[currentMove]);
14702 blackPlaysFirst = !WhiteOnMove(currentMove);
14704 currentMove = forwardMostMove = backwardMostMove = 0;
14705 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14707 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14713 /* [DM] icsEngineAnalyze - possible call from other functions */
14714 if (appData.icsEngineAnalyze) {
14715 appData.icsEngineAnalyze = FALSE;
14717 DisplayMessage("",_("Close ICS engine analyze..."));
14719 if (first.analysisSupport && first.analyzing) {
14720 SendToBoth("exit\n");
14721 first.analyzing = second.analyzing = FALSE;
14723 thinkOutput[0] = NULLCHAR;
14727 EditPositionDone (Boolean fakeRights)
14729 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14731 startedFromSetupPosition = TRUE;
14732 InitChessProgram(&first, FALSE);
14733 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14734 boards[0][EP_STATUS] = EP_NONE;
14735 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14736 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14737 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14738 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14739 } else boards[0][CASTLING][2] = NoRights;
14740 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14741 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14742 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14743 } else boards[0][CASTLING][5] = NoRights;
14744 if(gameInfo.variant == VariantSChess) {
14746 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14747 boards[0][VIRGIN][i] = 0;
14748 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14749 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14753 SendToProgram("force\n", &first);
14754 if (blackPlaysFirst) {
14755 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14756 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14757 currentMove = forwardMostMove = backwardMostMove = 1;
14758 CopyBoard(boards[1], boards[0]);
14760 currentMove = forwardMostMove = backwardMostMove = 0;
14762 SendBoard(&first, forwardMostMove);
14763 if (appData.debugMode) {
14764 fprintf(debugFP, "EditPosDone\n");
14767 DisplayMessage("", "");
14768 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14769 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14770 gameMode = EditGame;
14772 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14773 ClearHighlights(); /* [AS] */
14776 /* Pause for `ms' milliseconds */
14777 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14779 TimeDelay (long ms)
14786 } while (SubtractTimeMarks(&m2, &m1) < ms);
14789 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14791 SendMultiLineToICS (char *buf)
14793 char temp[MSG_SIZ+1], *p;
14800 strncpy(temp, buf, len);
14805 if (*p == '\n' || *p == '\r')
14810 strcat(temp, "\n");
14812 SendToPlayer(temp, strlen(temp));
14816 SetWhiteToPlayEvent ()
14818 if (gameMode == EditPosition) {
14819 blackPlaysFirst = FALSE;
14820 DisplayBothClocks(); /* works because currentMove is 0 */
14821 } else if (gameMode == IcsExamining) {
14822 SendToICS(ics_prefix);
14823 SendToICS("tomove white\n");
14828 SetBlackToPlayEvent ()
14830 if (gameMode == EditPosition) {
14831 blackPlaysFirst = TRUE;
14832 currentMove = 1; /* kludge */
14833 DisplayBothClocks();
14835 } else if (gameMode == IcsExamining) {
14836 SendToICS(ics_prefix);
14837 SendToICS("tomove black\n");
14842 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14845 ChessSquare piece = boards[0][y][x];
14846 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14847 static int lastVariant;
14849 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14851 switch (selection) {
14853 CopyBoard(currentBoard, boards[0]);
14854 CopyBoard(menuBoard, initialPosition);
14855 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14856 SendToICS(ics_prefix);
14857 SendToICS("bsetup clear\n");
14858 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14859 SendToICS(ics_prefix);
14860 SendToICS("clearboard\n");
14863 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14864 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14865 for (y = 0; y < BOARD_HEIGHT; y++) {
14866 if (gameMode == IcsExamining) {
14867 if (boards[currentMove][y][x] != EmptySquare) {
14868 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14873 if(boards[0][y][x] != p) nonEmpty++;
14874 boards[0][y][x] = p;
14877 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14879 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14880 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
14881 ChessSquare p = menuBoard[0][x];
14882 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14883 p = menuBoard[BOARD_HEIGHT-1][x];
14884 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14886 DisplayMessage("Clicking clock again restores position", "");
14887 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14888 if(!nonEmpty) { // asked to clear an empty board
14889 CopyBoard(boards[0], menuBoard);
14891 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14892 CopyBoard(boards[0], initialPosition);
14894 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14895 && !CompareBoards(nullBoard, erasedBoard)) {
14896 CopyBoard(boards[0], erasedBoard);
14898 CopyBoard(erasedBoard, currentBoard);
14902 if (gameMode == EditPosition) {
14903 DrawPosition(FALSE, boards[0]);
14908 SetWhiteToPlayEvent();
14912 SetBlackToPlayEvent();
14916 if (gameMode == IcsExamining) {
14917 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14918 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14921 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14922 if(x == BOARD_LEFT-2) {
14923 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14924 boards[0][y][1] = 0;
14926 if(x == BOARD_RGHT+1) {
14927 if(y >= gameInfo.holdingsSize) break;
14928 boards[0][y][BOARD_WIDTH-2] = 0;
14931 boards[0][y][x] = EmptySquare;
14932 DrawPosition(FALSE, boards[0]);
14937 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14938 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14939 selection = (ChessSquare) (PROMOTED piece);
14940 } else if(piece == EmptySquare) selection = WhiteSilver;
14941 else selection = (ChessSquare)((int)piece - 1);
14945 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14946 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14947 selection = (ChessSquare) (DEMOTED piece);
14948 } else if(piece == EmptySquare) selection = BlackSilver;
14949 else selection = (ChessSquare)((int)piece + 1);
14954 if(gameInfo.variant == VariantShatranj ||
14955 gameInfo.variant == VariantXiangqi ||
14956 gameInfo.variant == VariantCourier ||
14957 gameInfo.variant == VariantASEAN ||
14958 gameInfo.variant == VariantMakruk )
14959 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14964 if(gameInfo.variant == VariantXiangqi)
14965 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14966 if(gameInfo.variant == VariantKnightmate)
14967 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14970 if (gameMode == IcsExamining) {
14971 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14972 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14973 PieceToChar(selection), AAA + x, ONE + y);
14976 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14978 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14979 n = PieceToNumber(selection - BlackPawn);
14980 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14981 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14982 boards[0][BOARD_HEIGHT-1-n][1]++;
14984 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14985 n = PieceToNumber(selection);
14986 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14987 boards[0][n][BOARD_WIDTH-1] = selection;
14988 boards[0][n][BOARD_WIDTH-2]++;
14991 boards[0][y][x] = selection;
14992 DrawPosition(TRUE, boards[0]);
14994 fromX = fromY = -1;
15002 DropMenuEvent (ChessSquare selection, int x, int y)
15004 ChessMove moveType;
15006 switch (gameMode) {
15007 case IcsPlayingWhite:
15008 case MachinePlaysBlack:
15009 if (!WhiteOnMove(currentMove)) {
15010 DisplayMoveError(_("It is Black's turn"));
15013 moveType = WhiteDrop;
15015 case IcsPlayingBlack:
15016 case MachinePlaysWhite:
15017 if (WhiteOnMove(currentMove)) {
15018 DisplayMoveError(_("It is White's turn"));
15021 moveType = BlackDrop;
15024 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15030 if (moveType == BlackDrop && selection < BlackPawn) {
15031 selection = (ChessSquare) ((int) selection
15032 + (int) BlackPawn - (int) WhitePawn);
15034 if (boards[currentMove][y][x] != EmptySquare) {
15035 DisplayMoveError(_("That square is occupied"));
15039 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15045 /* Accept a pending offer of any kind from opponent */
15047 if (appData.icsActive) {
15048 SendToICS(ics_prefix);
15049 SendToICS("accept\n");
15050 } else if (cmailMsgLoaded) {
15051 if (currentMove == cmailOldMove &&
15052 commentList[cmailOldMove] != NULL &&
15053 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15054 "Black offers a draw" : "White offers a draw")) {
15056 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15057 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15059 DisplayError(_("There is no pending offer on this move"), 0);
15060 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15063 /* Not used for offers from chess program */
15070 /* Decline a pending offer of any kind from opponent */
15072 if (appData.icsActive) {
15073 SendToICS(ics_prefix);
15074 SendToICS("decline\n");
15075 } else if (cmailMsgLoaded) {
15076 if (currentMove == cmailOldMove &&
15077 commentList[cmailOldMove] != NULL &&
15078 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15079 "Black offers a draw" : "White offers a draw")) {
15081 AppendComment(cmailOldMove, "Draw declined", TRUE);
15082 DisplayComment(cmailOldMove - 1, "Draw declined");
15085 DisplayError(_("There is no pending offer on this move"), 0);
15088 /* Not used for offers from chess program */
15095 /* Issue ICS rematch command */
15096 if (appData.icsActive) {
15097 SendToICS(ics_prefix);
15098 SendToICS("rematch\n");
15105 /* Call your opponent's flag (claim a win on time) */
15106 if (appData.icsActive) {
15107 SendToICS(ics_prefix);
15108 SendToICS("flag\n");
15110 switch (gameMode) {
15113 case MachinePlaysWhite:
15116 GameEnds(GameIsDrawn, "Both players ran out of time",
15119 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15121 DisplayError(_("Your opponent is not out of time"), 0);
15124 case MachinePlaysBlack:
15127 GameEnds(GameIsDrawn, "Both players ran out of time",
15130 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15132 DisplayError(_("Your opponent is not out of time"), 0);
15140 ClockClick (int which)
15141 { // [HGM] code moved to back-end from winboard.c
15142 if(which) { // black clock
15143 if (gameMode == EditPosition || gameMode == IcsExamining) {
15144 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15145 SetBlackToPlayEvent();
15146 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15147 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15148 } else if (shiftKey) {
15149 AdjustClock(which, -1);
15150 } else if (gameMode == IcsPlayingWhite ||
15151 gameMode == MachinePlaysBlack) {
15154 } else { // white clock
15155 if (gameMode == EditPosition || gameMode == IcsExamining) {
15156 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15157 SetWhiteToPlayEvent();
15158 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15159 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15160 } else if (shiftKey) {
15161 AdjustClock(which, -1);
15162 } else if (gameMode == IcsPlayingBlack ||
15163 gameMode == MachinePlaysWhite) {
15172 /* Offer draw or accept pending draw offer from opponent */
15174 if (appData.icsActive) {
15175 /* Note: tournament rules require draw offers to be
15176 made after you make your move but before you punch
15177 your clock. Currently ICS doesn't let you do that;
15178 instead, you immediately punch your clock after making
15179 a move, but you can offer a draw at any time. */
15181 SendToICS(ics_prefix);
15182 SendToICS("draw\n");
15183 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15184 } else if (cmailMsgLoaded) {
15185 if (currentMove == cmailOldMove &&
15186 commentList[cmailOldMove] != NULL &&
15187 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15188 "Black offers a draw" : "White offers a draw")) {
15189 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15190 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15191 } else if (currentMove == cmailOldMove + 1) {
15192 char *offer = WhiteOnMove(cmailOldMove) ?
15193 "White offers a draw" : "Black offers a draw";
15194 AppendComment(currentMove, offer, TRUE);
15195 DisplayComment(currentMove - 1, offer);
15196 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15198 DisplayError(_("You must make your move before offering a draw"), 0);
15199 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15201 } else if (first.offeredDraw) {
15202 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15204 if (first.sendDrawOffers) {
15205 SendToProgram("draw\n", &first);
15206 userOfferedDraw = TRUE;
15214 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15216 if (appData.icsActive) {
15217 SendToICS(ics_prefix);
15218 SendToICS("adjourn\n");
15220 /* Currently GNU Chess doesn't offer or accept Adjourns */
15228 /* Offer Abort or accept pending Abort offer from opponent */
15230 if (appData.icsActive) {
15231 SendToICS(ics_prefix);
15232 SendToICS("abort\n");
15234 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15241 /* Resign. You can do this even if it's not your turn. */
15243 if (appData.icsActive) {
15244 SendToICS(ics_prefix);
15245 SendToICS("resign\n");
15247 switch (gameMode) {
15248 case MachinePlaysWhite:
15249 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15251 case MachinePlaysBlack:
15252 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15255 if (cmailMsgLoaded) {
15257 if (WhiteOnMove(cmailOldMove)) {
15258 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15260 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15262 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15273 StopObservingEvent ()
15275 /* Stop observing current games */
15276 SendToICS(ics_prefix);
15277 SendToICS("unobserve\n");
15281 StopExaminingEvent ()
15283 /* Stop observing current game */
15284 SendToICS(ics_prefix);
15285 SendToICS("unexamine\n");
15289 ForwardInner (int target)
15291 int limit; int oldSeekGraphUp = seekGraphUp;
15293 if (appData.debugMode)
15294 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15295 target, currentMove, forwardMostMove);
15297 if (gameMode == EditPosition)
15300 seekGraphUp = FALSE;
15301 MarkTargetSquares(1);
15303 if (gameMode == PlayFromGameFile && !pausing)
15306 if (gameMode == IcsExamining && pausing)
15307 limit = pauseExamForwardMostMove;
15309 limit = forwardMostMove;
15311 if (target > limit) target = limit;
15313 if (target > 0 && moveList[target - 1][0]) {
15314 int fromX, fromY, toX, toY;
15315 toX = moveList[target - 1][2] - AAA;
15316 toY = moveList[target - 1][3] - ONE;
15317 if (moveList[target - 1][1] == '@') {
15318 if (appData.highlightLastMove) {
15319 SetHighlights(-1, -1, toX, toY);
15322 fromX = moveList[target - 1][0] - AAA;
15323 fromY = moveList[target - 1][1] - ONE;
15324 if (target == currentMove + 1) {
15325 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15327 if (appData.highlightLastMove) {
15328 SetHighlights(fromX, fromY, toX, toY);
15332 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15333 gameMode == Training || gameMode == PlayFromGameFile ||
15334 gameMode == AnalyzeFile) {
15335 while (currentMove < target) {
15336 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15337 SendMoveToProgram(currentMove++, &first);
15340 currentMove = target;
15343 if (gameMode == EditGame || gameMode == EndOfGame) {
15344 whiteTimeRemaining = timeRemaining[0][currentMove];
15345 blackTimeRemaining = timeRemaining[1][currentMove];
15347 DisplayBothClocks();
15348 DisplayMove(currentMove - 1);
15349 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15350 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15351 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15352 DisplayComment(currentMove - 1, commentList[currentMove]);
15354 ClearMap(); // [HGM] exclude: invalidate map
15361 if (gameMode == IcsExamining && !pausing) {
15362 SendToICS(ics_prefix);
15363 SendToICS("forward\n");
15365 ForwardInner(currentMove + 1);
15372 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15373 /* to optimze, we temporarily turn off analysis mode while we feed
15374 * the remaining moves to the engine. Otherwise we get analysis output
15377 if (first.analysisSupport) {
15378 SendToProgram("exit\nforce\n", &first);
15379 first.analyzing = FALSE;
15383 if (gameMode == IcsExamining && !pausing) {
15384 SendToICS(ics_prefix);
15385 SendToICS("forward 999999\n");
15387 ForwardInner(forwardMostMove);
15390 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15391 /* we have fed all the moves, so reactivate analysis mode */
15392 SendToProgram("analyze\n", &first);
15393 first.analyzing = TRUE;
15394 /*first.maybeThinking = TRUE;*/
15395 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15400 BackwardInner (int target)
15402 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15404 if (appData.debugMode)
15405 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15406 target, currentMove, forwardMostMove);
15408 if (gameMode == EditPosition) return;
15409 seekGraphUp = FALSE;
15410 MarkTargetSquares(1);
15411 if (currentMove <= backwardMostMove) {
15413 DrawPosition(full_redraw, boards[currentMove]);
15416 if (gameMode == PlayFromGameFile && !pausing)
15419 if (moveList[target][0]) {
15420 int fromX, fromY, toX, toY;
15421 toX = moveList[target][2] - AAA;
15422 toY = moveList[target][3] - ONE;
15423 if (moveList[target][1] == '@') {
15424 if (appData.highlightLastMove) {
15425 SetHighlights(-1, -1, toX, toY);
15428 fromX = moveList[target][0] - AAA;
15429 fromY = moveList[target][1] - ONE;
15430 if (target == currentMove - 1) {
15431 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15433 if (appData.highlightLastMove) {
15434 SetHighlights(fromX, fromY, toX, toY);
15438 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15439 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15440 while (currentMove > target) {
15441 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15442 // null move cannot be undone. Reload program with move history before it.
15444 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15445 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15447 SendBoard(&first, i);
15448 if(second.analyzing) SendBoard(&second, i);
15449 for(currentMove=i; currentMove<target; currentMove++) {
15450 SendMoveToProgram(currentMove, &first);
15451 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15455 SendToBoth("undo\n");
15459 currentMove = target;
15462 if (gameMode == EditGame || gameMode == EndOfGame) {
15463 whiteTimeRemaining = timeRemaining[0][currentMove];
15464 blackTimeRemaining = timeRemaining[1][currentMove];
15466 DisplayBothClocks();
15467 DisplayMove(currentMove - 1);
15468 DrawPosition(full_redraw, boards[currentMove]);
15469 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15470 // [HGM] PV info: routine tests if comment empty
15471 DisplayComment(currentMove - 1, commentList[currentMove]);
15472 ClearMap(); // [HGM] exclude: invalidate map
15478 if (gameMode == IcsExamining && !pausing) {
15479 SendToICS(ics_prefix);
15480 SendToICS("backward\n");
15482 BackwardInner(currentMove - 1);
15489 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15490 /* to optimize, we temporarily turn off analysis mode while we undo
15491 * all the moves. Otherwise we get analysis output after each undo.
15493 if (first.analysisSupport) {
15494 SendToProgram("exit\nforce\n", &first);
15495 first.analyzing = FALSE;
15499 if (gameMode == IcsExamining && !pausing) {
15500 SendToICS(ics_prefix);
15501 SendToICS("backward 999999\n");
15503 BackwardInner(backwardMostMove);
15506 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15507 /* we have fed all the moves, so reactivate analysis mode */
15508 SendToProgram("analyze\n", &first);
15509 first.analyzing = TRUE;
15510 /*first.maybeThinking = TRUE;*/
15511 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15518 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15519 if (to >= forwardMostMove) to = forwardMostMove;
15520 if (to <= backwardMostMove) to = backwardMostMove;
15521 if (to < currentMove) {
15529 RevertEvent (Boolean annotate)
15531 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15534 if (gameMode != IcsExamining) {
15535 DisplayError(_("You are not examining a game"), 0);
15539 DisplayError(_("You can't revert while pausing"), 0);
15542 SendToICS(ics_prefix);
15543 SendToICS("revert\n");
15547 RetractMoveEvent ()
15549 switch (gameMode) {
15550 case MachinePlaysWhite:
15551 case MachinePlaysBlack:
15552 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15553 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15556 if (forwardMostMove < 2) return;
15557 currentMove = forwardMostMove = forwardMostMove - 2;
15558 whiteTimeRemaining = timeRemaining[0][currentMove];
15559 blackTimeRemaining = timeRemaining[1][currentMove];
15560 DisplayBothClocks();
15561 DisplayMove(currentMove - 1);
15562 ClearHighlights();/*!! could figure this out*/
15563 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15564 SendToProgram("remove\n", &first);
15565 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15568 case BeginningOfGame:
15572 case IcsPlayingWhite:
15573 case IcsPlayingBlack:
15574 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15575 SendToICS(ics_prefix);
15576 SendToICS("takeback 2\n");
15578 SendToICS(ics_prefix);
15579 SendToICS("takeback 1\n");
15588 ChessProgramState *cps;
15590 switch (gameMode) {
15591 case MachinePlaysWhite:
15592 if (!WhiteOnMove(forwardMostMove)) {
15593 DisplayError(_("It is your turn"), 0);
15598 case MachinePlaysBlack:
15599 if (WhiteOnMove(forwardMostMove)) {
15600 DisplayError(_("It is your turn"), 0);
15605 case TwoMachinesPlay:
15606 if (WhiteOnMove(forwardMostMove) ==
15607 (first.twoMachinesColor[0] == 'w')) {
15613 case BeginningOfGame:
15617 SendToProgram("?\n", cps);
15621 TruncateGameEvent ()
15624 if (gameMode != EditGame) return;
15631 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15632 if (forwardMostMove > currentMove) {
15633 if (gameInfo.resultDetails != NULL) {
15634 free(gameInfo.resultDetails);
15635 gameInfo.resultDetails = NULL;
15636 gameInfo.result = GameUnfinished;
15638 forwardMostMove = currentMove;
15639 HistorySet(parseList, backwardMostMove, forwardMostMove,
15647 if (appData.noChessProgram) return;
15648 switch (gameMode) {
15649 case MachinePlaysWhite:
15650 if (WhiteOnMove(forwardMostMove)) {
15651 DisplayError(_("Wait until your turn."), 0);
15655 case BeginningOfGame:
15656 case MachinePlaysBlack:
15657 if (!WhiteOnMove(forwardMostMove)) {
15658 DisplayError(_("Wait until your turn."), 0);
15663 DisplayError(_("No hint available"), 0);
15666 SendToProgram("hint\n", &first);
15667 hintRequested = TRUE;
15673 ListGame * lg = (ListGame *) gameList.head;
15676 static int secondTime = FALSE;
15678 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15679 DisplayError(_("Game list not loaded or empty"), 0);
15683 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15686 DisplayNote(_("Book file exists! Try again for overwrite."));
15690 creatingBook = TRUE;
15691 secondTime = FALSE;
15693 /* Get list size */
15694 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15695 LoadGame(f, nItem, "", TRUE);
15696 AddGameToBook(TRUE);
15697 lg = (ListGame *) lg->node.succ;
15700 creatingBook = FALSE;
15707 if (appData.noChessProgram) return;
15708 switch (gameMode) {
15709 case MachinePlaysWhite:
15710 if (WhiteOnMove(forwardMostMove)) {
15711 DisplayError(_("Wait until your turn."), 0);
15715 case BeginningOfGame:
15716 case MachinePlaysBlack:
15717 if (!WhiteOnMove(forwardMostMove)) {
15718 DisplayError(_("Wait until your turn."), 0);
15723 EditPositionDone(TRUE);
15725 case TwoMachinesPlay:
15730 SendToProgram("bk\n", &first);
15731 bookOutput[0] = NULLCHAR;
15732 bookRequested = TRUE;
15738 char *tags = PGNTags(&gameInfo);
15739 TagsPopUp(tags, CmailMsg());
15743 /* end button procedures */
15746 PrintPosition (FILE *fp, int move)
15750 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15751 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15752 char c = PieceToChar(boards[move][i][j]);
15753 fputc(c == 'x' ? '.' : c, fp);
15754 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15757 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15758 fprintf(fp, "white to play\n");
15760 fprintf(fp, "black to play\n");
15764 PrintOpponents (FILE *fp)
15766 if (gameInfo.white != NULL) {
15767 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15773 /* Find last component of program's own name, using some heuristics */
15775 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15778 int local = (strcmp(host, "localhost") == 0);
15779 while (!local && (p = strchr(prog, ';')) != NULL) {
15781 while (*p == ' ') p++;
15784 if (*prog == '"' || *prog == '\'') {
15785 q = strchr(prog + 1, *prog);
15787 q = strchr(prog, ' ');
15789 if (q == NULL) q = prog + strlen(prog);
15791 while (p >= prog && *p != '/' && *p != '\\') p--;
15793 if(p == prog && *p == '"') p++;
15795 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15796 memcpy(buf, p, q - p);
15797 buf[q - p] = NULLCHAR;
15805 TimeControlTagValue ()
15808 if (!appData.clockMode) {
15809 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15810 } else if (movesPerSession > 0) {
15811 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15812 } else if (timeIncrement == 0) {
15813 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15815 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15817 return StrSave(buf);
15823 /* This routine is used only for certain modes */
15824 VariantClass v = gameInfo.variant;
15825 ChessMove r = GameUnfinished;
15828 if(keepInfo) return;
15830 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15831 r = gameInfo.result;
15832 p = gameInfo.resultDetails;
15833 gameInfo.resultDetails = NULL;
15835 ClearGameInfo(&gameInfo);
15836 gameInfo.variant = v;
15838 switch (gameMode) {
15839 case MachinePlaysWhite:
15840 gameInfo.event = StrSave( appData.pgnEventHeader );
15841 gameInfo.site = StrSave(HostName());
15842 gameInfo.date = PGNDate();
15843 gameInfo.round = StrSave("-");
15844 gameInfo.white = StrSave(first.tidy);
15845 gameInfo.black = StrSave(UserName());
15846 gameInfo.timeControl = TimeControlTagValue();
15849 case MachinePlaysBlack:
15850 gameInfo.event = StrSave( appData.pgnEventHeader );
15851 gameInfo.site = StrSave(HostName());
15852 gameInfo.date = PGNDate();
15853 gameInfo.round = StrSave("-");
15854 gameInfo.white = StrSave(UserName());
15855 gameInfo.black = StrSave(first.tidy);
15856 gameInfo.timeControl = TimeControlTagValue();
15859 case TwoMachinesPlay:
15860 gameInfo.event = StrSave( appData.pgnEventHeader );
15861 gameInfo.site = StrSave(HostName());
15862 gameInfo.date = PGNDate();
15865 snprintf(buf, MSG_SIZ, "%d", roundNr);
15866 gameInfo.round = StrSave(buf);
15868 gameInfo.round = StrSave("-");
15870 if (first.twoMachinesColor[0] == 'w') {
15871 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15872 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15874 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15875 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15877 gameInfo.timeControl = TimeControlTagValue();
15881 gameInfo.event = StrSave("Edited game");
15882 gameInfo.site = StrSave(HostName());
15883 gameInfo.date = PGNDate();
15884 gameInfo.round = StrSave("-");
15885 gameInfo.white = StrSave("-");
15886 gameInfo.black = StrSave("-");
15887 gameInfo.result = r;
15888 gameInfo.resultDetails = p;
15892 gameInfo.event = StrSave("Edited position");
15893 gameInfo.site = StrSave(HostName());
15894 gameInfo.date = PGNDate();
15895 gameInfo.round = StrSave("-");
15896 gameInfo.white = StrSave("-");
15897 gameInfo.black = StrSave("-");
15900 case IcsPlayingWhite:
15901 case IcsPlayingBlack:
15906 case PlayFromGameFile:
15907 gameInfo.event = StrSave("Game from non-PGN file");
15908 gameInfo.site = StrSave(HostName());
15909 gameInfo.date = PGNDate();
15910 gameInfo.round = StrSave("-");
15911 gameInfo.white = StrSave("?");
15912 gameInfo.black = StrSave("?");
15921 ReplaceComment (int index, char *text)
15927 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15928 pvInfoList[index-1].depth == len &&
15929 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15930 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15931 while (*text == '\n') text++;
15932 len = strlen(text);
15933 while (len > 0 && text[len - 1] == '\n') len--;
15935 if (commentList[index] != NULL)
15936 free(commentList[index]);
15939 commentList[index] = NULL;
15942 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15943 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15944 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15945 commentList[index] = (char *) malloc(len + 2);
15946 strncpy(commentList[index], text, len);
15947 commentList[index][len] = '\n';
15948 commentList[index][len + 1] = NULLCHAR;
15950 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15952 commentList[index] = (char *) malloc(len + 7);
15953 safeStrCpy(commentList[index], "{\n", 3);
15954 safeStrCpy(commentList[index]+2, text, len+1);
15955 commentList[index][len+2] = NULLCHAR;
15956 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15957 strcat(commentList[index], "\n}\n");
15962 CrushCRs (char *text)
15970 if (ch == '\r') continue;
15972 } while (ch != '\0');
15976 AppendComment (int index, char *text, Boolean addBraces)
15977 /* addBraces tells if we should add {} */
15982 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15983 if(addBraces == 3) addBraces = 0; else // force appending literally
15984 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15987 while (*text == '\n') text++;
15988 len = strlen(text);
15989 while (len > 0 && text[len - 1] == '\n') len--;
15990 text[len] = NULLCHAR;
15992 if (len == 0) return;
15994 if (commentList[index] != NULL) {
15995 Boolean addClosingBrace = addBraces;
15996 old = commentList[index];
15997 oldlen = strlen(old);
15998 while(commentList[index][oldlen-1] == '\n')
15999 commentList[index][--oldlen] = NULLCHAR;
16000 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16001 safeStrCpy(commentList[index], old, oldlen + len + 6);
16003 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16004 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16005 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16006 while (*text == '\n') { text++; len--; }
16007 commentList[index][--oldlen] = NULLCHAR;
16009 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16010 else strcat(commentList[index], "\n");
16011 strcat(commentList[index], text);
16012 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16013 else strcat(commentList[index], "\n");
16015 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16017 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16018 else commentList[index][0] = NULLCHAR;
16019 strcat(commentList[index], text);
16020 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16021 if(addBraces == TRUE) strcat(commentList[index], "}\n");
16026 FindStr (char * text, char * sub_text)
16028 char * result = strstr( text, sub_text );
16030 if( result != NULL ) {
16031 result += strlen( sub_text );
16037 /* [AS] Try to extract PV info from PGN comment */
16038 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16040 GetInfoFromComment (int index, char * text)
16042 char * sep = text, *p;
16044 if( text != NULL && index > 0 ) {
16047 int time = -1, sec = 0, deci;
16048 char * s_eval = FindStr( text, "[%eval " );
16049 char * s_emt = FindStr( text, "[%emt " );
16051 if( s_eval != NULL || s_emt != NULL ) {
16053 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16058 if( s_eval != NULL ) {
16059 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16063 if( delim != ']' ) {
16068 if( s_emt != NULL ) {
16073 /* We expect something like: [+|-]nnn.nn/dd */
16076 if(*text != '{') return text; // [HGM] braces: must be normal comment
16078 sep = strchr( text, '/' );
16079 if( sep == NULL || sep < (text+4) ) {
16084 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16085 if(p[1] == '(') { // comment starts with PV
16086 p = strchr(p, ')'); // locate end of PV
16087 if(p == NULL || sep < p+5) return text;
16088 // at this point we have something like "{(.*) +0.23/6 ..."
16089 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16090 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16091 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16093 time = -1; sec = -1; deci = -1;
16094 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16095 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16096 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16097 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16101 if( score_lo < 0 || score_lo >= 100 ) {
16105 if(sec >= 0) time = 600*time + 10*sec; else
16106 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16108 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16110 /* [HGM] PV time: now locate end of PV info */
16111 while( *++sep >= '0' && *sep <= '9'); // strip depth
16113 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16115 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16117 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16118 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16129 pvInfoList[index-1].depth = depth;
16130 pvInfoList[index-1].score = score;
16131 pvInfoList[index-1].time = 10*time; // centi-sec
16132 if(*sep == '}') *sep = 0; else *--sep = '{';
16133 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16139 SendToProgram (char *message, ChessProgramState *cps)
16141 int count, outCount, error;
16144 if (cps->pr == NoProc) return;
16147 if (appData.debugMode) {
16150 fprintf(debugFP, "%ld >%-6s: %s",
16151 SubtractTimeMarks(&now, &programStartTime),
16152 cps->which, message);
16154 fprintf(serverFP, "%ld >%-6s: %s",
16155 SubtractTimeMarks(&now, &programStartTime),
16156 cps->which, message), fflush(serverFP);
16159 count = strlen(message);
16160 outCount = OutputToProcess(cps->pr, message, count, &error);
16161 if (outCount < count && !exiting
16162 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16163 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16164 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16165 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16166 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16167 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16168 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16169 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16171 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16172 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16173 gameInfo.result = res;
16175 gameInfo.resultDetails = StrSave(buf);
16177 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16178 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16183 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16187 ChessProgramState *cps = (ChessProgramState *)closure;
16189 if (isr != cps->isr) return; /* Killed intentionally */
16192 RemoveInputSource(cps->isr);
16193 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16194 _(cps->which), cps->program);
16195 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16196 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16197 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16198 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16199 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16200 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16202 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16203 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16204 gameInfo.result = res;
16206 gameInfo.resultDetails = StrSave(buf);
16208 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16209 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16211 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16212 _(cps->which), cps->program);
16213 RemoveInputSource(cps->isr);
16215 /* [AS] Program is misbehaving badly... kill it */
16216 if( count == -2 ) {
16217 DestroyChildProcess( cps->pr, 9 );
16221 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16226 if ((end_str = strchr(message, '\r')) != NULL)
16227 *end_str = NULLCHAR;
16228 if ((end_str = strchr(message, '\n')) != NULL)
16229 *end_str = NULLCHAR;
16231 if (appData.debugMode) {
16232 TimeMark now; int print = 1;
16233 char *quote = ""; char c; int i;
16235 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16236 char start = message[0];
16237 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16238 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16239 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16240 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16241 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16242 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16243 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16244 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16245 sscanf(message, "hint: %c", &c)!=1 &&
16246 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16247 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16248 print = (appData.engineComments >= 2);
16250 message[0] = start; // restore original message
16254 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16255 SubtractTimeMarks(&now, &programStartTime), cps->which,
16259 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16260 SubtractTimeMarks(&now, &programStartTime), cps->which,
16262 message), fflush(serverFP);
16266 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16267 if (appData.icsEngineAnalyze) {
16268 if (strstr(message, "whisper") != NULL ||
16269 strstr(message, "kibitz") != NULL ||
16270 strstr(message, "tellics") != NULL) return;
16273 HandleMachineMove(message, cps);
16278 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16283 if( timeControl_2 > 0 ) {
16284 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16285 tc = timeControl_2;
16288 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16289 inc /= cps->timeOdds;
16290 st /= cps->timeOdds;
16292 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16295 /* Set exact time per move, normally using st command */
16296 if (cps->stKludge) {
16297 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16299 if (seconds == 0) {
16300 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16302 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16305 snprintf(buf, MSG_SIZ, "st %d\n", st);
16308 /* Set conventional or incremental time control, using level command */
16309 if (seconds == 0) {
16310 /* Note old gnuchess bug -- minutes:seconds used to not work.
16311 Fixed in later versions, but still avoid :seconds
16312 when seconds is 0. */
16313 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16315 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16316 seconds, inc/1000.);
16319 SendToProgram(buf, cps);
16321 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16322 /* Orthogonally, limit search to given depth */
16324 if (cps->sdKludge) {
16325 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16327 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16329 SendToProgram(buf, cps);
16332 if(cps->nps >= 0) { /* [HGM] nps */
16333 if(cps->supportsNPS == FALSE)
16334 cps->nps = -1; // don't use if engine explicitly says not supported!
16336 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16337 SendToProgram(buf, cps);
16342 ChessProgramState *
16344 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16346 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16347 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16353 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16355 char message[MSG_SIZ];
16358 /* Note: this routine must be called when the clocks are stopped
16359 or when they have *just* been set or switched; otherwise
16360 it will be off by the time since the current tick started.
16362 if (machineWhite) {
16363 time = whiteTimeRemaining / 10;
16364 otime = blackTimeRemaining / 10;
16366 time = blackTimeRemaining / 10;
16367 otime = whiteTimeRemaining / 10;
16369 /* [HGM] translate opponent's time by time-odds factor */
16370 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16372 if (time <= 0) time = 1;
16373 if (otime <= 0) otime = 1;
16375 snprintf(message, MSG_SIZ, "time %ld\n", time);
16376 SendToProgram(message, cps);
16378 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16379 SendToProgram(message, cps);
16383 EngineDefinedVariant (ChessProgramState *cps, int n)
16384 { // return name of n-th unknown variant that engine supports
16385 static char buf[MSG_SIZ];
16386 char *p, *s = cps->variants;
16387 if(!s) return NULL;
16388 do { // parse string from variants feature
16390 p = strchr(s, ',');
16391 if(p) *p = NULLCHAR;
16392 v = StringToVariant(s);
16393 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16394 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16395 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16398 if(n < 0) return buf;
16404 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16407 int len = strlen(name);
16410 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16412 sscanf(*p, "%d", &val);
16414 while (**p && **p != ' ')
16416 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16417 SendToProgram(buf, cps);
16424 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16427 int len = strlen(name);
16428 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16430 sscanf(*p, "%d", loc);
16431 while (**p && **p != ' ') (*p)++;
16432 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16433 SendToProgram(buf, cps);
16440 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16443 int len = strlen(name);
16444 if (strncmp((*p), name, len) == 0
16445 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16447 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16448 sscanf(*p, "%[^\"]", *loc);
16449 while (**p && **p != '\"') (*p)++;
16450 if (**p == '\"') (*p)++;
16451 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16452 SendToProgram(buf, cps);
16459 ParseOption (Option *opt, ChessProgramState *cps)
16460 // [HGM] options: process the string that defines an engine option, and determine
16461 // name, type, default value, and allowed value range
16463 char *p, *q, buf[MSG_SIZ];
16464 int n, min = (-1)<<31, max = 1<<31, def;
16466 if(p = strstr(opt->name, " -spin ")) {
16467 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16468 if(max < min) max = min; // enforce consistency
16469 if(def < min) def = min;
16470 if(def > max) def = max;
16475 } else if((p = strstr(opt->name, " -slider "))) {
16476 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16477 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16478 if(max < min) max = min; // enforce consistency
16479 if(def < min) def = min;
16480 if(def > max) def = max;
16484 opt->type = Spin; // Slider;
16485 } else if((p = strstr(opt->name, " -string "))) {
16486 opt->textValue = p+9;
16487 opt->type = TextBox;
16488 } else if((p = strstr(opt->name, " -file "))) {
16489 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16490 opt->textValue = p+7;
16491 opt->type = FileName; // FileName;
16492 } else if((p = strstr(opt->name, " -path "))) {
16493 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16494 opt->textValue = p+7;
16495 opt->type = PathName; // PathName;
16496 } else if(p = strstr(opt->name, " -check ")) {
16497 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16498 opt->value = (def != 0);
16499 opt->type = CheckBox;
16500 } else if(p = strstr(opt->name, " -combo ")) {
16501 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16502 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16503 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16504 opt->value = n = 0;
16505 while(q = StrStr(q, " /// ")) {
16506 n++; *q = 0; // count choices, and null-terminate each of them
16508 if(*q == '*') { // remember default, which is marked with * prefix
16512 cps->comboList[cps->comboCnt++] = q;
16514 cps->comboList[cps->comboCnt++] = NULL;
16516 opt->type = ComboBox;
16517 } else if(p = strstr(opt->name, " -button")) {
16518 opt->type = Button;
16519 } else if(p = strstr(opt->name, " -save")) {
16520 opt->type = SaveButton;
16521 } else return FALSE;
16522 *p = 0; // terminate option name
16523 // now look if the command-line options define a setting for this engine option.
16524 if(cps->optionSettings && cps->optionSettings[0])
16525 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16526 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16527 snprintf(buf, MSG_SIZ, "option %s", p);
16528 if(p = strstr(buf, ",")) *p = 0;
16529 if(q = strchr(buf, '=')) switch(opt->type) {
16531 for(n=0; n<opt->max; n++)
16532 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16535 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16539 opt->value = atoi(q+1);
16544 SendToProgram(buf, cps);
16550 FeatureDone (ChessProgramState *cps, int val)
16552 DelayedEventCallback cb = GetDelayedEvent();
16553 if ((cb == InitBackEnd3 && cps == &first) ||
16554 (cb == SettingsMenuIfReady && cps == &second) ||
16555 (cb == LoadEngine) ||
16556 (cb == TwoMachinesEventIfReady)) {
16557 CancelDelayedEvent();
16558 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16560 cps->initDone = val;
16561 if(val) cps->reload = FALSE;
16564 /* Parse feature command from engine */
16566 ParseFeatures (char *args, ChessProgramState *cps)
16574 while (*p == ' ') p++;
16575 if (*p == NULLCHAR) return;
16577 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16578 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16579 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16580 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16581 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16582 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16583 if (BoolFeature(&p, "reuse", &val, cps)) {
16584 /* Engine can disable reuse, but can't enable it if user said no */
16585 if (!val) cps->reuse = FALSE;
16588 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16589 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16590 if (gameMode == TwoMachinesPlay) {
16591 DisplayTwoMachinesTitle();
16597 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16598 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16599 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16600 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16601 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16602 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16603 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16604 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16605 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16606 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16607 if (IntFeature(&p, "done", &val, cps)) {
16608 FeatureDone(cps, val);
16611 /* Added by Tord: */
16612 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16613 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16614 /* End of additions by Tord */
16616 /* [HGM] added features: */
16617 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16618 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16619 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16620 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16621 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16622 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16623 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16624 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16625 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16626 FREE(cps->option[cps->nrOptions].name);
16627 cps->option[cps->nrOptions].name = q; q = NULL;
16628 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16629 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16630 SendToProgram(buf, cps);
16633 if(cps->nrOptions >= MAX_OPTIONS) {
16635 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16636 DisplayError(buf, 0);
16640 /* End of additions by HGM */
16642 /* unknown feature: complain and skip */
16644 while (*q && *q != '=') q++;
16645 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16646 SendToProgram(buf, cps);
16652 while (*p && *p != '\"') p++;
16653 if (*p == '\"') p++;
16655 while (*p && *p != ' ') p++;
16663 PeriodicUpdatesEvent (int newState)
16665 if (newState == appData.periodicUpdates)
16668 appData.periodicUpdates=newState;
16670 /* Display type changes, so update it now */
16671 // DisplayAnalysis();
16673 /* Get the ball rolling again... */
16675 AnalysisPeriodicEvent(1);
16676 StartAnalysisClock();
16681 PonderNextMoveEvent (int newState)
16683 if (newState == appData.ponderNextMove) return;
16684 if (gameMode == EditPosition) EditPositionDone(TRUE);
16686 SendToProgram("hard\n", &first);
16687 if (gameMode == TwoMachinesPlay) {
16688 SendToProgram("hard\n", &second);
16691 SendToProgram("easy\n", &first);
16692 thinkOutput[0] = NULLCHAR;
16693 if (gameMode == TwoMachinesPlay) {
16694 SendToProgram("easy\n", &second);
16697 appData.ponderNextMove = newState;
16701 NewSettingEvent (int option, int *feature, char *command, int value)
16705 if (gameMode == EditPosition) EditPositionDone(TRUE);
16706 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16707 if(feature == NULL || *feature) SendToProgram(buf, &first);
16708 if (gameMode == TwoMachinesPlay) {
16709 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16714 ShowThinkingEvent ()
16715 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16717 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16718 int newState = appData.showThinking
16719 // [HGM] thinking: other features now need thinking output as well
16720 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16722 if (oldState == newState) return;
16723 oldState = newState;
16724 if (gameMode == EditPosition) EditPositionDone(TRUE);
16726 SendToProgram("post\n", &first);
16727 if (gameMode == TwoMachinesPlay) {
16728 SendToProgram("post\n", &second);
16731 SendToProgram("nopost\n", &first);
16732 thinkOutput[0] = NULLCHAR;
16733 if (gameMode == TwoMachinesPlay) {
16734 SendToProgram("nopost\n", &second);
16737 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16741 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16743 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16744 if (pr == NoProc) return;
16745 AskQuestion(title, question, replyPrefix, pr);
16749 TypeInEvent (char firstChar)
16751 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16752 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16753 gameMode == AnalyzeMode || gameMode == EditGame ||
16754 gameMode == EditPosition || gameMode == IcsExamining ||
16755 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16756 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16757 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16758 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16759 gameMode == Training) PopUpMoveDialog(firstChar);
16763 TypeInDoneEvent (char *move)
16766 int n, fromX, fromY, toX, toY;
16768 ChessMove moveType;
16771 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16772 EditPositionPasteFEN(move);
16775 // [HGM] movenum: allow move number to be typed in any mode
16776 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16780 // undocumented kludge: allow command-line option to be typed in!
16781 // (potentially fatal, and does not implement the effect of the option.)
16782 // should only be used for options that are values on which future decisions will be made,
16783 // and definitely not on options that would be used during initialization.
16784 if(strstr(move, "!!! -") == move) {
16785 ParseArgsFromString(move+4);
16789 if (gameMode != EditGame && currentMove != forwardMostMove &&
16790 gameMode != Training) {
16791 DisplayMoveError(_("Displayed move is not current"));
16793 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16794 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16795 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16796 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16797 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16798 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16800 DisplayMoveError(_("Could not parse move"));
16806 DisplayMove (int moveNumber)
16808 char message[MSG_SIZ];
16810 char cpThinkOutput[MSG_SIZ];
16812 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16814 if (moveNumber == forwardMostMove - 1 ||
16815 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16817 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16819 if (strchr(cpThinkOutput, '\n')) {
16820 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16823 *cpThinkOutput = NULLCHAR;
16826 /* [AS] Hide thinking from human user */
16827 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16828 *cpThinkOutput = NULLCHAR;
16829 if( thinkOutput[0] != NULLCHAR ) {
16832 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16833 cpThinkOutput[i] = '.';
16835 cpThinkOutput[i] = NULLCHAR;
16836 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16840 if (moveNumber == forwardMostMove - 1 &&
16841 gameInfo.resultDetails != NULL) {
16842 if (gameInfo.resultDetails[0] == NULLCHAR) {
16843 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16845 snprintf(res, MSG_SIZ, " {%s} %s",
16846 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16852 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16853 DisplayMessage(res, cpThinkOutput);
16855 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16856 WhiteOnMove(moveNumber) ? " " : ".. ",
16857 parseList[moveNumber], res);
16858 DisplayMessage(message, cpThinkOutput);
16863 DisplayComment (int moveNumber, char *text)
16865 char title[MSG_SIZ];
16867 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16868 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16870 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16871 WhiteOnMove(moveNumber) ? " " : ".. ",
16872 parseList[moveNumber]);
16874 if (text != NULL && (appData.autoDisplayComment || commentUp))
16875 CommentPopUp(title, text);
16878 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16879 * might be busy thinking or pondering. It can be omitted if your
16880 * gnuchess is configured to stop thinking immediately on any user
16881 * input. However, that gnuchess feature depends on the FIONREAD
16882 * ioctl, which does not work properly on some flavors of Unix.
16885 Attention (ChessProgramState *cps)
16888 if (!cps->useSigint) return;
16889 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16890 switch (gameMode) {
16891 case MachinePlaysWhite:
16892 case MachinePlaysBlack:
16893 case TwoMachinesPlay:
16894 case IcsPlayingWhite:
16895 case IcsPlayingBlack:
16898 /* Skip if we know it isn't thinking */
16899 if (!cps->maybeThinking) return;
16900 if (appData.debugMode)
16901 fprintf(debugFP, "Interrupting %s\n", cps->which);
16902 InterruptChildProcess(cps->pr);
16903 cps->maybeThinking = FALSE;
16908 #endif /*ATTENTION*/
16914 if (whiteTimeRemaining <= 0) {
16917 if (appData.icsActive) {
16918 if (appData.autoCallFlag &&
16919 gameMode == IcsPlayingBlack && !blackFlag) {
16920 SendToICS(ics_prefix);
16921 SendToICS("flag\n");
16925 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16927 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16928 if (appData.autoCallFlag) {
16929 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16936 if (blackTimeRemaining <= 0) {
16939 if (appData.icsActive) {
16940 if (appData.autoCallFlag &&
16941 gameMode == IcsPlayingWhite && !whiteFlag) {
16942 SendToICS(ics_prefix);
16943 SendToICS("flag\n");
16947 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16949 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16950 if (appData.autoCallFlag) {
16951 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16962 CheckTimeControl ()
16964 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16965 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16968 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16970 if ( !WhiteOnMove(forwardMostMove) ) {
16971 /* White made time control */
16972 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16973 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16974 /* [HGM] time odds: correct new time quota for time odds! */
16975 / WhitePlayer()->timeOdds;
16976 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16978 lastBlack -= blackTimeRemaining;
16979 /* Black made time control */
16980 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16981 / WhitePlayer()->other->timeOdds;
16982 lastWhite = whiteTimeRemaining;
16987 DisplayBothClocks ()
16989 int wom = gameMode == EditPosition ?
16990 !blackPlaysFirst : WhiteOnMove(currentMove);
16991 DisplayWhiteClock(whiteTimeRemaining, wom);
16992 DisplayBlackClock(blackTimeRemaining, !wom);
16996 /* Timekeeping seems to be a portability nightmare. I think everyone
16997 has ftime(), but I'm really not sure, so I'm including some ifdefs
16998 to use other calls if you don't. Clocks will be less accurate if
16999 you have neither ftime nor gettimeofday.
17002 /* VS 2008 requires the #include outside of the function */
17003 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17004 #include <sys/timeb.h>
17007 /* Get the current time as a TimeMark */
17009 GetTimeMark (TimeMark *tm)
17011 #if HAVE_GETTIMEOFDAY
17013 struct timeval timeVal;
17014 struct timezone timeZone;
17016 gettimeofday(&timeVal, &timeZone);
17017 tm->sec = (long) timeVal.tv_sec;
17018 tm->ms = (int) (timeVal.tv_usec / 1000L);
17020 #else /*!HAVE_GETTIMEOFDAY*/
17023 // include <sys/timeb.h> / moved to just above start of function
17024 struct timeb timeB;
17027 tm->sec = (long) timeB.time;
17028 tm->ms = (int) timeB.millitm;
17030 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17031 tm->sec = (long) time(NULL);
17037 /* Return the difference in milliseconds between two
17038 time marks. We assume the difference will fit in a long!
17041 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17043 return 1000L*(tm2->sec - tm1->sec) +
17044 (long) (tm2->ms - tm1->ms);
17049 * Code to manage the game clocks.
17051 * In tournament play, black starts the clock and then white makes a move.
17052 * We give the human user a slight advantage if he is playing white---the
17053 * clocks don't run until he makes his first move, so it takes zero time.
17054 * Also, we don't account for network lag, so we could get out of sync
17055 * with GNU Chess's clock -- but then, referees are always right.
17058 static TimeMark tickStartTM;
17059 static long intendedTickLength;
17062 NextTickLength (long timeRemaining)
17064 long nominalTickLength, nextTickLength;
17066 if (timeRemaining > 0L && timeRemaining <= 10000L)
17067 nominalTickLength = 100L;
17069 nominalTickLength = 1000L;
17070 nextTickLength = timeRemaining % nominalTickLength;
17071 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17073 return nextTickLength;
17076 /* Adjust clock one minute up or down */
17078 AdjustClock (Boolean which, int dir)
17080 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17081 if(which) blackTimeRemaining += 60000*dir;
17082 else whiteTimeRemaining += 60000*dir;
17083 DisplayBothClocks();
17084 adjustedClock = TRUE;
17087 /* Stop clocks and reset to a fresh time control */
17091 (void) StopClockTimer();
17092 if (appData.icsActive) {
17093 whiteTimeRemaining = blackTimeRemaining = 0;
17094 } else if (searchTime) {
17095 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17096 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17097 } else { /* [HGM] correct new time quote for time odds */
17098 whiteTC = blackTC = fullTimeControlString;
17099 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17100 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17102 if (whiteFlag || blackFlag) {
17104 whiteFlag = blackFlag = FALSE;
17106 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17107 DisplayBothClocks();
17108 adjustedClock = FALSE;
17111 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17113 /* Decrement running clock by amount of time that has passed */
17117 long timeRemaining;
17118 long lastTickLength, fudge;
17121 if (!appData.clockMode) return;
17122 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17126 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17128 /* Fudge if we woke up a little too soon */
17129 fudge = intendedTickLength - lastTickLength;
17130 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17132 if (WhiteOnMove(forwardMostMove)) {
17133 if(whiteNPS >= 0) lastTickLength = 0;
17134 timeRemaining = whiteTimeRemaining -= lastTickLength;
17135 if(timeRemaining < 0 && !appData.icsActive) {
17136 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17137 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17138 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17139 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17142 DisplayWhiteClock(whiteTimeRemaining - fudge,
17143 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17145 if(blackNPS >= 0) lastTickLength = 0;
17146 timeRemaining = blackTimeRemaining -= lastTickLength;
17147 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17148 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17150 blackStartMove = forwardMostMove;
17151 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17154 DisplayBlackClock(blackTimeRemaining - fudge,
17155 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17157 if (CheckFlags()) return;
17159 if(twoBoards) { // count down secondary board's clocks as well
17160 activePartnerTime -= lastTickLength;
17162 if(activePartner == 'W')
17163 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17165 DisplayBlackClock(activePartnerTime, TRUE);
17170 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17171 StartClockTimer(intendedTickLength);
17173 /* if the time remaining has fallen below the alarm threshold, sound the
17174 * alarm. if the alarm has sounded and (due to a takeback or time control
17175 * with increment) the time remaining has increased to a level above the
17176 * threshold, reset the alarm so it can sound again.
17179 if (appData.icsActive && appData.icsAlarm) {
17181 /* make sure we are dealing with the user's clock */
17182 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17183 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17186 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17187 alarmSounded = FALSE;
17188 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17190 alarmSounded = TRUE;
17196 /* A player has just moved, so stop the previously running
17197 clock and (if in clock mode) start the other one.
17198 We redisplay both clocks in case we're in ICS mode, because
17199 ICS gives us an update to both clocks after every move.
17200 Note that this routine is called *after* forwardMostMove
17201 is updated, so the last fractional tick must be subtracted
17202 from the color that is *not* on move now.
17205 SwitchClocks (int newMoveNr)
17207 long lastTickLength;
17209 int flagged = FALSE;
17213 if (StopClockTimer() && appData.clockMode) {
17214 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17215 if (!WhiteOnMove(forwardMostMove)) {
17216 if(blackNPS >= 0) lastTickLength = 0;
17217 blackTimeRemaining -= lastTickLength;
17218 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17219 // if(pvInfoList[forwardMostMove].time == -1)
17220 pvInfoList[forwardMostMove].time = // use GUI time
17221 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17223 if(whiteNPS >= 0) lastTickLength = 0;
17224 whiteTimeRemaining -= lastTickLength;
17225 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17226 // if(pvInfoList[forwardMostMove].time == -1)
17227 pvInfoList[forwardMostMove].time =
17228 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17230 flagged = CheckFlags();
17232 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17233 CheckTimeControl();
17235 if (flagged || !appData.clockMode) return;
17237 switch (gameMode) {
17238 case MachinePlaysBlack:
17239 case MachinePlaysWhite:
17240 case BeginningOfGame:
17241 if (pausing) return;
17245 case PlayFromGameFile:
17253 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17254 if(WhiteOnMove(forwardMostMove))
17255 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17256 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17260 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17261 whiteTimeRemaining : blackTimeRemaining);
17262 StartClockTimer(intendedTickLength);
17266 /* Stop both clocks */
17270 long lastTickLength;
17273 if (!StopClockTimer()) return;
17274 if (!appData.clockMode) return;
17278 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17279 if (WhiteOnMove(forwardMostMove)) {
17280 if(whiteNPS >= 0) lastTickLength = 0;
17281 whiteTimeRemaining -= lastTickLength;
17282 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17284 if(blackNPS >= 0) lastTickLength = 0;
17285 blackTimeRemaining -= lastTickLength;
17286 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17291 /* Start clock of player on move. Time may have been reset, so
17292 if clock is already running, stop and restart it. */
17296 (void) StopClockTimer(); /* in case it was running already */
17297 DisplayBothClocks();
17298 if (CheckFlags()) return;
17300 if (!appData.clockMode) return;
17301 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17303 GetTimeMark(&tickStartTM);
17304 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17305 whiteTimeRemaining : blackTimeRemaining);
17307 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17308 whiteNPS = blackNPS = -1;
17309 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17310 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17311 whiteNPS = first.nps;
17312 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17313 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17314 blackNPS = first.nps;
17315 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17316 whiteNPS = second.nps;
17317 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17318 blackNPS = second.nps;
17319 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17321 StartClockTimer(intendedTickLength);
17325 TimeString (long ms)
17327 long second, minute, hour, day;
17329 static char buf[32];
17331 if (ms > 0 && ms <= 9900) {
17332 /* convert milliseconds to tenths, rounding up */
17333 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17335 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17339 /* convert milliseconds to seconds, rounding up */
17340 /* use floating point to avoid strangeness of integer division
17341 with negative dividends on many machines */
17342 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17349 day = second / (60 * 60 * 24);
17350 second = second % (60 * 60 * 24);
17351 hour = second / (60 * 60);
17352 second = second % (60 * 60);
17353 minute = second / 60;
17354 second = second % 60;
17357 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17358 sign, day, hour, minute, second);
17360 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17362 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17369 * This is necessary because some C libraries aren't ANSI C compliant yet.
17372 StrStr (char *string, char *match)
17376 length = strlen(match);
17378 for (i = strlen(string) - length; i >= 0; i--, string++)
17379 if (!strncmp(match, string, length))
17386 StrCaseStr (char *string, char *match)
17390 length = strlen(match);
17392 for (i = strlen(string) - length; i >= 0; i--, string++) {
17393 for (j = 0; j < length; j++) {
17394 if (ToLower(match[j]) != ToLower(string[j]))
17397 if (j == length) return string;
17405 StrCaseCmp (char *s1, char *s2)
17410 c1 = ToLower(*s1++);
17411 c2 = ToLower(*s2++);
17412 if (c1 > c2) return 1;
17413 if (c1 < c2) return -1;
17414 if (c1 == NULLCHAR) return 0;
17422 return isupper(c) ? tolower(c) : c;
17429 return islower(c) ? toupper(c) : c;
17431 #endif /* !_amigados */
17438 if ((ret = (char *) malloc(strlen(s) + 1)))
17440 safeStrCpy(ret, s, strlen(s)+1);
17446 StrSavePtr (char *s, char **savePtr)
17451 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17452 safeStrCpy(*savePtr, s, strlen(s)+1);
17464 clock = time((time_t *)NULL);
17465 tm = localtime(&clock);
17466 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17467 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17468 return StrSave(buf);
17473 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17475 int i, j, fromX, fromY, toX, toY;
17482 whiteToPlay = (gameMode == EditPosition) ?
17483 !blackPlaysFirst : (move % 2 == 0);
17486 /* Piece placement data */
17487 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17488 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17490 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17491 if (boards[move][i][j] == EmptySquare) {
17493 } else { ChessSquare piece = boards[move][i][j];
17494 if (emptycount > 0) {
17495 if(emptycount<10) /* [HGM] can be >= 10 */
17496 *p++ = '0' + emptycount;
17497 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17500 if(PieceToChar(piece) == '+') {
17501 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17503 piece = (ChessSquare)(DEMOTED piece);
17505 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17507 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17508 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17513 if (emptycount > 0) {
17514 if(emptycount<10) /* [HGM] can be >= 10 */
17515 *p++ = '0' + emptycount;
17516 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17523 /* [HGM] print Crazyhouse or Shogi holdings */
17524 if( gameInfo.holdingsWidth ) {
17525 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17527 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17528 piece = boards[move][i][BOARD_WIDTH-1];
17529 if( piece != EmptySquare )
17530 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17531 *p++ = PieceToChar(piece);
17533 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17534 piece = boards[move][BOARD_HEIGHT-i-1][0];
17535 if( piece != EmptySquare )
17536 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17537 *p++ = PieceToChar(piece);
17540 if( q == p ) *p++ = '-';
17546 *p++ = whiteToPlay ? 'w' : 'b';
17549 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17550 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17552 if(nrCastlingRights) {
17554 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17555 /* [HGM] write directly from rights */
17556 if(boards[move][CASTLING][2] != NoRights &&
17557 boards[move][CASTLING][0] != NoRights )
17558 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17559 if(boards[move][CASTLING][2] != NoRights &&
17560 boards[move][CASTLING][1] != NoRights )
17561 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17562 if(boards[move][CASTLING][5] != NoRights &&
17563 boards[move][CASTLING][3] != NoRights )
17564 *p++ = boards[move][CASTLING][3] + AAA;
17565 if(boards[move][CASTLING][5] != NoRights &&
17566 boards[move][CASTLING][4] != NoRights )
17567 *p++ = boards[move][CASTLING][4] + AAA;
17570 /* [HGM] write true castling rights */
17571 if( nrCastlingRights == 6 ) {
17573 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17574 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17575 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17576 boards[move][CASTLING][2] != NoRights );
17577 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17578 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17579 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17580 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17581 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17585 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17586 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17587 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17588 boards[move][CASTLING][5] != NoRights );
17589 if(gameInfo.variant == VariantSChess) {
17590 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17591 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17592 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17593 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17598 if (q == p) *p++ = '-'; /* No castling rights */
17602 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17603 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17604 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17605 /* En passant target square */
17606 if (move > backwardMostMove) {
17607 fromX = moveList[move - 1][0] - AAA;
17608 fromY = moveList[move - 1][1] - ONE;
17609 toX = moveList[move - 1][2] - AAA;
17610 toY = moveList[move - 1][3] - ONE;
17611 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17612 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17613 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17615 /* 2-square pawn move just happened */
17617 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17621 } else if(move == backwardMostMove) {
17622 // [HGM] perhaps we should always do it like this, and forget the above?
17623 if((signed char)boards[move][EP_STATUS] >= 0) {
17624 *p++ = boards[move][EP_STATUS] + AAA;
17625 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17637 { int i = 0, j=move;
17639 /* [HGM] find reversible plies */
17640 if (appData.debugMode) { int k;
17641 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17642 for(k=backwardMostMove; k<=forwardMostMove; k++)
17643 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17647 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17648 if( j == backwardMostMove ) i += initialRulePlies;
17649 sprintf(p, "%d ", i);
17650 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17652 /* Fullmove number */
17653 sprintf(p, "%d", (move / 2) + 1);
17654 } else *--p = NULLCHAR;
17656 return StrSave(buf);
17660 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17664 int emptycount, virgin[BOARD_FILES];
17669 /* Piece placement data */
17670 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17673 if (*p == '/' || *p == ' ' || *p == '[' ) {
17675 emptycount = gameInfo.boardWidth - j;
17676 while (emptycount--)
17677 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17678 if (*p == '/') p++;
17679 else if(autoSize) { // we stumbled unexpectedly into end of board
17680 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17681 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17683 appData.NrRanks = gameInfo.boardHeight - i; i=0;
17686 #if(BOARD_FILES >= 10)
17687 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17688 p++; emptycount=10;
17689 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17690 while (emptycount--)
17691 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17693 } else if (*p == '*') {
17694 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17695 } else if (isdigit(*p)) {
17696 emptycount = *p++ - '0';
17697 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17698 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17699 while (emptycount--)
17700 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17701 } else if (*p == '+' || isalpha(*p)) {
17702 if (j >= gameInfo.boardWidth) return FALSE;
17704 piece = CharToPiece(*++p);
17705 if(piece == EmptySquare) return FALSE; /* unknown piece */
17706 piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17707 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17708 } else piece = CharToPiece(*p++);
17710 if(piece==EmptySquare) return FALSE; /* unknown piece */
17711 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17712 piece = (ChessSquare) (PROMOTED piece);
17713 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17716 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17722 while (*p == '/' || *p == ' ') p++;
17724 if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17726 /* [HGM] by default clear Crazyhouse holdings, if present */
17727 if(gameInfo.holdingsWidth) {
17728 for(i=0; i<BOARD_HEIGHT; i++) {
17729 board[i][0] = EmptySquare; /* black holdings */
17730 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17731 board[i][1] = (ChessSquare) 0; /* black counts */
17732 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17736 /* [HGM] look for Crazyhouse holdings here */
17737 while(*p==' ') p++;
17738 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17740 if(*p == '-' ) p++; /* empty holdings */ else {
17741 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17742 /* if we would allow FEN reading to set board size, we would */
17743 /* have to add holdings and shift the board read so far here */
17744 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17746 if((int) piece >= (int) BlackPawn ) {
17747 i = (int)piece - (int)BlackPawn;
17748 i = PieceToNumber((ChessSquare)i);
17749 if( i >= gameInfo.holdingsSize ) return FALSE;
17750 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17751 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17753 i = (int)piece - (int)WhitePawn;
17754 i = PieceToNumber((ChessSquare)i);
17755 if( i >= gameInfo.holdingsSize ) return FALSE;
17756 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17757 board[i][BOARD_WIDTH-2]++; /* black holdings */
17764 while(*p == ' ') p++;
17768 if(appData.colorNickNames) {
17769 if( c == appData.colorNickNames[0] ) c = 'w'; else
17770 if( c == appData.colorNickNames[1] ) c = 'b';
17774 *blackPlaysFirst = FALSE;
17777 *blackPlaysFirst = TRUE;
17783 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17784 /* return the extra info in global variiables */
17786 /* set defaults in case FEN is incomplete */
17787 board[EP_STATUS] = EP_UNKNOWN;
17788 for(i=0; i<nrCastlingRights; i++ ) {
17789 board[CASTLING][i] =
17790 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17791 } /* assume possible unless obviously impossible */
17792 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17793 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17794 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17795 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17796 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17797 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17798 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17799 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17802 while(*p==' ') p++;
17803 if(nrCastlingRights) {
17804 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17805 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17806 /* castling indicator present, so default becomes no castlings */
17807 for(i=0; i<nrCastlingRights; i++ ) {
17808 board[CASTLING][i] = NoRights;
17811 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17812 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17813 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17814 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17815 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17817 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17818 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17819 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17821 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17822 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17823 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17824 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17825 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17826 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17829 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17830 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17831 board[CASTLING][2] = whiteKingFile;
17832 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17833 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17836 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17837 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17838 board[CASTLING][2] = whiteKingFile;
17839 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17840 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17843 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17844 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17845 board[CASTLING][5] = blackKingFile;
17846 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17847 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17850 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17851 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17852 board[CASTLING][5] = blackKingFile;
17853 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17854 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17857 default: /* FRC castlings */
17858 if(c >= 'a') { /* black rights */
17859 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17860 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17861 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17862 if(i == BOARD_RGHT) break;
17863 board[CASTLING][5] = i;
17865 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17866 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17868 board[CASTLING][3] = c;
17870 board[CASTLING][4] = c;
17871 } else { /* white rights */
17872 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17873 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17874 if(board[0][i] == WhiteKing) break;
17875 if(i == BOARD_RGHT) break;
17876 board[CASTLING][2] = i;
17877 c -= AAA - 'a' + 'A';
17878 if(board[0][c] >= WhiteKing) break;
17880 board[CASTLING][0] = c;
17882 board[CASTLING][1] = c;
17886 for(i=0; i<nrCastlingRights; i++)
17887 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17888 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17889 if (appData.debugMode) {
17890 fprintf(debugFP, "FEN castling rights:");
17891 for(i=0; i<nrCastlingRights; i++)
17892 fprintf(debugFP, " %d", board[CASTLING][i]);
17893 fprintf(debugFP, "\n");
17896 while(*p==' ') p++;
17899 /* read e.p. field in games that know e.p. capture */
17900 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17901 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17902 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17904 p++; board[EP_STATUS] = EP_NONE;
17906 char c = *p++ - AAA;
17908 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17909 if(*p >= '0' && *p <='9') p++;
17910 board[EP_STATUS] = c;
17915 if(sscanf(p, "%d", &i) == 1) {
17916 FENrulePlies = i; /* 50-move ply counter */
17917 /* (The move number is still ignored) */
17924 EditPositionPasteFEN (char *fen)
17927 Board initial_position;
17929 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17930 DisplayError(_("Bad FEN position in clipboard"), 0);
17933 int savedBlackPlaysFirst = blackPlaysFirst;
17934 EditPositionEvent();
17935 blackPlaysFirst = savedBlackPlaysFirst;
17936 CopyBoard(boards[0], initial_position);
17937 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17938 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17939 DisplayBothClocks();
17940 DrawPosition(FALSE, boards[currentMove]);
17945 static char cseq[12] = "\\ ";
17948 set_cont_sequence (char *new_seq)
17953 // handle bad attempts to set the sequence
17955 return 0; // acceptable error - no debug
17957 len = strlen(new_seq);
17958 ret = (len > 0) && (len < sizeof(cseq));
17960 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17961 else if (appData.debugMode)
17962 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17967 reformat a source message so words don't cross the width boundary. internal
17968 newlines are not removed. returns the wrapped size (no null character unless
17969 included in source message). If dest is NULL, only calculate the size required
17970 for the dest buffer. lp argument indicats line position upon entry, and it's
17971 passed back upon exit.
17974 wrap (char *dest, char *src, int count, int width, int *lp)
17976 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17978 cseq_len = strlen(cseq);
17979 old_line = line = *lp;
17980 ansi = len = clen = 0;
17982 for (i=0; i < count; i++)
17984 if (src[i] == '\033')
17987 // if we hit the width, back up
17988 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17990 // store i & len in case the word is too long
17991 old_i = i, old_len = len;
17993 // find the end of the last word
17994 while (i && src[i] != ' ' && src[i] != '\n')
18000 // word too long? restore i & len before splitting it
18001 if ((old_i-i+clen) >= width)
18008 if (i && src[i-1] == ' ')
18011 if (src[i] != ' ' && src[i] != '\n')
18018 // now append the newline and continuation sequence
18023 strncpy(dest+len, cseq, cseq_len);
18031 dest[len] = src[i];
18035 if (src[i] == '\n')
18040 if (dest && appData.debugMode)
18042 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18043 count, width, line, len, *lp);
18044 show_bytes(debugFP, src, count);
18045 fprintf(debugFP, "\ndest: ");
18046 show_bytes(debugFP, dest, len);
18047 fprintf(debugFP, "\n");
18049 *lp = dest ? line : old_line;
18054 // [HGM] vari: routines for shelving variations
18055 Boolean modeRestore = FALSE;
18058 PushInner (int firstMove, int lastMove)
18060 int i, j, nrMoves = lastMove - firstMove;
18062 // push current tail of game on stack
18063 savedResult[storedGames] = gameInfo.result;
18064 savedDetails[storedGames] = gameInfo.resultDetails;
18065 gameInfo.resultDetails = NULL;
18066 savedFirst[storedGames] = firstMove;
18067 savedLast [storedGames] = lastMove;
18068 savedFramePtr[storedGames] = framePtr;
18069 framePtr -= nrMoves; // reserve space for the boards
18070 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18071 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18072 for(j=0; j<MOVE_LEN; j++)
18073 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18074 for(j=0; j<2*MOVE_LEN; j++)
18075 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18076 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18077 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18078 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18079 pvInfoList[firstMove+i-1].depth = 0;
18080 commentList[framePtr+i] = commentList[firstMove+i];
18081 commentList[firstMove+i] = NULL;
18085 forwardMostMove = firstMove; // truncate game so we can start variation
18089 PushTail (int firstMove, int lastMove)
18091 if(appData.icsActive) { // only in local mode
18092 forwardMostMove = currentMove; // mimic old ICS behavior
18095 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18097 PushInner(firstMove, lastMove);
18098 if(storedGames == 1) GreyRevert(FALSE);
18099 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18103 PopInner (Boolean annotate)
18106 char buf[8000], moveBuf[20];
18108 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18109 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18110 nrMoves = savedLast[storedGames] - currentMove;
18113 if(!WhiteOnMove(currentMove))
18114 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18115 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18116 for(i=currentMove; i<forwardMostMove; i++) {
18118 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18119 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18120 strcat(buf, moveBuf);
18121 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18122 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18126 for(i=1; i<=nrMoves; i++) { // copy last variation back
18127 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18128 for(j=0; j<MOVE_LEN; j++)
18129 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18130 for(j=0; j<2*MOVE_LEN; j++)
18131 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18132 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18133 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18134 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18135 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18136 commentList[currentMove+i] = commentList[framePtr+i];
18137 commentList[framePtr+i] = NULL;
18139 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18140 framePtr = savedFramePtr[storedGames];
18141 gameInfo.result = savedResult[storedGames];
18142 if(gameInfo.resultDetails != NULL) {
18143 free(gameInfo.resultDetails);
18145 gameInfo.resultDetails = savedDetails[storedGames];
18146 forwardMostMove = currentMove + nrMoves;
18150 PopTail (Boolean annotate)
18152 if(appData.icsActive) return FALSE; // only in local mode
18153 if(!storedGames) return FALSE; // sanity
18154 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18156 PopInner(annotate);
18157 if(currentMove < forwardMostMove) ForwardEvent(); else
18158 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18160 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18166 { // remove all shelved variations
18168 for(i=0; i<storedGames; i++) {
18169 if(savedDetails[i])
18170 free(savedDetails[i]);
18171 savedDetails[i] = NULL;
18173 for(i=framePtr; i<MAX_MOVES; i++) {
18174 if(commentList[i]) free(commentList[i]);
18175 commentList[i] = NULL;
18177 framePtr = MAX_MOVES-1;
18182 LoadVariation (int index, char *text)
18183 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18184 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18185 int level = 0, move;
18187 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18188 // first find outermost bracketing variation
18189 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18190 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18191 if(*p == '{') wait = '}'; else
18192 if(*p == '[') wait = ']'; else
18193 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18194 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18196 if(*p == wait) wait = NULLCHAR; // closing ]} found
18199 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18200 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18201 end[1] = NULLCHAR; // clip off comment beyond variation
18202 ToNrEvent(currentMove-1);
18203 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18204 // kludge: use ParsePV() to append variation to game
18205 move = currentMove;
18206 ParsePV(start, TRUE, TRUE);
18207 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18208 ClearPremoveHighlights();
18210 ToNrEvent(currentMove+1);
18216 char *p, *q, buf[MSG_SIZ];
18217 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18218 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18219 ParseArgsFromString(buf);
18220 ActivateTheme(TRUE); // also redo colors
18224 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18227 q = appData.themeNames;
18228 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18229 if(appData.useBitmaps) {
18230 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18231 appData.liteBackTextureFile, appData.darkBackTextureFile,
18232 appData.liteBackTextureMode,
18233 appData.darkBackTextureMode );
18235 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18236 Col2Text(2), // lightSquareColor
18237 Col2Text(3) ); // darkSquareColor
18239 if(appData.useBorder) {
18240 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18243 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18245 if(appData.useFont) {
18246 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18247 appData.renderPiecesWithFont,
18248 appData.fontToPieceTable,
18249 Col2Text(9), // appData.fontBackColorWhite
18250 Col2Text(10) ); // appData.fontForeColorBlack
18252 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18253 appData.pieceDirectory);
18254 if(!appData.pieceDirectory[0])
18255 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18256 Col2Text(0), // whitePieceColor
18257 Col2Text(1) ); // blackPieceColor
18259 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18260 Col2Text(4), // highlightSquareColor
18261 Col2Text(5) ); // premoveHighlightColor
18262 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18263 if(insert != q) insert[-1] = NULLCHAR;
18264 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18267 ActivateTheme(FALSE);