2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
9 * Software Foundation, Inc.
11 * Enhancements Copyright 2005 Alessandro Scotti
13 * The following terms apply to Digital Equipment Corporation's copyright
15 * ------------------------------------------------------------------------
18 * Permission to use, copy, modify, and distribute this software and its
19 * documentation for any purpose and without fee is hereby granted,
20 * provided that the above copyright notice appear in all copies and that
21 * both that copyright notice and this permission notice appear in
22 * supporting documentation, and that the name of Digital not be
23 * used in advertising or publicity pertaining to distribution of the
24 * software without specific, written prior permission.
26 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
33 * ------------------------------------------------------------------------
35 * The following terms apply to the enhanced version of XBoard
36 * distributed by the Free Software Foundation:
37 * ------------------------------------------------------------------------
39 * GNU XBoard is free software: you can redistribute it and/or modify
40 * it under the terms of the GNU General Public License as published by
41 * the Free Software Foundation, either version 3 of the License, or (at
42 * your option) any later version.
44 * GNU XBoard is distributed in the hope that it will be useful, but
45 * WITHOUT ANY WARRANTY; without even the implied warranty of
46 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47 * General Public License for more details.
49 * You should have received a copy of the GNU General Public License
50 * along with this program. If not, see http://www.gnu.org/licenses/. *
52 *------------------------------------------------------------------------
53 ** See the file ChangeLog for a revision history. */
55 /* [AS] Also useful here for debugging */
59 int flock(int f, int code);
64 # define EGBB_NAME "egbbdll64.dll"
66 # define EGBB_NAME "egbbdll.dll"
71 # include <sys/file.h>
76 # define EGBB_NAME "egbbso64.so"
78 # define EGBB_NAME "egbbso.so"
80 // kludge to allow Windows code in back-end by converting it to corresponding Linux code
82 # define HMODULE void *
83 # define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 # define GetProcAddress dlsym
94 #include <sys/types.h>
103 #else /* not STDC_HEADERS */
106 # else /* not HAVE_STRING_H */
107 # include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
124 # include <sys/time.h>
130 #if defined(_amigados) && !defined(__GNUC__)
135 extern int gettimeofday(struct timeval *, struct timezone *);
143 #include "frontend.h"
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173 char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175 char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188 /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199 char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201 int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
250 extern void ConsoleCreate();
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
264 int deadRanks, handSize, handOffsets;
266 extern int tinyLayout, smallLayout;
267 ChessProgramStats programStats;
268 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
270 static int exiting = 0; /* [HGM] moved to top */
271 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
272 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
273 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
274 int partnerHighlight[2];
275 Boolean partnerBoardValid = 0;
276 char partnerStatus[MSG_SIZ];
278 Boolean originalFlip;
279 Boolean twoBoards = 0;
280 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
281 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
282 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
283 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
284 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
285 int opponentKibitzes;
286 int lastSavedGame; /* [HGM] save: ID of game */
287 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
288 extern int chatCount;
290 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
291 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
292 char lastMsg[MSG_SIZ];
293 char lastTalker[MSG_SIZ];
294 ChessSquare pieceSweep = EmptySquare;
295 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
296 int promoDefaultAltered;
297 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
298 static int initPing = -1;
299 int border; /* [HGM] width of board rim, needed to size seek graph */
300 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
301 int solvingTime, totalTime;
303 /* States for ics_getting_history */
305 #define H_REQUESTED 1
306 #define H_GOT_REQ_HEADER 2
307 #define H_GOT_UNREQ_HEADER 3
308 #define H_GETTING_MOVES 4
309 #define H_GOT_UNWANTED_HEADER 5
311 /* whosays values for GameEnds */
320 /* Maximum number of games in a cmail message */
321 #define CMAIL_MAX_GAMES 20
323 /* Different types of move when calling RegisterMove */
325 #define CMAIL_RESIGN 1
327 #define CMAIL_ACCEPT 3
329 /* Different types of result to remember for each game */
330 #define CMAIL_NOT_RESULT 0
331 #define CMAIL_OLD_RESULT 1
332 #define CMAIL_NEW_RESULT 2
334 /* Telnet protocol constants */
345 safeStrCpy (char *dst, const char *src, size_t count)
348 assert( dst != NULL );
349 assert( src != NULL );
352 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
353 if( i == count && dst[count-1] != NULLCHAR)
355 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
356 if(appData.debugMode)
357 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
363 /* Some compiler can't cast u64 to double
364 * This function do the job for us:
366 * We use the highest bit for cast, this only
367 * works if the highest bit is not
368 * in use (This should not happen)
370 * We used this for all compiler
373 u64ToDouble (u64 value)
376 u64 tmp = value & u64Const(0x7fffffffffffffff);
377 r = (double)(s64)tmp;
378 if (value & u64Const(0x8000000000000000))
379 r += 9.2233720368547758080e18; /* 2^63 */
383 /* Fake up flags for now, as we aren't keeping track of castling
384 availability yet. [HGM] Change of logic: the flag now only
385 indicates the type of castlings allowed by the rule of the game.
386 The actual rights themselves are maintained in the array
387 castlingRights, as part of the game history, and are not probed
393 int flags = F_ALL_CASTLE_OK;
394 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
395 switch (gameInfo.variant) {
397 flags &= ~F_ALL_CASTLE_OK;
398 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
399 flags |= F_IGNORE_CHECK;
401 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
404 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
406 case VariantKriegspiel:
407 flags |= F_KRIEGSPIEL_CAPTURE;
409 case VariantCapaRandom:
410 case VariantFischeRandom:
411 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
412 case VariantNoCastle:
413 case VariantShatranj:
418 flags &= ~F_ALL_CASTLE_OK;
421 case VariantChuChess:
423 flags |= F_NULL_MOVE;
428 if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
432 FILE *gameFileFP, *debugFP, *serverFP;
433 char *currentDebugFile; // [HGM] debug split: to remember name
436 [AS] Note: sometimes, the sscanf() function is used to parse the input
437 into a fixed-size buffer. Because of this, we must be prepared to
438 receive strings as long as the size of the input buffer, which is currently
439 set to 4K for Windows and 8K for the rest.
440 So, we must either allocate sufficiently large buffers here, or
441 reduce the size of the input buffer in the input reading part.
444 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
445 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
446 char thinkOutput1[MSG_SIZ*10];
447 char promoRestrict[MSG_SIZ];
449 ChessProgramState first, second, pairing;
451 /* premove variables */
454 int premoveFromX = 0;
455 int premoveFromY = 0;
456 int premovePromoChar = 0;
458 Boolean alarmSounded;
459 /* end premove variables */
461 char *ics_prefix = "$";
462 enum ICS_TYPE ics_type = ICS_GENERIC;
464 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
465 int pauseExamForwardMostMove = 0;
466 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
467 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
468 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
469 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
470 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
471 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
472 int whiteFlag = FALSE, blackFlag = FALSE;
473 int userOfferedDraw = FALSE;
474 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
475 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
476 int cmailMoveType[CMAIL_MAX_GAMES];
477 long ics_clock_paused = 0;
478 ProcRef icsPR = NoProc, cmailPR = NoProc;
479 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
480 GameMode gameMode = BeginningOfGame;
481 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
482 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
483 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
484 int hiddenThinkOutputState = 0; /* [AS] */
485 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
486 int adjudicateLossPlies = 6;
487 char white_holding[64], black_holding[64];
488 TimeMark lastNodeCountTime;
489 long lastNodeCount=0;
490 int shiftKey, controlKey; // [HGM] set by mouse handler
492 int have_sent_ICS_logon = 0;
494 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
495 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
496 Boolean adjustedClock;
497 long timeControl_2; /* [AS] Allow separate time controls */
498 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
499 long timeRemaining[2][MAX_MOVES];
500 int matchGame = 0, nextGame = 0, roundNr = 0;
501 Boolean waitingForGame = FALSE, startingEngine = FALSE;
502 TimeMark programStartTime, pauseStart;
503 char ics_handle[MSG_SIZ];
504 int have_set_title = 0;
506 /* animateTraining preserves the state of appData.animate
507 * when Training mode is activated. This allows the
508 * response to be animated when appData.animate == TRUE and
509 * appData.animateDragging == TRUE.
511 Boolean animateTraining;
517 Board boards[MAX_MOVES];
518 /* [HGM] Following 7 needed for accurate legality tests: */
519 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
520 unsigned char initialRights[BOARD_FILES];
521 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
522 int initialRulePlies, FENrulePlies;
523 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
525 Boolean shuffleOpenings;
526 int mute; // mute all sounds
528 // [HGM] vari: next 12 to save and restore variations
529 #define MAX_VARIATIONS 10
530 int framePtr = MAX_MOVES-1; // points to free stack entry
532 int savedFirst[MAX_VARIATIONS];
533 int savedLast[MAX_VARIATIONS];
534 int savedFramePtr[MAX_VARIATIONS];
535 char *savedDetails[MAX_VARIATIONS];
536 ChessMove savedResult[MAX_VARIATIONS];
538 void PushTail P((int firstMove, int lastMove));
539 Boolean PopTail P((Boolean annotate));
540 void PushInner P((int firstMove, int lastMove));
541 void PopInner P((Boolean annotate));
542 void CleanupTail P((void));
544 ChessSquare FIDEArray[2][BOARD_FILES] = {
545 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
548 BlackKing, BlackBishop, BlackKnight, BlackRook }
551 ChessSquare twoKingsArray[2][BOARD_FILES] = {
552 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
553 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
554 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
555 BlackKing, BlackKing, BlackKnight, BlackRook }
558 ChessSquare KnightmateArray[2][BOARD_FILES] = {
559 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
560 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
561 { BlackRook, BlackMan, BlackBishop, BlackQueen,
562 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
565 ChessSquare SpartanArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
567 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
568 { BlackAlfil, BlackDragon, BlackKing, BlackTower,
569 BlackTower, BlackKing, BlackAngel, BlackAlfil }
572 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
573 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
574 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
575 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
576 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
579 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
580 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
581 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
582 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
583 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
586 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
587 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
588 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
589 { BlackRook, BlackKnight, BlackMan, BlackFerz,
590 BlackKing, BlackMan, BlackKnight, BlackRook }
593 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
594 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
595 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
596 { BlackRook, BlackKnight, BlackMan, BlackFerz,
597 BlackKing, BlackMan, BlackKnight, BlackRook }
600 ChessSquare lionArray[2][BOARD_FILES] = {
601 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
602 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
603 { BlackRook, BlackLion, BlackBishop, BlackQueen,
604 BlackKing, BlackBishop, BlackKnight, BlackRook }
608 #if (BOARD_FILES>=10)
609 ChessSquare ShogiArray[2][BOARD_FILES] = {
610 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
611 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
612 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
613 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
616 ChessSquare XiangqiArray[2][BOARD_FILES] = {
617 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
618 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
619 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
620 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
623 ChessSquare CapablancaArray[2][BOARD_FILES] = {
624 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
625 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
626 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
627 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
630 ChessSquare GreatArray[2][BOARD_FILES] = {
631 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
632 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
633 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
634 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
637 ChessSquare JanusArray[2][BOARD_FILES] = {
638 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
639 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
640 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
641 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
644 ChessSquare GrandArray[2][BOARD_FILES] = {
645 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
646 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
647 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
648 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
651 ChessSquare ChuChessArray[2][BOARD_FILES] = {
652 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
653 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
654 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
655 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
659 ChessSquare GothicArray[2][BOARD_FILES] = {
660 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
661 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
662 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
663 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
666 #define GothicArray CapablancaArray
670 ChessSquare FalconArray[2][BOARD_FILES] = {
671 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
672 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
673 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
674 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
677 #define FalconArray CapablancaArray
680 #else // !(BOARD_FILES>=10)
681 #define XiangqiPosition FIDEArray
682 #define CapablancaArray FIDEArray
683 #define GothicArray FIDEArray
684 #define GreatArray FIDEArray
685 #endif // !(BOARD_FILES>=10)
687 #if (BOARD_FILES>=12)
688 ChessSquare CourierArray[2][BOARD_FILES] = {
689 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
690 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
691 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
692 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
694 ChessSquare ChuArray[6][BOARD_FILES] = {
695 { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
696 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
697 { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
698 BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
699 { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
700 WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
701 { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
702 BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
703 { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
704 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
705 { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
706 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
708 #else // !(BOARD_FILES>=12)
709 #define CourierArray CapablancaArray
710 #define ChuArray CapablancaArray
711 #endif // !(BOARD_FILES>=12)
714 Board initialPosition;
717 /* Convert str to a rating. Checks for special cases of "----",
719 "++++", etc. Also strips ()'s */
721 string_to_rating (char *str)
723 while(*str && !isdigit(*str)) ++str;
725 return 0; /* One of the special "no rating" cases */
733 /* Init programStats */
734 programStats.movelist[0] = 0;
735 programStats.depth = 0;
736 programStats.nr_moves = 0;
737 programStats.moves_left = 0;
738 programStats.nodes = 0;
739 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
740 programStats.score = 0;
741 programStats.got_only_move = 0;
742 programStats.got_fail = 0;
743 programStats.line_is_book = 0;
748 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
749 if (appData.firstPlaysBlack) {
750 first.twoMachinesColor = "black\n";
751 second.twoMachinesColor = "white\n";
753 first.twoMachinesColor = "white\n";
754 second.twoMachinesColor = "black\n";
757 first.other = &second;
758 second.other = &first;
761 if(appData.timeOddsMode) {
762 norm = appData.timeOdds[0];
763 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
765 first.timeOdds = appData.timeOdds[0]/norm;
766 second.timeOdds = appData.timeOdds[1]/norm;
769 if(programVersion) free(programVersion);
770 if (appData.noChessProgram) {
771 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
772 sprintf(programVersion, "%s", PACKAGE_STRING);
774 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
775 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
776 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
781 UnloadEngine (ChessProgramState *cps)
783 /* Kill off first chess program */
784 if (cps->isr != NULL)
785 RemoveInputSource(cps->isr);
788 if (cps->pr != NoProc) {
790 DoSleep( appData.delayBeforeQuit );
791 SendToProgram("quit\n", cps);
792 DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
795 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
799 ClearOptions (ChessProgramState *cps)
802 cps->nrOptions = cps->comboCnt = 0;
803 for(i=0; i<MAX_OPTIONS; i++) {
804 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
805 cps->option[i].textValue = 0;
809 char *engineNames[] = {
810 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
811 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
813 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
814 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
819 InitEngine (ChessProgramState *cps, int n)
820 { // [HGM] all engine initialiation put in a function that does one engine
824 cps->which = engineNames[n];
825 cps->maybeThinking = FALSE;
829 cps->sendDrawOffers = 1;
831 cps->program = appData.chessProgram[n];
832 cps->host = appData.host[n];
833 cps->dir = appData.directory[n];
834 cps->initString = appData.engInitString[n];
835 cps->computerString = appData.computerString[n];
836 cps->useSigint = TRUE;
837 cps->useSigterm = TRUE;
838 cps->reuse = appData.reuse[n];
839 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
840 cps->useSetboard = FALSE;
842 cps->usePing = FALSE;
845 cps->usePlayother = FALSE;
846 cps->useColors = TRUE;
847 cps->useUsermove = FALSE;
848 cps->sendICS = FALSE;
849 cps->sendName = appData.icsActive;
850 cps->sdKludge = FALSE;
851 cps->stKludge = FALSE;
852 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
853 TidyProgramName(cps->program, cps->host, cps->tidy);
855 ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
856 cps->analysisSupport = 2; /* detect */
857 cps->analyzing = FALSE;
858 cps->initDone = FALSE;
860 cps->pseudo = appData.pseudo[n];
862 /* New features added by Tord: */
863 cps->useFEN960 = FALSE;
864 cps->useOOCastle = TRUE;
865 /* End of new features added by Tord. */
866 cps->fenOverride = appData.fenOverride[n];
868 /* [HGM] time odds: set factor for each machine */
869 cps->timeOdds = appData.timeOdds[n];
871 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
872 cps->accumulateTC = appData.accumulateTC[n];
873 cps->maxNrOfSessions = 1;
878 cps->drawDepth = appData.drawDepth[n];
879 cps->supportsNPS = UNKNOWN;
880 cps->memSize = FALSE;
881 cps->maxCores = FALSE;
882 ASSIGN(cps->egtFormats, "");
885 cps->optionSettings = appData.engOptions[n];
887 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
888 cps->isUCI = appData.isUCI[n]; /* [AS] */
889 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
892 if (appData.protocolVersion[n] > PROTOVER
893 || appData.protocolVersion[n] < 1)
898 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
899 appData.protocolVersion[n]);
900 if( (len >= MSG_SIZ) && appData.debugMode )
901 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
903 DisplayFatalError(buf, 0, 2);
907 cps->protocolVersion = appData.protocolVersion[n];
910 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
911 ParseFeatures(appData.featureDefaults, cps);
914 ChessProgramState *savCps;
916 GameMode oldMode, tryNr;
918 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
919 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
920 char *insert, *wbOptions, *currentEngine[2]; // point in ChessProgramNames were we should insert new engine
921 static char newEngineCommand[MSG_SIZ];
924 FloatToFront(char **list, char *engineLine)
926 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
928 if(appData.recentEngines <= 0) return;
929 TidyProgramName(engineLine, "localhost", tidy+1);
930 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
931 strncpy(buf+1, *list, MSG_SIZ-50);
932 if(p = strstr(buf, tidy)) { // tidy name appears in list
933 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
934 while(*p++ = *++q); // squeeze out
936 strcat(tidy, buf+1); // put list behind tidy name
937 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
938 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
939 ASSIGN(*list, tidy+1);
943 AddToEngineList (int i)
946 char quote, buf[MSG_SIZ];
947 char *q = firstChessProgramNames, *p = newEngineCommand;
948 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
949 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
950 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s",
951 quote, p, quote, appData.directory[i],
952 useNick ? " -fn \"" : "",
953 useNick ? nickName : "",
955 v1 ? " -firstProtocolVersion 1" : "",
956 hasBook ? "" : " -fNoOwnBookUCI",
957 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
958 storeVariant ? " -variant " : "",
959 storeVariant ? VariantName(gameInfo.variant) : "");
960 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " %s", wbOptions);
961 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 2);
962 if(insert != q) insert[-1] = NULLCHAR;
963 snprintf(firstChessProgramNames, len, "%s\n%s\n%s", q, buf, insert);
965 FloatToFront(&appData.recentEngineList, buf);
966 ASSIGN(currentEngine[i], buf);
973 if(WaitForEngine(savCps, LoadEngine)) return;
974 if(tryNr == 1 && !isUCI) { SendToProgram("uci\n", savCps); tryNr = 2; ScheduleDelayedEvent(LoadEngine, FEATURE_TIMEOUT); return; }
975 if(tryNr) v1 = (tryNr == 2), tryNr = 0, AddToEngineList(0); // deferred to after protocol determination
976 CommonEngineInit(); // recalculate time odds
977 if(gameInfo.variant != StringToVariant(appData.variant)) {
978 // we changed variant when loading the engine; this forces us to reset
979 Reset(TRUE, savCps != &first);
980 oldMode = BeginningOfGame; // to prevent restoring old mode
982 InitChessProgram(savCps, FALSE);
983 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
984 DisplayMessage("", "");
985 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
986 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
989 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
993 ReplaceEngine (ChessProgramState *cps, int n)
995 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
997 if(oldMode != BeginningOfGame) EditGameEvent();
1000 appData.noChessProgram = FALSE;
1001 appData.clockMode = TRUE;
1004 if(n) return; // only startup first engine immediately; second can wait
1005 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
1009 static char resetOptions[] =
1010 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
1011 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
1012 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
1013 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
1016 Load (ChessProgramState *cps, int i)
1018 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
1019 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
1020 ASSIGN(currentEngine[i], engineLine);
1021 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
1022 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
1023 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
1024 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
1025 appData.firstProtocolVersion = PROTOVER;
1026 ParseArgsFromString(buf);
1028 ReplaceEngine(cps, i);
1029 FloatToFront(&appData.recentEngineList, engineLine);
1030 if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1034 while(q = strchr(p, SLASH)) p = q+1;
1035 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1036 if(engineDir[0] != NULLCHAR) {
1037 ASSIGN(appData.directory[i], engineDir); p = engineName;
1038 } else if(p != engineName) { // derive directory from engine path, when not given
1040 ASSIGN(appData.directory[i], engineName);
1042 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1043 } else { ASSIGN(appData.directory[i], "."); }
1044 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1046 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1047 snprintf(command, MSG_SIZ, "%s %s", p, params);
1050 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1051 ASSIGN(appData.chessProgram[i], p);
1052 appData.isUCI[i] = isUCI;
1053 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1054 appData.hasOwnBookUCI[i] = hasBook;
1055 if(!nickName[0]) useNick = FALSE;
1056 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1057 safeStrCpy(newEngineCommand, p, MSG_SIZ);
1059 ReplaceEngine(cps, i);
1065 int matched, min, sec;
1067 * Parse timeControl resource
1069 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1070 appData.movesPerSession)) {
1072 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1073 DisplayFatalError(buf, 0, 2);
1077 * Parse searchTime resource
1079 if (*appData.searchTime != NULLCHAR) {
1080 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1082 searchTime = min * 60;
1083 } else if (matched == 2) {
1084 searchTime = min * 60 + sec;
1087 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1088 DisplayFatalError(buf, 0, 2);
1097 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1098 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1100 GetTimeMark(&programStartTime);
1101 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1102 appData.seedBase = random() + (random()<<15);
1103 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1105 ClearProgramStats();
1106 programStats.ok_to_send = 1;
1107 programStats.seen_stat = 0;
1110 * Initialize game list
1116 * Internet chess server status
1118 if (appData.icsActive) {
1119 appData.matchMode = FALSE;
1120 appData.matchGames = 0;
1122 appData.noChessProgram = !appData.zippyPlay;
1124 appData.zippyPlay = FALSE;
1125 appData.zippyTalk = FALSE;
1126 appData.noChessProgram = TRUE;
1128 if (*appData.icsHelper != NULLCHAR) {
1129 appData.useTelnet = TRUE;
1130 appData.telnetProgram = appData.icsHelper;
1133 appData.zippyTalk = appData.zippyPlay = FALSE;
1136 /* [AS] Initialize pv info list [HGM] and game state */
1140 for( i=0; i<=framePtr; i++ ) {
1141 pvInfoList[i].depth = -1;
1142 boards[i][EP_STATUS] = EP_NONE;
1143 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1149 /* [AS] Adjudication threshold */
1150 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1152 InitEngine(&first, 0);
1153 InitEngine(&second, 1);
1156 pairing.which = "pairing"; // pairing engine
1157 pairing.pr = NoProc;
1159 pairing.program = appData.pairingEngine;
1160 pairing.host = "localhost";
1163 if (appData.icsActive) {
1164 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1165 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1166 appData.clockMode = FALSE;
1167 first.sendTime = second.sendTime = 0;
1171 /* Override some settings from environment variables, for backward
1172 compatibility. Unfortunately it's not feasible to have the env
1173 vars just set defaults, at least in xboard. Ugh.
1175 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1180 if (!appData.icsActive) {
1184 /* Check for variants that are supported only in ICS mode,
1185 or not at all. Some that are accepted here nevertheless
1186 have bugs; see comments below.
1188 VariantClass variant = StringToVariant(appData.variant);
1190 case VariantBughouse: /* need four players and two boards */
1191 case VariantKriegspiel: /* need to hide pieces and move details */
1192 /* case VariantFischeRandom: (Fabien: moved below) */
1193 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1194 if( (len >= MSG_SIZ) && appData.debugMode )
1195 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1197 DisplayFatalError(buf, 0, 2);
1200 case VariantUnknown:
1201 case VariantLoadable:
1211 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1212 if( (len >= MSG_SIZ) && appData.debugMode )
1213 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1215 DisplayFatalError(buf, 0, 2);
1218 case VariantNormal: /* definitely works! */
1219 if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1220 safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1223 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1224 case VariantFairy: /* [HGM] TestLegality definitely off! */
1225 case VariantGothic: /* [HGM] should work */
1226 case VariantCapablanca: /* [HGM] should work */
1227 case VariantCourier: /* [HGM] initial forced moves not implemented */
1228 case VariantShogi: /* [HGM] could still mate with pawn drop */
1229 case VariantChu: /* [HGM] experimental */
1230 case VariantKnightmate: /* [HGM] should work */
1231 case VariantCylinder: /* [HGM] untested */
1232 case VariantFalcon: /* [HGM] untested */
1233 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1234 offboard interposition not understood */
1235 case VariantWildCastle: /* pieces not automatically shuffled */
1236 case VariantNoCastle: /* pieces not automatically shuffled */
1237 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1238 case VariantLosers: /* should work except for win condition,
1239 and doesn't know captures are mandatory */
1240 case VariantSuicide: /* should work except for win condition,
1241 and doesn't know captures are mandatory */
1242 case VariantGiveaway: /* should work except for win condition,
1243 and doesn't know captures are mandatory */
1244 case VariantTwoKings: /* should work */
1245 case VariantAtomic: /* should work except for win condition */
1246 case Variant3Check: /* should work except for win condition */
1247 case VariantShatranj: /* should work except for all win conditions */
1248 case VariantMakruk: /* should work except for draw countdown */
1249 case VariantASEAN : /* should work except for draw countdown */
1250 case VariantBerolina: /* might work if TestLegality is off */
1251 case VariantCapaRandom: /* should work */
1252 case VariantJanus: /* should work */
1253 case VariantSuper: /* experimental */
1254 case VariantGreat: /* experimental, requires legality testing to be off */
1255 case VariantSChess: /* S-Chess, should work */
1256 case VariantGrand: /* should work */
1257 case VariantSpartan: /* should work */
1258 case VariantLion: /* should work */
1259 case VariantChuChess: /* should work */
1267 NextIntegerFromString (char ** str, long * value)
1272 while( *s == ' ' || *s == '\t' ) {
1278 if( *s >= '0' && *s <= '9' ) {
1279 while( *s >= '0' && *s <= '9' ) {
1280 *value = *value * 10 + (*s - '0');
1293 NextTimeControlFromString (char ** str, long * value)
1296 int result = NextIntegerFromString( str, &temp );
1299 *value = temp * 60; /* Minutes */
1300 if( **str == ':' ) {
1302 result = NextIntegerFromString( str, &temp );
1303 *value += temp; /* Seconds */
1311 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1312 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1313 int result = -1, type = 0; long temp, temp2;
1315 if(**str != ':') return -1; // old params remain in force!
1317 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1318 if( NextIntegerFromString( str, &temp ) ) return -1;
1319 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1322 /* time only: incremental or sudden-death time control */
1323 if(**str == '+') { /* increment follows; read it */
1325 if(**str == '!') type = *(*str)++; // Bronstein TC
1326 if(result = NextIntegerFromString( str, &temp2)) return -1;
1327 *inc = temp2 * 1000;
1328 if(**str == '.') { // read fraction of increment
1329 char *start = ++(*str);
1330 if(result = NextIntegerFromString( str, &temp2)) return -1;
1332 while(start++ < *str) temp2 /= 10;
1336 *moves = 0; *tc = temp * 1000; *incType = type;
1340 (*str)++; /* classical time control */
1341 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1353 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1354 { /* [HGM] get time to add from the multi-session time-control string */
1355 int incType, moves=1; /* kludge to force reading of first session */
1356 long time, increment;
1359 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1361 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1362 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1363 if(movenr == -1) return time; /* last move before new session */
1364 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1365 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1366 if(!moves) return increment; /* current session is incremental */
1367 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1368 } while(movenr >= -1); /* try again for next session */
1370 return 0; // no new time quota on this move
1374 ParseTimeControl (char *tc, float ti, int mps)
1378 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1381 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1382 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1383 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1387 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1389 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1392 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1394 snprintf(buf, MSG_SIZ, ":%s", mytc);
1396 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1398 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1403 /* Parse second time control */
1406 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1414 timeControl_2 = tc2 * 1000;
1424 timeControl = tc1 * 1000;
1427 timeIncrement = ti * 1000; /* convert to ms */
1428 movesPerSession = 0;
1431 movesPerSession = mps;
1439 if (appData.debugMode) {
1440 # ifdef __GIT_VERSION
1441 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1443 fprintf(debugFP, "Version: %s\n", programVersion);
1446 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1448 set_cont_sequence(appData.wrapContSeq);
1449 if (appData.matchGames > 0) {
1450 appData.matchMode = TRUE;
1451 } else if (appData.matchMode) {
1452 appData.matchGames = 1;
1454 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1455 appData.matchGames = appData.sameColorGames;
1456 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1457 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1458 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1461 if (appData.noChessProgram || first.protocolVersion == 1) {
1464 /* kludge: allow timeout for initial "feature" commands */
1466 DisplayMessage("", _("Starting chess program"));
1467 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1472 CalculateIndex (int index, int gameNr)
1473 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1475 if(index > 0) return index; // fixed nmber
1476 if(index == 0) return 1;
1477 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1478 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1483 LoadGameOrPosition (int gameNr)
1484 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1485 if (*appData.loadGameFile != NULLCHAR) {
1486 if (!LoadGameFromFile(appData.loadGameFile,
1487 CalculateIndex(appData.loadGameIndex, gameNr),
1488 appData.loadGameFile, FALSE)) {
1489 DisplayFatalError(_("Bad game file"), 0, 1);
1492 } else if (*appData.loadPositionFile != NULLCHAR) {
1493 if (!LoadPositionFromFile(appData.loadPositionFile,
1494 CalculateIndex(appData.loadPositionIndex, gameNr),
1495 appData.loadPositionFile)) {
1496 DisplayFatalError(_("Bad position file"), 0, 1);
1504 ReserveGame (int gameNr, char resChar)
1506 FILE *tf = fopen(appData.tourneyFile, "r+");
1507 char *p, *q, c, buf[MSG_SIZ];
1508 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1509 safeStrCpy(buf, lastMsg, MSG_SIZ);
1510 DisplayMessage(_("Pick new game"), "");
1511 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1512 ParseArgsFromFile(tf);
1513 p = q = appData.results;
1514 if(appData.debugMode) {
1515 char *r = appData.participants;
1516 fprintf(debugFP, "results = '%s'\n", p);
1517 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1518 fprintf(debugFP, "\n");
1520 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1522 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1523 safeStrCpy(q, p, strlen(p) + 2);
1524 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1525 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1526 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1527 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1530 fseek(tf, -(strlen(p)+4), SEEK_END);
1532 if(c != '"') // depending on DOS or Unix line endings we can be one off
1533 fseek(tf, -(strlen(p)+2), SEEK_END);
1534 else fseek(tf, -(strlen(p)+3), SEEK_END);
1535 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1536 DisplayMessage(buf, "");
1537 free(p); appData.results = q;
1538 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1539 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1540 int round = appData.defaultMatchGames * appData.tourneyType;
1541 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1542 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1543 UnloadEngine(&first); // next game belongs to other pairing;
1544 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1546 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1550 MatchEvent (int mode)
1551 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1553 if(matchMode) { // already in match mode: switch it off
1555 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1558 // if(gameMode != BeginningOfGame) {
1559 // DisplayError(_("You can only start a match from the initial position."), 0);
1563 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1564 /* Set up machine vs. machine match */
1566 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1567 if(appData.tourneyFile[0]) {
1569 if(nextGame > appData.matchGames) {
1571 if(strchr(appData.results, '*') == NULL) {
1573 appData.tourneyCycles++;
1574 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1576 NextTourneyGame(-1, &dummy);
1578 if(nextGame <= appData.matchGames) {
1579 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1581 ScheduleDelayedEvent(NextMatchGame, 10000);
1586 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1587 DisplayError(buf, 0);
1588 appData.tourneyFile[0] = 0;
1592 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1593 DisplayFatalError(_("Can't have a match with no chess programs"),
1598 matchGame = roundNr = 1;
1599 first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1604 InitBackEnd3 P((void))
1606 GameMode initialMode;
1610 ParseFeatures(appData.features[0], &first);
1611 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1612 !strcmp(appData.variant, "normal") && // no explicit variant request
1613 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1614 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1615 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1616 char c, *q = first.variants, *p = strchr(q, ',');
1617 if(p) *p = NULLCHAR;
1618 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1620 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1621 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1622 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1623 Reset(TRUE, FALSE); // and re-initialize
1628 InitChessProgram(&first, startedFromSetupPosition);
1630 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1631 free(programVersion);
1632 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1633 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1634 FloatToFront(&appData.recentEngineList, currentEngine[0] ? currentEngine[0] : appData.firstChessProgram);
1637 if (appData.icsActive) {
1639 /* [DM] Make a console window if needed [HGM] merged ifs */
1645 if (*appData.icsCommPort != NULLCHAR)
1646 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1647 appData.icsCommPort);
1649 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1650 appData.icsHost, appData.icsPort);
1652 if( (len >= MSG_SIZ) && appData.debugMode )
1653 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1655 DisplayFatalError(buf, err, 1);
1660 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1662 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1663 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1664 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1665 } else if (appData.noChessProgram) {
1671 if (*appData.cmailGameName != NULLCHAR) {
1673 OpenLoopback(&cmailPR);
1675 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1679 DisplayMessage("", "");
1680 if (StrCaseCmp(appData.initialMode, "") == 0) {
1681 initialMode = BeginningOfGame;
1682 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1683 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1684 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1685 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1688 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1689 initialMode = TwoMachinesPlay;
1690 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1691 initialMode = AnalyzeFile;
1692 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1693 initialMode = AnalyzeMode;
1694 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1695 initialMode = MachinePlaysWhite;
1696 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1697 initialMode = MachinePlaysBlack;
1698 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1699 initialMode = EditGame;
1700 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1701 initialMode = EditPosition;
1702 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1703 initialMode = Training;
1705 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1706 if( (len >= MSG_SIZ) && appData.debugMode )
1707 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1709 DisplayFatalError(buf, 0, 2);
1713 if (appData.matchMode) {
1714 if(appData.tourneyFile[0]) { // start tourney from command line
1716 if(f = fopen(appData.tourneyFile, "r")) {
1717 ParseArgsFromFile(f); // make sure tourney parmeters re known
1719 appData.clockMode = TRUE;
1721 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1724 } else if (*appData.cmailGameName != NULLCHAR) {
1725 /* Set up cmail mode */
1726 ReloadCmailMsgEvent(TRUE);
1728 /* Set up other modes */
1729 if (initialMode == AnalyzeFile) {
1730 if (*appData.loadGameFile == NULLCHAR) {
1731 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1735 if (*appData.loadGameFile != NULLCHAR) {
1736 (void) LoadGameFromFile(appData.loadGameFile,
1737 appData.loadGameIndex,
1738 appData.loadGameFile, TRUE);
1739 } else if (*appData.loadPositionFile != NULLCHAR) {
1740 (void) LoadPositionFromFile(appData.loadPositionFile,
1741 appData.loadPositionIndex,
1742 appData.loadPositionFile);
1743 /* [HGM] try to make self-starting even after FEN load */
1744 /* to allow automatic setup of fairy variants with wtm */
1745 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1746 gameMode = BeginningOfGame;
1747 setboardSpoiledMachineBlack = 1;
1749 /* [HGM] loadPos: make that every new game uses the setup */
1750 /* from file as long as we do not switch variant */
1751 if(!blackPlaysFirst) {
1752 startedFromPositionFile = TRUE;
1753 CopyBoard(filePosition, boards[0]);
1754 CopyBoard(initialPosition, boards[0]);
1756 } else if(*appData.fen != NULLCHAR) {
1757 if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1758 startedFromPositionFile = TRUE;
1762 if (initialMode == AnalyzeMode) {
1763 if (appData.noChessProgram) {
1764 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1767 if (appData.icsActive) {
1768 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1772 } else if (initialMode == AnalyzeFile) {
1773 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1774 ShowThinkingEvent();
1776 AnalysisPeriodicEvent(1);
1777 } else if (initialMode == MachinePlaysWhite) {
1778 if (appData.noChessProgram) {
1779 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1783 if (appData.icsActive) {
1784 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1788 MachineWhiteEvent();
1789 } else if (initialMode == MachinePlaysBlack) {
1790 if (appData.noChessProgram) {
1791 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1795 if (appData.icsActive) {
1796 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1800 MachineBlackEvent();
1801 } else if (initialMode == TwoMachinesPlay) {
1802 if (appData.noChessProgram) {
1803 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1807 if (appData.icsActive) {
1808 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1813 } else if (initialMode == EditGame) {
1815 } else if (initialMode == EditPosition) {
1816 EditPositionEvent();
1817 } else if (initialMode == Training) {
1818 if (*appData.loadGameFile == NULLCHAR) {
1819 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1828 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1830 DisplayBook(current+1);
1832 MoveHistorySet( movelist, first, last, current, pvInfoList );
1834 EvalGraphSet( first, last, current, pvInfoList );
1836 MakeEngineOutputTitle();
1840 * Establish will establish a contact to a remote host.port.
1841 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1842 * used to talk to the host.
1843 * Returns 0 if okay, error code if not.
1850 if (*appData.icsCommPort != NULLCHAR) {
1851 /* Talk to the host through a serial comm port */
1852 return OpenCommPort(appData.icsCommPort, &icsPR);
1854 } else if (*appData.gateway != NULLCHAR) {
1855 if (*appData.remoteShell == NULLCHAR) {
1856 /* Use the rcmd protocol to run telnet program on a gateway host */
1857 snprintf(buf, sizeof(buf), "%s %s %s",
1858 appData.telnetProgram, appData.icsHost, appData.icsPort);
1859 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1862 /* Use the rsh program to run telnet program on a gateway host */
1863 if (*appData.remoteUser == NULLCHAR) {
1864 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1865 appData.gateway, appData.telnetProgram,
1866 appData.icsHost, appData.icsPort);
1868 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1869 appData.remoteShell, appData.gateway,
1870 appData.remoteUser, appData.telnetProgram,
1871 appData.icsHost, appData.icsPort);
1873 return StartChildProcess(buf, "", &icsPR);
1876 } else if (appData.useTelnet) {
1877 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1880 /* TCP socket interface differs somewhat between
1881 Unix and NT; handle details in the front end.
1883 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1888 EscapeExpand (char *p, char *q)
1889 { // [HGM] initstring: routine to shape up string arguments
1890 while(*p++ = *q++) if(p[-1] == '\\')
1892 case 'n': p[-1] = '\n'; break;
1893 case 'r': p[-1] = '\r'; break;
1894 case 't': p[-1] = '\t'; break;
1895 case '\\': p[-1] = '\\'; break;
1896 case 0: *p = 0; return;
1897 default: p[-1] = q[-1]; break;
1902 show_bytes (FILE *fp, char *buf, int count)
1905 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1906 fprintf(fp, "\\%03o", *buf & 0xff);
1915 /* Returns an errno value */
1917 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1919 char buf[8192], *p, *q, *buflim;
1920 int left, newcount, outcount;
1922 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1923 *appData.gateway != NULLCHAR) {
1924 if (appData.debugMode) {
1925 fprintf(debugFP, ">ICS: ");
1926 show_bytes(debugFP, message, count);
1927 fprintf(debugFP, "\n");
1929 return OutputToProcess(pr, message, count, outError);
1932 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1939 if (appData.debugMode) {
1940 fprintf(debugFP, ">ICS: ");
1941 show_bytes(debugFP, buf, newcount);
1942 fprintf(debugFP, "\n");
1944 outcount = OutputToProcess(pr, buf, newcount, outError);
1945 if (outcount < newcount) return -1; /* to be sure */
1952 } else if (((unsigned char) *p) == TN_IAC) {
1953 *q++ = (char) TN_IAC;
1960 if (appData.debugMode) {
1961 fprintf(debugFP, ">ICS: ");
1962 show_bytes(debugFP, buf, newcount);
1963 fprintf(debugFP, "\n");
1965 outcount = OutputToProcess(pr, buf, newcount, outError);
1966 if (outcount < newcount) return -1; /* to be sure */
1971 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1973 int outError, outCount;
1974 static int gotEof = 0;
1977 /* Pass data read from player on to ICS */
1980 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1981 if (outCount < count) {
1982 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1984 if(have_sent_ICS_logon == 2) {
1985 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1986 fprintf(ini, "%s", message);
1987 have_sent_ICS_logon = 3;
1989 have_sent_ICS_logon = 1;
1990 } else if(have_sent_ICS_logon == 3) {
1991 fprintf(ini, "%s", message);
1993 have_sent_ICS_logon = 1;
1995 } else if (count < 0) {
1996 RemoveInputSource(isr);
1997 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1998 } else if (gotEof++ > 0) {
1999 RemoveInputSource(isr);
2000 DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
2006 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
2007 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2008 connectionAlive = FALSE; // only sticks if no response to 'date' command.
2009 SendToICS("date\n");
2010 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2013 /* added routine for printf style output to ics */
2015 ics_printf (char *format, ...)
2017 char buffer[MSG_SIZ];
2020 va_start(args, format);
2021 vsnprintf(buffer, sizeof(buffer), format, args);
2022 buffer[sizeof(buffer)-1] = '\0';
2030 int count, outCount, outError;
2032 if (icsPR == NoProc) return;
2035 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2036 if (outCount < count) {
2037 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2041 /* This is used for sending logon scripts to the ICS. Sending
2042 without a delay causes problems when using timestamp on ICC
2043 (at least on my machine). */
2045 SendToICSDelayed (char *s, long msdelay)
2047 int count, outCount, outError;
2049 if (icsPR == NoProc) return;
2052 if (appData.debugMode) {
2053 fprintf(debugFP, ">ICS: ");
2054 show_bytes(debugFP, s, count);
2055 fprintf(debugFP, "\n");
2057 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2059 if (outCount < count) {
2060 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2065 /* Remove all highlighting escape sequences in s
2066 Also deletes any suffix starting with '('
2069 StripHighlightAndTitle (char *s)
2071 static char retbuf[MSG_SIZ];
2074 while (*s != NULLCHAR) {
2075 while (*s == '\033') {
2076 while (*s != NULLCHAR && !isalpha(*s)) s++;
2077 if (*s != NULLCHAR) s++;
2079 while (*s != NULLCHAR && *s != '\033') {
2080 if (*s == '(' || *s == '[') {
2091 /* Remove all highlighting escape sequences in s */
2093 StripHighlight (char *s)
2095 static char retbuf[MSG_SIZ];
2098 while (*s != NULLCHAR) {
2099 while (*s == '\033') {
2100 while (*s != NULLCHAR && !isalpha(*s)) s++;
2101 if (*s != NULLCHAR) s++;
2103 while (*s != NULLCHAR && *s != '\033') {
2111 char engineVariant[MSG_SIZ];
2112 char *variantNames[] = VARIANT_NAMES;
2114 VariantName (VariantClass v)
2116 if(v == VariantUnknown || *engineVariant) return engineVariant;
2117 return variantNames[v];
2121 /* Identify a variant from the strings the chess servers use or the
2122 PGN Variant tag names we use. */
2124 StringToVariant (char *e)
2128 VariantClass v = VariantNormal;
2129 int i, found = FALSE;
2130 char buf[MSG_SIZ], c;
2135 /* [HGM] skip over optional board-size prefixes */
2136 if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2137 sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2138 while( *e++ != '_');
2141 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2145 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2146 if (p = StrCaseStr(e, variantNames[i])) {
2147 if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2148 v = (VariantClass) i;
2155 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2156 || StrCaseStr(e, "wild/fr")
2157 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2158 v = VariantFischeRandom;
2159 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2160 (i = 1, p = StrCaseStr(e, "w"))) {
2162 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2169 case 0: /* FICS only, actually */
2171 /* Castling legal even if K starts on d-file */
2172 v = VariantWildCastle;
2177 /* Castling illegal even if K & R happen to start in
2178 normal positions. */
2179 v = VariantNoCastle;
2192 /* Castling legal iff K & R start in normal positions */
2198 /* Special wilds for position setup; unclear what to do here */
2199 v = VariantLoadable;
2202 /* Bizarre ICC game */
2203 v = VariantTwoKings;
2206 v = VariantKriegspiel;
2212 v = VariantFischeRandom;
2215 v = VariantCrazyhouse;
2218 v = VariantBughouse;
2224 /* Not quite the same as FICS suicide! */
2225 v = VariantGiveaway;
2231 v = VariantShatranj;
2234 /* Temporary names for future ICC types. The name *will* change in
2235 the next xboard/WinBoard release after ICC defines it. */
2273 v = VariantCapablanca;
2276 v = VariantKnightmate;
2282 v = VariantCylinder;
2288 v = VariantCapaRandom;
2291 v = VariantBerolina;
2303 /* Found "wild" or "w" in the string but no number;
2304 must assume it's normal chess. */
2308 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2309 if( (len >= MSG_SIZ) && appData.debugMode )
2310 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2312 DisplayError(buf, 0);
2318 if (appData.debugMode) {
2319 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2320 e, wnum, VariantName(v));
2325 static int leftover_start = 0, leftover_len = 0;
2326 char star_match[STAR_MATCH_N][MSG_SIZ];
2328 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2329 advance *index beyond it, and set leftover_start to the new value of
2330 *index; else return FALSE. If pattern contains the character '*', it
2331 matches any sequence of characters not containing '\r', '\n', or the
2332 character following the '*' (if any), and the matched sequence(s) are
2333 copied into star_match.
2336 looking_at ( char *buf, int *index, char *pattern)
2338 char *bufp = &buf[*index], *patternp = pattern;
2340 char *matchp = star_match[0];
2343 if (*patternp == NULLCHAR) {
2344 *index = leftover_start = bufp - buf;
2348 if (*bufp == NULLCHAR) return FALSE;
2349 if (*patternp == '*') {
2350 if (*bufp == *(patternp + 1)) {
2352 matchp = star_match[++star_count];
2356 } else if (*bufp == '\n' || *bufp == '\r') {
2358 if (*patternp == NULLCHAR)
2363 *matchp++ = *bufp++;
2367 if (*patternp != *bufp) return FALSE;
2374 SendToPlayer (char *data, int length)
2376 int error, outCount;
2377 outCount = OutputToProcess(NoProc, data, length, &error);
2378 if (outCount < length) {
2379 DisplayFatalError(_("Error writing to display"), error, 1);
2384 PackHolding (char packed[], char *holding)
2394 switch (runlength) {
2405 sprintf(q, "%d", runlength);
2417 /* Telnet protocol requests from the front end */
2419 TelnetRequest (unsigned char ddww, unsigned char option)
2421 unsigned char msg[3];
2422 int outCount, outError;
2424 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2426 if (appData.debugMode) {
2427 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2443 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2452 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2455 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2460 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2462 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2469 if (!appData.icsActive) return;
2470 TelnetRequest(TN_DO, TN_ECHO);
2476 if (!appData.icsActive) return;
2477 TelnetRequest(TN_DONT, TN_ECHO);
2481 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2483 /* put the holdings sent to us by the server on the board holdings area */
2484 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2488 if(gameInfo.holdingsWidth < 2) return;
2489 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2490 return; // prevent overwriting by pre-board holdings
2492 if( (int)lowestPiece >= BlackPawn ) {
2495 holdingsStartRow = handSize-1;
2498 holdingsColumn = BOARD_WIDTH-1;
2499 countsColumn = BOARD_WIDTH-2;
2500 holdingsStartRow = 0;
2504 for(i=0; i<BOARD_RANKS-1; i++) { /* clear holdings */
2505 board[i][holdingsColumn] = EmptySquare;
2506 board[i][countsColumn] = (ChessSquare) 0;
2508 while( (p=*holdings++) != NULLCHAR ) {
2509 piece = CharToPiece( ToUpper(p) );
2510 if(piece == EmptySquare) continue;
2511 /*j = (int) piece - (int) WhitePawn;*/
2512 j = PieceToNumber(piece);
2513 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2514 if(j < 0) continue; /* should not happen */
2515 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2516 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2517 board[holdingsStartRow+j*direction][countsColumn]++;
2523 VariantSwitch (Board board, VariantClass newVariant)
2525 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2526 static Board oldBoard;
2528 startedFromPositionFile = FALSE;
2529 if(gameInfo.variant == newVariant) return;
2531 /* [HGM] This routine is called each time an assignment is made to
2532 * gameInfo.variant during a game, to make sure the board sizes
2533 * are set to match the new variant. If that means adding or deleting
2534 * holdings, we shift the playing board accordingly
2535 * This kludge is needed because in ICS observe mode, we get boards
2536 * of an ongoing game without knowing the variant, and learn about the
2537 * latter only later. This can be because of the move list we requested,
2538 * in which case the game history is refilled from the beginning anyway,
2539 * but also when receiving holdings of a crazyhouse game. In the latter
2540 * case we want to add those holdings to the already received position.
2544 if (appData.debugMode) {
2545 fprintf(debugFP, "Switch board from %s to %s\n",
2546 VariantName(gameInfo.variant), VariantName(newVariant));
2547 setbuf(debugFP, NULL);
2549 shuffleOpenings = 0; /* [HGM] shuffle */
2550 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2554 newWidth = 9; newHeight = 9;
2555 gameInfo.holdingsSize = 7;
2556 case VariantBughouse:
2557 case VariantCrazyhouse:
2558 newHoldingsWidth = 2; break;
2562 newHoldingsWidth = 2;
2563 gameInfo.holdingsSize = 8;
2566 case VariantCapablanca:
2567 case VariantCapaRandom:
2570 newHoldingsWidth = gameInfo.holdingsSize = 0;
2573 if(newWidth != gameInfo.boardWidth ||
2574 newHeight != gameInfo.boardHeight ||
2575 newHoldingsWidth != gameInfo.holdingsWidth ) {
2577 /* shift position to new playing area, if needed */
2578 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2579 for(i=0; i<BOARD_HEIGHT; i++)
2580 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2581 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2583 for(i=0; i<newHeight; i++) {
2584 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2585 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2587 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2588 for(i=0; i<BOARD_HEIGHT; i++)
2589 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2590 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2593 board[HOLDINGS_SET] = 0;
2594 gameInfo.boardWidth = newWidth;
2595 gameInfo.boardHeight = newHeight;
2596 gameInfo.holdingsWidth = newHoldingsWidth;
2597 gameInfo.variant = newVariant;
2598 InitDrawingSizes(-2, 0);
2599 } else gameInfo.variant = newVariant;
2600 CopyBoard(oldBoard, board); // remember correctly formatted board
2601 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2602 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2605 static int loggedOn = FALSE;
2607 /*-- Game start info cache: --*/
2609 char gs_kind[MSG_SIZ];
2610 static char player1Name[128] = "";
2611 static char player2Name[128] = "";
2612 static char cont_seq[] = "\n\\ ";
2613 static int player1Rating = -1;
2614 static int player2Rating = -1;
2615 /*----------------------------*/
2617 ColorClass curColor = ColorNormal;
2618 int suppressKibitz = 0;
2621 Boolean soughtPending = FALSE;
2622 Boolean seekGraphUp;
2623 #define MAX_SEEK_ADS 200
2625 char *seekAdList[MAX_SEEK_ADS];
2626 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2627 float tcList[MAX_SEEK_ADS];
2628 char colorList[MAX_SEEK_ADS];
2629 int nrOfSeekAds = 0;
2630 int minRating = 1010, maxRating = 2800;
2631 int hMargin = 10, vMargin = 20, h, w;
2632 extern int squareSize, lineGap;
2637 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2638 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2639 if(r < minRating+100 && r >=0 ) r = minRating+100;
2640 if(r > maxRating) r = maxRating;
2641 if(tc < 1.f) tc = 1.f;
2642 if(tc > 95.f) tc = 95.f;
2643 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2644 y = ((double)r - minRating)/(maxRating - minRating)
2645 * (h-vMargin-squareSize/8-1) + vMargin;
2646 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2647 if(strstr(seekAdList[i], " u ")) color = 1;
2648 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2649 !strstr(seekAdList[i], "bullet") &&
2650 !strstr(seekAdList[i], "blitz") &&
2651 !strstr(seekAdList[i], "standard") ) color = 2;
2652 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2653 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2657 PlotSingleSeekAd (int i)
2663 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2665 char buf[MSG_SIZ], *ext = "";
2666 VariantClass v = StringToVariant(type);
2667 if(strstr(type, "wild")) {
2668 ext = type + 4; // append wild number
2669 if(v == VariantFischeRandom) type = "chess960"; else
2670 if(v == VariantLoadable) type = "setup"; else
2671 type = VariantName(v);
2673 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2674 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2675 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2676 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2677 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2678 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2679 seekNrList[nrOfSeekAds] = nr;
2680 zList[nrOfSeekAds] = 0;
2681 seekAdList[nrOfSeekAds++] = StrSave(buf);
2682 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2687 EraseSeekDot (int i)
2689 int x = xList[i], y = yList[i], d=squareSize/4, k;
2690 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2691 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2692 // now replot every dot that overlapped
2693 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2694 int xx = xList[k], yy = yList[k];
2695 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2696 DrawSeekDot(xx, yy, colorList[k]);
2701 RemoveSeekAd (int nr)
2704 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2706 if(seekAdList[i]) free(seekAdList[i]);
2707 seekAdList[i] = seekAdList[--nrOfSeekAds];
2708 seekNrList[i] = seekNrList[nrOfSeekAds];
2709 ratingList[i] = ratingList[nrOfSeekAds];
2710 colorList[i] = colorList[nrOfSeekAds];
2711 tcList[i] = tcList[nrOfSeekAds];
2712 xList[i] = xList[nrOfSeekAds];
2713 yList[i] = yList[nrOfSeekAds];
2714 zList[i] = zList[nrOfSeekAds];
2715 seekAdList[nrOfSeekAds] = NULL;
2721 MatchSoughtLine (char *line)
2723 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2724 int nr, base, inc, u=0; char dummy;
2726 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2727 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2729 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2730 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2731 // match: compact and save the line
2732 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2742 if(!seekGraphUp) return FALSE;
2743 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2744 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap + 2*border;
2746 DrawSeekBackground(0, 0, w, h);
2747 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2748 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2749 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2750 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2752 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2755 snprintf(buf, MSG_SIZ, "%d", i);
2756 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2759 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2760 for(i=1; i<100; i+=(i<10?1:5)) {
2761 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2762 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2763 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2765 snprintf(buf, MSG_SIZ, "%d", i);
2766 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2769 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2774 SeekGraphClick (ClickType click, int x, int y, int moving)
2776 static int lastDown = 0, displayed = 0, lastSecond;
2777 if(y < 0) return FALSE;
2778 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2779 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2780 if(!seekGraphUp) return FALSE;
2781 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2782 DrawPosition(TRUE, NULL);
2785 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2786 if(click == Release || moving) return FALSE;
2788 soughtPending = TRUE;
2789 SendToICS(ics_prefix);
2790 SendToICS("sought\n"); // should this be "sought all"?
2791 } else { // issue challenge based on clicked ad
2792 int dist = 10000; int i, closest = 0, second = 0;
2793 for(i=0; i<nrOfSeekAds; i++) {
2794 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2795 if(d < dist) { dist = d; closest = i; }
2796 second += (d - zList[i] < 120); // count in-range ads
2797 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2801 second = (second > 1);
2802 if(displayed != closest || second != lastSecond) {
2803 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2804 lastSecond = second; displayed = closest;
2806 if(click == Press) {
2807 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2810 } // on press 'hit', only show info
2811 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2812 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2813 SendToICS(ics_prefix);
2815 return TRUE; // let incoming board of started game pop down the graph
2816 } else if(click == Release) { // release 'miss' is ignored
2817 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2818 if(moving == 2) { // right up-click
2819 nrOfSeekAds = 0; // refresh graph
2820 soughtPending = TRUE;
2821 SendToICS(ics_prefix);
2822 SendToICS("sought\n"); // should this be "sought all"?
2825 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2826 // press miss or release hit 'pop down' seek graph
2827 seekGraphUp = FALSE;
2828 DrawPosition(TRUE, NULL);
2834 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2836 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2837 #define STARTED_NONE 0
2838 #define STARTED_MOVES 1
2839 #define STARTED_BOARD 2
2840 #define STARTED_OBSERVE 3
2841 #define STARTED_HOLDINGS 4
2842 #define STARTED_CHATTER 5
2843 #define STARTED_COMMENT 6
2844 #define STARTED_MOVES_NOHIDE 7
2846 static int started = STARTED_NONE;
2847 static char parse[20000];
2848 static int parse_pos = 0;
2849 static char buf[BUF_SIZE + 1];
2850 static int firstTime = TRUE, intfSet = FALSE;
2851 static ColorClass prevColor = ColorNormal;
2852 static int savingComment = FALSE;
2853 static int cmatch = 0; // continuation sequence match
2860 int backup; /* [DM] For zippy color lines */
2862 char talker[MSG_SIZ]; // [HGM] chat
2863 int channel, collective=0;
2865 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2867 if (appData.debugMode) {
2869 fprintf(debugFP, "<ICS: ");
2870 show_bytes(debugFP, data, count);
2871 fprintf(debugFP, "\n");
2875 if (appData.debugMode) { int f = forwardMostMove;
2876 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2877 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2878 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2881 /* If last read ended with a partial line that we couldn't parse,
2882 prepend it to the new read and try again. */
2883 if (leftover_len > 0) {
2884 for (i=0; i<leftover_len; i++)
2885 buf[i] = buf[leftover_start + i];
2888 /* copy new characters into the buffer */
2889 bp = buf + leftover_len;
2890 buf_len=leftover_len;
2891 for (i=0; i<count; i++)
2894 if (data[i] == '\r')
2897 // join lines split by ICS?
2898 if (!appData.noJoin)
2901 Joining just consists of finding matches against the
2902 continuation sequence, and discarding that sequence
2903 if found instead of copying it. So, until a match
2904 fails, there's nothing to do since it might be the
2905 complete sequence, and thus, something we don't want
2908 if (data[i] == cont_seq[cmatch])
2911 if (cmatch == strlen(cont_seq))
2913 cmatch = 0; // complete match. just reset the counter
2916 it's possible for the ICS to not include the space
2917 at the end of the last word, making our [correct]
2918 join operation fuse two separate words. the server
2919 does this when the space occurs at the width setting.
2921 if (!buf_len || buf[buf_len-1] != ' ')
2932 match failed, so we have to copy what matched before
2933 falling through and copying this character. In reality,
2934 this will only ever be just the newline character, but
2935 it doesn't hurt to be precise.
2937 strncpy(bp, cont_seq, cmatch);
2949 buf[buf_len] = NULLCHAR;
2950 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2955 while (i < buf_len) {
2956 /* Deal with part of the TELNET option negotiation
2957 protocol. We refuse to do anything beyond the
2958 defaults, except that we allow the WILL ECHO option,
2959 which ICS uses to turn off password echoing when we are
2960 directly connected to it. We reject this option
2961 if localLineEditing mode is on (always on in xboard)
2962 and we are talking to port 23, which might be a real
2963 telnet server that will try to keep WILL ECHO on permanently.
2965 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2966 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2967 unsigned char option;
2969 switch ((unsigned char) buf[++i]) {
2971 if (appData.debugMode)
2972 fprintf(debugFP, "\n<WILL ");
2973 switch (option = (unsigned char) buf[++i]) {
2975 if (appData.debugMode)
2976 fprintf(debugFP, "ECHO ");
2977 /* Reply only if this is a change, according
2978 to the protocol rules. */
2979 if (remoteEchoOption) break;
2980 if (appData.localLineEditing &&
2981 atoi(appData.icsPort) == TN_PORT) {
2982 TelnetRequest(TN_DONT, TN_ECHO);
2985 TelnetRequest(TN_DO, TN_ECHO);
2986 remoteEchoOption = TRUE;
2990 if (appData.debugMode)
2991 fprintf(debugFP, "%d ", option);
2992 /* Whatever this is, we don't want it. */
2993 TelnetRequest(TN_DONT, option);
2998 if (appData.debugMode)
2999 fprintf(debugFP, "\n<WONT ");
3000 switch (option = (unsigned char) buf[++i]) {
3002 if (appData.debugMode)
3003 fprintf(debugFP, "ECHO ");
3004 /* Reply only if this is a change, according
3005 to the protocol rules. */
3006 if (!remoteEchoOption) break;
3008 TelnetRequest(TN_DONT, TN_ECHO);
3009 remoteEchoOption = FALSE;
3012 if (appData.debugMode)
3013 fprintf(debugFP, "%d ", (unsigned char) option);
3014 /* Whatever this is, it must already be turned
3015 off, because we never agree to turn on
3016 anything non-default, so according to the
3017 protocol rules, we don't reply. */
3022 if (appData.debugMode)
3023 fprintf(debugFP, "\n<DO ");
3024 switch (option = (unsigned char) buf[++i]) {
3026 /* Whatever this is, we refuse to do it. */
3027 if (appData.debugMode)
3028 fprintf(debugFP, "%d ", option);
3029 TelnetRequest(TN_WONT, option);
3034 if (appData.debugMode)
3035 fprintf(debugFP, "\n<DONT ");
3036 switch (option = (unsigned char) buf[++i]) {
3038 if (appData.debugMode)
3039 fprintf(debugFP, "%d ", option);
3040 /* Whatever this is, we are already not doing
3041 it, because we never agree to do anything
3042 non-default, so according to the protocol
3043 rules, we don't reply. */
3048 if (appData.debugMode)
3049 fprintf(debugFP, "\n<IAC ");
3050 /* Doubled IAC; pass it through */
3054 if (appData.debugMode)
3055 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3056 /* Drop all other telnet commands on the floor */
3059 if (oldi > next_out)
3060 SendToPlayer(&buf[next_out], oldi - next_out);
3066 /* OK, this at least will *usually* work */
3067 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3071 if (loggedOn && !intfSet) {
3072 if (ics_type == ICS_ICC) {
3073 snprintf(str, MSG_SIZ,
3074 "/set-quietly interface %s\n/set-quietly style 12\n",
3076 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3077 strcat(str, "/set-2 51 1\n/set seek 1\n");
3078 } else if (ics_type == ICS_CHESSNET) {
3079 snprintf(str, MSG_SIZ, "/style 12\n");
3081 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3082 strcat(str, programVersion);
3083 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3084 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3085 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3087 strcat(str, "$iset nohighlight 1\n");
3089 strcat(str, "$iset lock 1\n$style 12\n");
3092 NotifyFrontendLogin();
3096 if (started == STARTED_COMMENT) {
3097 /* Accumulate characters in comment */
3098 parse[parse_pos++] = buf[i];
3099 if (buf[i] == '\n') {
3100 parse[parse_pos] = NULLCHAR;
3101 if(chattingPartner>=0) {
3103 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3104 OutputChatMessage(chattingPartner, mess);
3105 if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3107 talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3108 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3109 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3110 OutputChatMessage(p, mess);
3114 chattingPartner = -1;
3115 if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3118 if(!suppressKibitz) // [HGM] kibitz
3119 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3120 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3121 int nrDigit = 0, nrAlph = 0, j;
3122 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3123 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3124 parse[parse_pos] = NULLCHAR;
3125 // try to be smart: if it does not look like search info, it should go to
3126 // ICS interaction window after all, not to engine-output window.
3127 for(j=0; j<parse_pos; j++) { // count letters and digits
3128 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3129 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3130 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3132 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3133 int depth=0; float score;
3134 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3135 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3136 pvInfoList[forwardMostMove-1].depth = depth;
3137 pvInfoList[forwardMostMove-1].score = 100*score;
3139 OutputKibitz(suppressKibitz, parse);
3142 if(gameMode == IcsObserving) // restore original ICS messages
3143 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3144 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3146 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3147 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3148 SendToPlayer(tmp, strlen(tmp));
3150 next_out = i+1; // [HGM] suppress printing in ICS window
3152 started = STARTED_NONE;
3154 /* Don't match patterns against characters in comment */
3159 if (started == STARTED_CHATTER) {
3160 if (buf[i] != '\n') {
3161 /* Don't match patterns against characters in chatter */
3165 started = STARTED_NONE;
3166 if(suppressKibitz) next_out = i+1;
3169 /* Kludge to deal with rcmd protocol */
3170 if (firstTime && looking_at(buf, &i, "\001*")) {
3171 DisplayFatalError(&buf[1], 0, 1);
3177 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3180 if (appData.debugMode)
3181 fprintf(debugFP, "ics_type %d\n", ics_type);
3184 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3185 ics_type = ICS_FICS;
3187 if (appData.debugMode)
3188 fprintf(debugFP, "ics_type %d\n", ics_type);
3191 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3192 ics_type = ICS_CHESSNET;
3194 if (appData.debugMode)
3195 fprintf(debugFP, "ics_type %d\n", ics_type);
3200 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3201 looking_at(buf, &i, "Logging you in as \"*\"") ||
3202 looking_at(buf, &i, "will be \"*\""))) {
3203 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3207 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3209 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3210 DisplayIcsInteractionTitle(buf);
3211 have_set_title = TRUE;
3214 /* skip finger notes */
3215 if (started == STARTED_NONE &&
3216 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3217 (buf[i] == '1' && buf[i+1] == '0')) &&
3218 buf[i+2] == ':' && buf[i+3] == ' ') {
3219 started = STARTED_CHATTER;
3225 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3226 if(appData.seekGraph) {
3227 if(soughtPending && MatchSoughtLine(buf+i)) {
3228 i = strstr(buf+i, "rated") - buf;
3229 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3230 next_out = leftover_start = i;
3231 started = STARTED_CHATTER;
3232 suppressKibitz = TRUE;
3235 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3236 && looking_at(buf, &i, "* ads displayed")) {
3237 soughtPending = FALSE;
3242 if(appData.autoRefresh) {
3243 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3244 int s = (ics_type == ICS_ICC); // ICC format differs
3246 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3247 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3248 looking_at(buf, &i, "*% "); // eat prompt
3249 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3250 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3251 next_out = i; // suppress
3254 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3255 char *p = star_match[0];
3257 if(seekGraphUp) RemoveSeekAd(atoi(p));
3258 while(*p && *p++ != ' '); // next
3260 looking_at(buf, &i, "*% "); // eat prompt
3261 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3268 /* skip formula vars */
3269 if (started == STARTED_NONE &&
3270 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3271 started = STARTED_CHATTER;
3276 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3277 if (appData.autoKibitz && started == STARTED_NONE &&
3278 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3279 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3280 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3281 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3282 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3283 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3284 suppressKibitz = TRUE;
3285 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3287 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3288 && (gameMode == IcsPlayingWhite)) ||
3289 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3290 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3291 started = STARTED_CHATTER; // own kibitz we simply discard
3293 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3294 parse_pos = 0; parse[0] = NULLCHAR;
3295 savingComment = TRUE;
3296 suppressKibitz = gameMode != IcsObserving ? 2 :
3297 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3301 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3302 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3303 && atoi(star_match[0])) {
3304 // suppress the acknowledgements of our own autoKibitz
3306 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3307 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3308 SendToPlayer(star_match[0], strlen(star_match[0]));
3309 if(looking_at(buf, &i, "*% ")) // eat prompt
3310 suppressKibitz = FALSE;
3314 } // [HGM] kibitz: end of patch
3316 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3318 // [HGM] chat: intercept tells by users for which we have an open chat window
3320 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3321 looking_at(buf, &i, "* whispers:") ||
3322 looking_at(buf, &i, "* kibitzes:") ||
3323 looking_at(buf, &i, "* shouts:") ||
3324 looking_at(buf, &i, "* c-shouts:") ||
3325 looking_at(buf, &i, "--> * ") ||
3326 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3327 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3328 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3329 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3331 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3332 chattingPartner = -1; collective = 0;
3334 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3335 for(p=0; p<MAX_CHAT; p++) {
3337 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3338 talker[0] = '['; strcat(talker, "] ");
3339 Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3340 chattingPartner = p; break;
3343 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3344 for(p=0; p<MAX_CHAT; p++) {
3346 if(!strcmp("kibitzes", chatPartner[p])) {
3347 talker[0] = '['; strcat(talker, "] ");
3348 chattingPartner = p; break;
3351 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3352 for(p=0; p<MAX_CHAT; p++) {
3354 if(!strcmp("whispers", chatPartner[p])) {
3355 talker[0] = '['; strcat(talker, "] ");
3356 chattingPartner = p; break;
3359 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3360 if(buf[i-8] == '-' && buf[i-3] == 't')
3361 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3363 if(!strcmp("c-shouts", chatPartner[p])) {
3364 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3365 chattingPartner = p; break;
3368 if(chattingPartner < 0)
3369 for(p=0; p<MAX_CHAT; p++) {
3371 if(!strcmp("shouts", chatPartner[p])) {
3372 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3373 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3374 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3375 chattingPartner = p; break;
3379 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3380 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3382 Colorize(ColorTell, FALSE);
3383 if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3385 chattingPartner = p; break;
3387 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3388 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3389 started = STARTED_COMMENT;
3390 parse_pos = 0; parse[0] = NULLCHAR;
3391 savingComment = 3 + chattingPartner; // counts as TRUE
3392 if(collective == 3) i = oldi; else {
3393 suppressKibitz = TRUE;
3394 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3395 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3399 } // [HGM] chat: end of patch
3402 if (appData.zippyTalk || appData.zippyPlay) {
3403 /* [DM] Backup address for color zippy lines */
3405 if (loggedOn == TRUE)
3406 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3407 (appData.zippyPlay && ZippyMatch(buf, &backup)))
3410 } // [DM] 'else { ' deleted
3412 /* Regular tells and says */
3413 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3414 looking_at(buf, &i, "* (your partner) tells you: ") ||
3415 looking_at(buf, &i, "* says: ") ||
3416 /* Don't color "message" or "messages" output */
3417 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3418 looking_at(buf, &i, "*. * at *:*: ") ||
3419 looking_at(buf, &i, "--* (*:*): ") ||
3420 /* Message notifications (same color as tells) */
3421 looking_at(buf, &i, "* has left a message ") ||
3422 looking_at(buf, &i, "* just sent you a message:\n") ||
3423 /* Whispers and kibitzes */
3424 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3425 looking_at(buf, &i, "* kibitzes: ") ||
3427 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3429 if (tkind == 1 && strchr(star_match[0], ':')) {
3430 /* Avoid "tells you:" spoofs in channels */
3433 if (star_match[0][0] == NULLCHAR ||
3434 strchr(star_match[0], ' ') ||
3435 (tkind == 3 && strchr(star_match[1], ' '))) {
3436 /* Reject bogus matches */
3439 if (appData.colorize) {
3440 if (oldi > next_out) {
3441 SendToPlayer(&buf[next_out], oldi - next_out);
3446 Colorize(ColorTell, FALSE);
3447 curColor = ColorTell;
3450 Colorize(ColorKibitz, FALSE);
3451 curColor = ColorKibitz;
3454 p = strrchr(star_match[1], '(');
3461 Colorize(ColorChannel1, FALSE);
3462 curColor = ColorChannel1;
3464 Colorize(ColorChannel, FALSE);
3465 curColor = ColorChannel;
3469 curColor = ColorNormal;
3473 if (started == STARTED_NONE && appData.autoComment &&
3474 (gameMode == IcsObserving ||
3475 gameMode == IcsPlayingWhite ||
3476 gameMode == IcsPlayingBlack)) {
3477 parse_pos = i - oldi;
3478 memcpy(parse, &buf[oldi], parse_pos);
3479 parse[parse_pos] = NULLCHAR;
3480 started = STARTED_COMMENT;
3481 savingComment = TRUE;
3482 } else if(collective != 3) {
3483 started = STARTED_CHATTER;
3484 savingComment = FALSE;
3491 if (looking_at(buf, &i, "* s-shouts: ") ||
3492 looking_at(buf, &i, "* c-shouts: ")) {
3493 if (appData.colorize) {
3494 if (oldi > next_out) {
3495 SendToPlayer(&buf[next_out], oldi - next_out);
3498 Colorize(ColorSShout, FALSE);
3499 curColor = ColorSShout;
3502 started = STARTED_CHATTER;
3506 if (looking_at(buf, &i, "--->")) {
3511 if (looking_at(buf, &i, "* shouts: ") ||
3512 looking_at(buf, &i, "--> ")) {
3513 if (appData.colorize) {
3514 if (oldi > next_out) {
3515 SendToPlayer(&buf[next_out], oldi - next_out);
3518 Colorize(ColorShout, FALSE);
3519 curColor = ColorShout;
3522 started = STARTED_CHATTER;
3526 if (looking_at( buf, &i, "Challenge:")) {
3527 if (appData.colorize) {
3528 if (oldi > next_out) {
3529 SendToPlayer(&buf[next_out], oldi - next_out);
3532 Colorize(ColorChallenge, FALSE);
3533 curColor = ColorChallenge;
3539 if (looking_at(buf, &i, "* offers you") ||
3540 looking_at(buf, &i, "* offers to be") ||
3541 looking_at(buf, &i, "* would like to") ||
3542 looking_at(buf, &i, "* requests to") ||
3543 looking_at(buf, &i, "Your opponent offers") ||
3544 looking_at(buf, &i, "Your opponent requests")) {
3546 if (appData.colorize) {
3547 if (oldi > next_out) {
3548 SendToPlayer(&buf[next_out], oldi - next_out);
3551 Colorize(ColorRequest, FALSE);
3552 curColor = ColorRequest;
3557 if (looking_at(buf, &i, "* (*) seeking")) {
3558 if (appData.colorize) {
3559 if (oldi > next_out) {
3560 SendToPlayer(&buf[next_out], oldi - next_out);
3563 Colorize(ColorSeek, FALSE);
3564 curColor = ColorSeek;
3569 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3571 if (looking_at(buf, &i, "\\ ")) {
3572 if (prevColor != ColorNormal) {
3573 if (oldi > next_out) {
3574 SendToPlayer(&buf[next_out], oldi - next_out);
3577 Colorize(prevColor, TRUE);
3578 curColor = prevColor;
3580 if (savingComment) {
3581 parse_pos = i - oldi;
3582 memcpy(parse, &buf[oldi], parse_pos);
3583 parse[parse_pos] = NULLCHAR;
3584 started = STARTED_COMMENT;
3585 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3586 chattingPartner = savingComment - 3; // kludge to remember the box
3588 started = STARTED_CHATTER;
3593 if (looking_at(buf, &i, "Black Strength :") ||
3594 looking_at(buf, &i, "<<< style 10 board >>>") ||
3595 looking_at(buf, &i, "<10>") ||
3596 looking_at(buf, &i, "#@#")) {
3597 /* Wrong board style */
3599 SendToICS(ics_prefix);
3600 SendToICS("set style 12\n");
3601 SendToICS(ics_prefix);
3602 SendToICS("refresh\n");
3606 if (looking_at(buf, &i, "login:")) {
3607 if (!have_sent_ICS_logon) {
3609 have_sent_ICS_logon = 1;
3610 else // no init script was found
3611 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3612 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3613 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3618 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3619 (looking_at(buf, &i, "\n<12> ") ||
3620 looking_at(buf, &i, "<12> "))) {
3622 if (oldi > next_out) {
3623 SendToPlayer(&buf[next_out], oldi - next_out);
3626 started = STARTED_BOARD;
3631 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3632 looking_at(buf, &i, "<b1> ")) {
3633 if (oldi > next_out) {
3634 SendToPlayer(&buf[next_out], oldi - next_out);
3637 started = STARTED_HOLDINGS;
3642 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3644 /* Header for a move list -- first line */
3646 switch (ics_getting_history) {
3650 case BeginningOfGame:
3651 /* User typed "moves" or "oldmoves" while we
3652 were idle. Pretend we asked for these
3653 moves and soak them up so user can step
3654 through them and/or save them.
3657 gameMode = IcsObserving;
3660 ics_getting_history = H_GOT_UNREQ_HEADER;
3662 case EditGame: /*?*/
3663 case EditPosition: /*?*/
3664 /* Should above feature work in these modes too? */
3665 /* For now it doesn't */
3666 ics_getting_history = H_GOT_UNWANTED_HEADER;
3669 ics_getting_history = H_GOT_UNWANTED_HEADER;
3674 /* Is this the right one? */
3675 if (gameInfo.white && gameInfo.black &&
3676 strcmp(gameInfo.white, star_match[0]) == 0 &&
3677 strcmp(gameInfo.black, star_match[2]) == 0) {
3679 ics_getting_history = H_GOT_REQ_HEADER;
3682 case H_GOT_REQ_HEADER:
3683 case H_GOT_UNREQ_HEADER:
3684 case H_GOT_UNWANTED_HEADER:
3685 case H_GETTING_MOVES:
3686 /* Should not happen */
3687 DisplayError(_("Error gathering move list: two headers"), 0);
3688 ics_getting_history = H_FALSE;
3692 /* Save player ratings into gameInfo if needed */
3693 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3694 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3695 (gameInfo.whiteRating == -1 ||
3696 gameInfo.blackRating == -1)) {
3698 gameInfo.whiteRating = string_to_rating(star_match[1]);
3699 gameInfo.blackRating = string_to_rating(star_match[3]);
3700 if (appData.debugMode)
3701 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3702 gameInfo.whiteRating, gameInfo.blackRating);
3707 if (looking_at(buf, &i,
3708 "* * match, initial time: * minute*, increment: * second")) {
3709 /* Header for a move list -- second line */
3710 /* Initial board will follow if this is a wild game */
3711 if (gameInfo.event != NULL) free(gameInfo.event);
3712 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3713 gameInfo.event = StrSave(str);
3714 /* [HGM] we switched variant. Translate boards if needed. */
3715 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3719 if (looking_at(buf, &i, "Move ")) {
3720 /* Beginning of a move list */
3721 switch (ics_getting_history) {
3723 /* Normally should not happen */
3724 /* Maybe user hit reset while we were parsing */
3727 /* Happens if we are ignoring a move list that is not
3728 * the one we just requested. Common if the user
3729 * tries to observe two games without turning off
3732 case H_GETTING_MOVES:
3733 /* Should not happen */
3734 DisplayError(_("Error gathering move list: nested"), 0);
3735 ics_getting_history = H_FALSE;
3737 case H_GOT_REQ_HEADER:
3738 ics_getting_history = H_GETTING_MOVES;
3739 started = STARTED_MOVES;
3741 if (oldi > next_out) {
3742 SendToPlayer(&buf[next_out], oldi - next_out);
3745 case H_GOT_UNREQ_HEADER:
3746 ics_getting_history = H_GETTING_MOVES;
3747 started = STARTED_MOVES_NOHIDE;
3750 case H_GOT_UNWANTED_HEADER:
3751 ics_getting_history = H_FALSE;
3757 if (looking_at(buf, &i, "% ") ||
3758 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3759 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3760 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3761 soughtPending = FALSE;
3765 if(suppressKibitz) next_out = i;
3766 savingComment = FALSE;
3770 case STARTED_MOVES_NOHIDE:
3771 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3772 parse[parse_pos + i - oldi] = NULLCHAR;
3773 ParseGameHistory(parse);
3775 if (appData.zippyPlay && first.initDone) {
3776 FeedMovesToProgram(&first, forwardMostMove);
3777 if (gameMode == IcsPlayingWhite) {
3778 if (WhiteOnMove(forwardMostMove)) {
3779 if (first.sendTime) {
3780 if (first.useColors) {
3781 SendToProgram("black\n", &first);
3783 SendTimeRemaining(&first, TRUE);
3785 if (first.useColors) {
3786 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3788 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3789 first.maybeThinking = TRUE;
3791 if (first.usePlayother) {
3792 if (first.sendTime) {
3793 SendTimeRemaining(&first, TRUE);
3795 SendToProgram("playother\n", &first);
3801 } else if (gameMode == IcsPlayingBlack) {
3802 if (!WhiteOnMove(forwardMostMove)) {
3803 if (first.sendTime) {
3804 if (first.useColors) {
3805 SendToProgram("white\n", &first);
3807 SendTimeRemaining(&first, FALSE);
3809 if (first.useColors) {
3810 SendToProgram("black\n", &first);
3812 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3813 first.maybeThinking = TRUE;
3815 if (first.usePlayother) {
3816 if (first.sendTime) {
3817 SendTimeRemaining(&first, FALSE);
3819 SendToProgram("playother\n", &first);
3828 if (gameMode == IcsObserving && ics_gamenum == -1) {
3829 /* Moves came from oldmoves or moves command
3830 while we weren't doing anything else.
3832 currentMove = forwardMostMove;
3833 ClearHighlights();/*!!could figure this out*/
3834 flipView = appData.flipView;
3835 DrawPosition(TRUE, boards[currentMove]);
3836 DisplayBothClocks();
3837 snprintf(str, MSG_SIZ, "%s %s %s",
3838 gameInfo.white, _("vs."), gameInfo.black);
3842 /* Moves were history of an active game */
3843 if (gameInfo.resultDetails != NULL) {
3844 free(gameInfo.resultDetails);
3845 gameInfo.resultDetails = NULL;
3848 HistorySet(parseList, backwardMostMove,
3849 forwardMostMove, currentMove-1);
3850 DisplayMove(currentMove - 1);
3851 if (started == STARTED_MOVES) next_out = i;
3852 started = STARTED_NONE;
3853 ics_getting_history = H_FALSE;
3856 case STARTED_OBSERVE:
3857 started = STARTED_NONE;
3858 SendToICS(ics_prefix);
3859 SendToICS("refresh\n");
3865 if(bookHit) { // [HGM] book: simulate book reply
3866 static char bookMove[MSG_SIZ]; // a bit generous?
3868 programStats.nodes = programStats.depth = programStats.time =
3869 programStats.score = programStats.got_only_move = 0;
3870 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3872 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3873 strcat(bookMove, bookHit);
3874 HandleMachineMove(bookMove, &first);
3879 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3880 started == STARTED_HOLDINGS ||
3881 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3882 /* Accumulate characters in move list or board */
3883 parse[parse_pos++] = buf[i];
3886 /* Start of game messages. Mostly we detect start of game
3887 when the first board image arrives. On some versions
3888 of the ICS, though, we need to do a "refresh" after starting
3889 to observe in order to get the current board right away. */
3890 if (looking_at(buf, &i, "Adding game * to observation list")) {
3891 started = STARTED_OBSERVE;
3895 /* Handle auto-observe */
3896 if (appData.autoObserve &&
3897 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3898 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3900 /* Choose the player that was highlighted, if any. */
3901 if (star_match[0][0] == '\033' ||
3902 star_match[1][0] != '\033') {
3903 player = star_match[0];
3905 player = star_match[2];
3907 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3908 ics_prefix, StripHighlightAndTitle(player));
3911 /* Save ratings from notify string */
3912 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3913 player1Rating = string_to_rating(star_match[1]);
3914 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3915 player2Rating = string_to_rating(star_match[3]);
3917 if (appData.debugMode)
3919 "Ratings from 'Game notification:' %s %d, %s %d\n",
3920 player1Name, player1Rating,
3921 player2Name, player2Rating);
3926 /* Deal with automatic examine mode after a game,
3927 and with IcsObserving -> IcsExamining transition */
3928 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3929 looking_at(buf, &i, "has made you an examiner of game *")) {
3931 int gamenum = atoi(star_match[0]);
3932 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3933 gamenum == ics_gamenum) {
3934 /* We were already playing or observing this game;
3935 no need to refetch history */
3936 gameMode = IcsExamining;
3938 pauseExamForwardMostMove = forwardMostMove;
3939 } else if (currentMove < forwardMostMove) {
3940 ForwardInner(forwardMostMove);
3943 /* I don't think this case really can happen */
3944 SendToICS(ics_prefix);
3945 SendToICS("refresh\n");
3950 /* Error messages */
3951 // if (ics_user_moved) {
3952 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3953 if (looking_at(buf, &i, "Illegal move") ||
3954 looking_at(buf, &i, "Not a legal move") ||
3955 looking_at(buf, &i, "Your king is in check") ||
3956 looking_at(buf, &i, "It isn't your turn") ||
3957 looking_at(buf, &i, "It is not your move")) {
3959 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3960 currentMove = forwardMostMove-1;
3961 DisplayMove(currentMove - 1); /* before DMError */
3962 DrawPosition(FALSE, boards[currentMove]);
3963 SwitchClocks(forwardMostMove-1); // [HGM] race
3964 DisplayBothClocks();
3966 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3972 if (looking_at(buf, &i, "still have time") ||
3973 looking_at(buf, &i, "not out of time") ||
3974 looking_at(buf, &i, "either player is out of time") ||
3975 looking_at(buf, &i, "has timeseal; checking")) {
3976 /* We must have called his flag a little too soon */
3977 whiteFlag = blackFlag = FALSE;
3981 if (looking_at(buf, &i, "added * seconds to") ||
3982 looking_at(buf, &i, "seconds were added to")) {
3983 /* Update the clocks */
3984 SendToICS(ics_prefix);
3985 SendToICS("refresh\n");
3989 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3990 ics_clock_paused = TRUE;
3995 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3996 ics_clock_paused = FALSE;
4001 /* Grab player ratings from the Creating: message.
4002 Note we have to check for the special case when
4003 the ICS inserts things like [white] or [black]. */
4004 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
4005 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
4007 0 player 1 name (not necessarily white)
4009 2 empty, white, or black (IGNORED)
4010 3 player 2 name (not necessarily black)
4013 The names/ratings are sorted out when the game
4014 actually starts (below).
4016 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4017 player1Rating = string_to_rating(star_match[1]);
4018 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4019 player2Rating = string_to_rating(star_match[4]);
4021 if (appData.debugMode)
4023 "Ratings from 'Creating:' %s %d, %s %d\n",
4024 player1Name, player1Rating,
4025 player2Name, player2Rating);
4030 /* Improved generic start/end-of-game messages */
4031 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4032 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4033 /* If tkind == 0: */
4034 /* star_match[0] is the game number */
4035 /* [1] is the white player's name */
4036 /* [2] is the black player's name */
4037 /* For end-of-game: */
4038 /* [3] is the reason for the game end */
4039 /* [4] is a PGN end game-token, preceded by " " */
4040 /* For start-of-game: */
4041 /* [3] begins with "Creating" or "Continuing" */
4042 /* [4] is " *" or empty (don't care). */
4043 int gamenum = atoi(star_match[0]);
4044 char *whitename, *blackname, *why, *endtoken;
4045 ChessMove endtype = EndOfFile;
4048 whitename = star_match[1];
4049 blackname = star_match[2];
4050 why = star_match[3];
4051 endtoken = star_match[4];
4053 whitename = star_match[1];
4054 blackname = star_match[3];
4055 why = star_match[5];
4056 endtoken = star_match[6];
4059 /* Game start messages */
4060 if (strncmp(why, "Creating ", 9) == 0 ||
4061 strncmp(why, "Continuing ", 11) == 0) {
4062 gs_gamenum = gamenum;
4063 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4064 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4065 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4067 if (appData.zippyPlay) {
4068 ZippyGameStart(whitename, blackname);
4071 partnerBoardValid = FALSE; // [HGM] bughouse
4075 /* Game end messages */
4076 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4077 ics_gamenum != gamenum) {
4080 while (endtoken[0] == ' ') endtoken++;
4081 switch (endtoken[0]) {
4084 endtype = GameUnfinished;
4087 endtype = BlackWins;
4090 if (endtoken[1] == '/')
4091 endtype = GameIsDrawn;
4093 endtype = WhiteWins;
4096 GameEnds(endtype, why, GE_ICS);
4098 if (appData.zippyPlay && first.initDone) {
4099 ZippyGameEnd(endtype, why);
4100 if (first.pr == NoProc) {
4101 /* Start the next process early so that we'll
4102 be ready for the next challenge */
4103 StartChessProgram(&first);
4105 /* Send "new" early, in case this command takes
4106 a long time to finish, so that we'll be ready
4107 for the next challenge. */
4108 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4112 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4116 if (looking_at(buf, &i, "Removing game * from observation") ||
4117 looking_at(buf, &i, "no longer observing game *") ||
4118 looking_at(buf, &i, "Game * (*) has no examiners")) {
4119 if (gameMode == IcsObserving &&
4120 atoi(star_match[0]) == ics_gamenum)
4122 /* icsEngineAnalyze */
4123 if (appData.icsEngineAnalyze) {
4130 ics_user_moved = FALSE;
4135 if (looking_at(buf, &i, "no longer examining game *")) {
4136 if (gameMode == IcsExamining &&
4137 atoi(star_match[0]) == ics_gamenum)
4141 ics_user_moved = FALSE;
4146 /* Advance leftover_start past any newlines we find,
4147 so only partial lines can get reparsed */
4148 if (looking_at(buf, &i, "\n")) {
4149 prevColor = curColor;
4150 if (curColor != ColorNormal) {
4151 if (oldi > next_out) {
4152 SendToPlayer(&buf[next_out], oldi - next_out);
4155 Colorize(ColorNormal, FALSE);
4156 curColor = ColorNormal;
4158 if (started == STARTED_BOARD) {
4159 started = STARTED_NONE;
4160 parse[parse_pos] = NULLCHAR;
4161 ParseBoard12(parse);
4164 /* Send premove here */
4165 if (appData.premove) {
4167 if (currentMove == 0 &&
4168 gameMode == IcsPlayingWhite &&
4169 appData.premoveWhite) {
4170 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4171 if (appData.debugMode)
4172 fprintf(debugFP, "Sending premove:\n");
4174 } else if (currentMove == 1 &&
4175 gameMode == IcsPlayingBlack &&
4176 appData.premoveBlack) {
4177 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4178 if (appData.debugMode)
4179 fprintf(debugFP, "Sending premove:\n");
4181 } else if (gotPremove) {
4182 int oldFMM = forwardMostMove;
4184 ClearPremoveHighlights();
4185 if (appData.debugMode)
4186 fprintf(debugFP, "Sending premove:\n");
4187 UserMoveEvent(premoveFromX, premoveFromY,
4188 premoveToX, premoveToY,
4190 if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4191 if(moveList[oldFMM-1][1] != '@')
4192 SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4193 moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4195 SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4200 /* Usually suppress following prompt */
4201 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4202 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4203 if (looking_at(buf, &i, "*% ")) {
4204 savingComment = FALSE;
4209 } else if (started == STARTED_HOLDINGS) {
4211 char new_piece[MSG_SIZ];
4212 started = STARTED_NONE;
4213 parse[parse_pos] = NULLCHAR;
4214 if (appData.debugMode)
4215 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4216 parse, currentMove);
4217 if (sscanf(parse, " game %d", &gamenum) == 1) {
4218 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4219 new_piece[0] = NULLCHAR;
4220 sscanf(parse, "game %d white [%s black [%s <- %s",
4221 &gamenum, white_holding, black_holding,
4223 white_holding[strlen(white_holding)-1] = NULLCHAR;
4224 black_holding[strlen(black_holding)-1] = NULLCHAR;
4225 if (gameInfo.variant == VariantNormal) {
4226 /* [HGM] We seem to switch variant during a game!
4227 * Presumably no holdings were displayed, so we have
4228 * to move the position two files to the right to
4229 * create room for them!
4231 VariantClass newVariant;
4232 switch(gameInfo.boardWidth) { // base guess on board width
4233 case 9: newVariant = VariantShogi; break;
4234 case 10: newVariant = VariantGreat; break;
4235 default: newVariant = VariantCrazyhouse;
4236 if(strchr(white_holding, 'E') || strchr(black_holding, 'E') ||
4237 strchr(white_holding, 'H') || strchr(black_holding, 'H') )
4238 newVariant = VariantSChess;
4240 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4241 /* Get a move list just to see the header, which
4242 will tell us whether this is really bug or zh */
4243 if (ics_getting_history == H_FALSE) {
4244 ics_getting_history = H_REQUESTED;
4245 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4249 /* [HGM] copy holdings to board holdings area */
4250 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4251 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4252 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4254 if (appData.zippyPlay && first.initDone) {
4255 ZippyHoldings(white_holding, black_holding,
4259 if (tinyLayout || smallLayout) {
4260 char wh[16], bh[16];
4261 PackHolding(wh, white_holding);
4262 PackHolding(bh, black_holding);
4263 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4264 gameInfo.white, gameInfo.black);
4266 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4267 gameInfo.white, white_holding, _("vs."),
4268 gameInfo.black, black_holding);
4270 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4271 DrawPosition(FALSE, boards[currentMove]);
4273 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4274 sscanf(parse, "game %d white [%s black [%s <- %s",
4275 &gamenum, white_holding, black_holding,
4277 white_holding[strlen(white_holding)-1] = NULLCHAR;
4278 black_holding[strlen(black_holding)-1] = NULLCHAR;
4279 /* [HGM] copy holdings to partner-board holdings area */
4280 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4281 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4282 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4283 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4284 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4287 /* Suppress following prompt */
4288 if (looking_at(buf, &i, "*% ")) {
4289 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4290 savingComment = FALSE;
4298 i++; /* skip unparsed character and loop back */
4301 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4302 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4303 // SendToPlayer(&buf[next_out], i - next_out);
4304 started != STARTED_HOLDINGS && leftover_start > next_out) {
4305 SendToPlayer(&buf[next_out], leftover_start - next_out);
4309 leftover_len = buf_len - leftover_start;
4310 /* if buffer ends with something we couldn't parse,
4311 reparse it after appending the next read */
4313 } else if (count == 0) {
4314 RemoveInputSource(isr);
4315 DisplayFatalError(_("Connection closed by ICS"), 0, 6666);
4317 DisplayFatalError(_("Error reading from ICS"), error, 1);
4322 /* Board style 12 looks like this:
4324 <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
4326 * The "<12> " is stripped before it gets to this routine. The two
4327 * trailing 0's (flip state and clock ticking) are later addition, and
4328 * some chess servers may not have them, or may have only the first.
4329 * Additional trailing fields may be added in the future.
4332 #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"
4334 #define RELATION_OBSERVING_PLAYED 0
4335 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4336 #define RELATION_PLAYING_MYMOVE 1
4337 #define RELATION_PLAYING_NOTMYMOVE -1
4338 #define RELATION_EXAMINING 2
4339 #define RELATION_ISOLATED_BOARD -3
4340 #define RELATION_STARTING_POSITION -4 /* FICS only */
4343 ParseBoard12 (char *string)
4347 char *bookHit = NULL; // [HGM] book
4349 GameMode newGameMode;
4350 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4351 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4352 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4353 char to_play, board_chars[200];
4354 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4355 char black[32], white[32];
4357 int prevMove = currentMove;
4360 int fromX, fromY, toX, toY;
4362 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4363 Boolean weird = FALSE, reqFlag = FALSE;
4365 fromX = fromY = toX = toY = -1;
4369 if (appData.debugMode)
4370 fprintf(debugFP, "Parsing board: %s\n", string);
4372 move_str[0] = NULLCHAR;
4373 elapsed_time[0] = NULLCHAR;
4374 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4376 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4377 if(string[i] == ' ') { ranks++; files = 0; }
4379 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4382 for(j = 0; j <i; j++) board_chars[j] = string[j];
4383 board_chars[i] = '\0';
4386 n = sscanf(string, PATTERN, &to_play, &double_push,
4387 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4388 &gamenum, white, black, &relation, &basetime, &increment,
4389 &white_stren, &black_stren, &white_time, &black_time,
4390 &moveNum, str, elapsed_time, move_str, &ics_flip,
4394 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4395 DisplayError(str, 0);
4399 /* Convert the move number to internal form */
4400 moveNum = (moveNum - 1) * 2;
4401 if (to_play == 'B') moveNum++;
4402 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4403 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4409 case RELATION_OBSERVING_PLAYED:
4410 case RELATION_OBSERVING_STATIC:
4411 if (gamenum == -1) {
4412 /* Old ICC buglet */
4413 relation = RELATION_OBSERVING_STATIC;
4415 newGameMode = IcsObserving;
4417 case RELATION_PLAYING_MYMOVE:
4418 case RELATION_PLAYING_NOTMYMOVE:
4420 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4421 IcsPlayingWhite : IcsPlayingBlack;
4422 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4424 case RELATION_EXAMINING:
4425 newGameMode = IcsExamining;
4427 case RELATION_ISOLATED_BOARD:
4429 /* Just display this board. If user was doing something else,
4430 we will forget about it until the next board comes. */
4431 newGameMode = IcsIdle;
4433 case RELATION_STARTING_POSITION:
4434 newGameMode = gameMode;
4438 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4439 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4440 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4441 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4442 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4443 static int lastBgGame = -1;
4445 for (k = 0; k < ranks; k++) {
4446 for (j = 0; j < files; j++)
4447 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4448 if(gameInfo.holdingsWidth > 1) {
4449 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4450 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4453 CopyBoard(partnerBoard, board);
4454 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4455 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4456 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4457 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4458 if(toSqr = strchr(str, '-')) {
4459 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4460 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4461 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4462 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4463 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4464 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4466 DisplayWhiteClock(white_time*fac, to_play == 'W');
4467 DisplayBlackClock(black_time*fac, to_play != 'W');
4468 activePartner = to_play;
4469 if(gamenum != lastBgGame) {
4471 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4474 lastBgGame = gamenum;
4475 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4476 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4477 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4478 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4479 if(!twoBoards) DisplayMessage(partnerStatus, "");
4480 partnerBoardValid = TRUE;
4484 if(appData.dualBoard && appData.bgObserve) {
4485 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4486 SendToICS(ics_prefix), SendToICS("pobserve\n");
4487 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4489 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4494 /* Modify behavior for initial board display on move listing
4497 switch (ics_getting_history) {
4501 case H_GOT_REQ_HEADER:
4502 case H_GOT_UNREQ_HEADER:
4503 /* This is the initial position of the current game */
4504 gamenum = ics_gamenum;
4505 moveNum = 0; /* old ICS bug workaround */
4506 if (to_play == 'B') {
4507 startedFromSetupPosition = TRUE;
4508 blackPlaysFirst = TRUE;
4510 if (forwardMostMove == 0) forwardMostMove = 1;
4511 if (backwardMostMove == 0) backwardMostMove = 1;
4512 if (currentMove == 0) currentMove = 1;
4514 newGameMode = gameMode;
4515 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4517 case H_GOT_UNWANTED_HEADER:
4518 /* This is an initial board that we don't want */
4520 case H_GETTING_MOVES:
4521 /* Should not happen */
4522 DisplayError(_("Error gathering move list: extra board"), 0);
4523 ics_getting_history = H_FALSE;
4527 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4528 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4529 weird && (int)gameInfo.variant < (int)VariantShogi) {
4530 /* [HGM] We seem to have switched variant unexpectedly
4531 * Try to guess new variant from board size
4533 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4534 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4535 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4536 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4537 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4538 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4539 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4540 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4541 /* Get a move list just to see the header, which
4542 will tell us whether this is really bug or zh */
4543 if (ics_getting_history == H_FALSE) {
4544 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4545 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4550 /* Take action if this is the first board of a new game, or of a
4551 different game than is currently being displayed. */
4552 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4553 relation == RELATION_ISOLATED_BOARD) {
4555 /* Forget the old game and get the history (if any) of the new one */
4556 if (gameMode != BeginningOfGame) {
4560 if (appData.autoRaiseBoard) BoardToTop();
4562 if (gamenum == -1) {
4563 newGameMode = IcsIdle;
4564 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4565 appData.getMoveList && !reqFlag) {
4566 /* Need to get game history */
4567 ics_getting_history = H_REQUESTED;
4568 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4572 /* Initially flip the board to have black on the bottom if playing
4573 black or if the ICS flip flag is set, but let the user change
4574 it with the Flip View button. */
4575 flipView = appData.autoFlipView ?
4576 (newGameMode == IcsPlayingBlack) || ics_flip :
4579 /* Done with values from previous mode; copy in new ones */
4580 gameMode = newGameMode;
4582 ics_gamenum = gamenum;
4583 if (gamenum == gs_gamenum) {
4584 int klen = strlen(gs_kind);
4585 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4586 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4587 gameInfo.event = StrSave(str);
4589 gameInfo.event = StrSave("ICS game");
4591 gameInfo.site = StrSave(appData.icsHost);
4592 gameInfo.date = PGNDate();
4593 gameInfo.round = StrSave("-");
4594 gameInfo.white = StrSave(white);
4595 gameInfo.black = StrSave(black);
4596 timeControl = basetime * 60 * 1000;
4598 timeIncrement = increment * 1000;
4599 movesPerSession = 0;
4600 gameInfo.timeControl = TimeControlTagValue();
4601 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4602 if (appData.debugMode) {
4603 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4604 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4605 setbuf(debugFP, NULL);
4608 gameInfo.outOfBook = NULL;
4610 /* Do we have the ratings? */
4611 if (strcmp(player1Name, white) == 0 &&
4612 strcmp(player2Name, black) == 0) {
4613 if (appData.debugMode)
4614 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4615 player1Rating, player2Rating);
4616 gameInfo.whiteRating = player1Rating;
4617 gameInfo.blackRating = player2Rating;
4618 } else if (strcmp(player2Name, white) == 0 &&
4619 strcmp(player1Name, black) == 0) {
4620 if (appData.debugMode)
4621 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4622 player2Rating, player1Rating);
4623 gameInfo.whiteRating = player2Rating;
4624 gameInfo.blackRating = player1Rating;
4626 player1Name[0] = player2Name[0] = NULLCHAR;
4628 /* Silence shouts if requested */
4629 if (appData.quietPlay &&
4630 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4631 SendToICS(ics_prefix);
4632 SendToICS("set shout 0\n");
4636 /* Deal with midgame name changes */
4638 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4639 if (gameInfo.white) free(gameInfo.white);
4640 gameInfo.white = StrSave(white);
4642 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4643 if (gameInfo.black) free(gameInfo.black);
4644 gameInfo.black = StrSave(black);
4648 /* Throw away game result if anything actually changes in examine mode */
4649 if (gameMode == IcsExamining && !newGame) {
4650 gameInfo.result = GameUnfinished;
4651 if (gameInfo.resultDetails != NULL) {
4652 free(gameInfo.resultDetails);
4653 gameInfo.resultDetails = NULL;
4657 /* In pausing && IcsExamining mode, we ignore boards coming
4658 in if they are in a different variation than we are. */
4659 if (pauseExamInvalid) return;
4660 if (pausing && gameMode == IcsExamining) {
4661 if (moveNum <= pauseExamForwardMostMove) {
4662 pauseExamInvalid = TRUE;
4663 forwardMostMove = pauseExamForwardMostMove;
4668 if (appData.debugMode) {
4669 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4671 /* Parse the board */
4672 for (k = 0; k < ranks; k++) {
4673 for (j = 0; j < files; j++)
4674 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4675 if(gameInfo.holdingsWidth > 1) {
4676 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4677 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4680 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4681 board[5][BOARD_RGHT+1] = WhiteAngel;
4682 board[6][BOARD_RGHT+1] = WhiteMarshall;
4683 board[1][0] = BlackMarshall;
4684 board[2][0] = BlackAngel;
4685 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4687 CopyBoard(boards[moveNum], board);
4688 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4690 startedFromSetupPosition =
4691 !CompareBoards(board, initialPosition);
4692 if(startedFromSetupPosition)
4693 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4696 /* [HGM] Set castling rights. Take the outermost Rooks,
4697 to make it also work for FRC opening positions. Note that board12
4698 is really defective for later FRC positions, as it has no way to
4699 indicate which Rook can castle if they are on the same side of King.
4700 For the initial position we grant rights to the outermost Rooks,
4701 and remember thos rights, and we then copy them on positions
4702 later in an FRC game. This means WB might not recognize castlings with
4703 Rooks that have moved back to their original position as illegal,
4704 but in ICS mode that is not its job anyway.
4706 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4707 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4709 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4710 if(board[0][i] == WhiteRook) j = i;
4711 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4712 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4713 if(board[0][i] == WhiteRook) j = i;
4714 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4715 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4716 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4717 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4718 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4719 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4720 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4722 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4723 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4724 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4725 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4726 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4727 if(board[BOARD_HEIGHT-1][k] == bKing)
4728 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4729 if(gameInfo.variant == VariantTwoKings) {
4730 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4731 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4732 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4735 r = boards[moveNum][CASTLING][0] = initialRights[0];
4736 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4737 r = boards[moveNum][CASTLING][1] = initialRights[1];
4738 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4739 r = boards[moveNum][CASTLING][3] = initialRights[3];
4740 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4741 r = boards[moveNum][CASTLING][4] = initialRights[4];
4742 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4743 /* wildcastle kludge: always assume King has rights */
4744 r = boards[moveNum][CASTLING][2] = initialRights[2];
4745 r = boards[moveNum][CASTLING][5] = initialRights[5];
4747 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4748 boards[moveNum][EP_STATUS] = EP_NONE;
4749 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4750 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4751 if(double_push != -1) {
4752 int dir = WhiteOnMove(moveNum) ? 1 : -1, last = BOARD_HEIGHT-1;
4753 boards[moveNum][EP_FILE] = // also set new e.p. variables
4754 boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4755 boards[moveNum][EP_RANK] = (last + 3*dir)/2;
4756 boards[moveNum][LAST_TO] = 128*(last + dir) + boards[moveNum][EP_FILE];
4757 } else boards[moveNum][EP_FILE] = boards[moveNum][EP_RANK] = 100;
4760 if (ics_getting_history == H_GOT_REQ_HEADER ||
4761 ics_getting_history == H_GOT_UNREQ_HEADER) {
4762 /* This was an initial position from a move list, not
4763 the current position */
4767 /* Update currentMove and known move number limits */
4768 newMove = newGame || moveNum > forwardMostMove;
4771 forwardMostMove = backwardMostMove = currentMove = moveNum;
4772 if (gameMode == IcsExamining && moveNum == 0) {
4773 /* Workaround for ICS limitation: we are not told the wild
4774 type when starting to examine a game. But if we ask for
4775 the move list, the move list header will tell us */
4776 ics_getting_history = H_REQUESTED;
4777 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4780 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4781 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4783 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4784 /* [HGM] applied this also to an engine that is silently watching */
4785 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4786 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4787 gameInfo.variant == currentlyInitializedVariant) {
4788 takeback = forwardMostMove - moveNum;
4789 for (i = 0; i < takeback; i++) {
4790 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4791 SendToProgram("undo\n", &first);
4796 forwardMostMove = moveNum;
4797 if (!pausing || currentMove > forwardMostMove)
4798 currentMove = forwardMostMove;
4800 /* New part of history that is not contiguous with old part */
4801 if (pausing && gameMode == IcsExamining) {
4802 pauseExamInvalid = TRUE;
4803 forwardMostMove = pauseExamForwardMostMove;
4806 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4808 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4809 // [HGM] when we will receive the move list we now request, it will be
4810 // fed to the engine from the first move on. So if the engine is not
4811 // in the initial position now, bring it there.
4812 InitChessProgram(&first, 0);
4815 ics_getting_history = H_REQUESTED;
4816 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4819 forwardMostMove = backwardMostMove = currentMove = moveNum;
4822 /* Update the clocks */
4823 if (strchr(elapsed_time, '.')) {
4825 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4826 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4828 /* Time is in seconds */
4829 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4830 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4835 if (appData.zippyPlay && newGame &&
4836 gameMode != IcsObserving && gameMode != IcsIdle &&
4837 gameMode != IcsExamining)
4838 ZippyFirstBoard(moveNum, basetime, increment);
4841 /* Put the move on the move list, first converting
4842 to canonical algebraic form. */
4844 if (appData.debugMode) {
4845 int f = forwardMostMove;
4846 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4847 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4848 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4849 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4850 fprintf(debugFP, "moveNum = %d\n", moveNum);
4851 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4852 setbuf(debugFP, NULL);
4854 if (moveNum <= backwardMostMove) {
4855 /* We don't know what the board looked like before
4857 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4858 strcat(parseList[moveNum - 1], " ");
4859 strcat(parseList[moveNum - 1], elapsed_time);
4860 moveList[moveNum - 1][0] = NULLCHAR;
4861 } else if (strcmp(move_str, "none") == 0) {
4862 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4863 /* Again, we don't know what the board looked like;
4864 this is really the start of the game. */
4865 parseList[moveNum - 1][0] = NULLCHAR;
4866 moveList[moveNum - 1][0] = NULLCHAR;
4867 backwardMostMove = moveNum;
4868 startedFromSetupPosition = TRUE;
4869 fromX = fromY = toX = toY = -1;
4871 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4872 // So we parse the long-algebraic move string in stead of the SAN move
4873 int valid; char buf[MSG_SIZ], *prom;
4875 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4876 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4877 // str looks something like "Q/a1-a2"; kill the slash
4879 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4880 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4881 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4882 strcat(buf, prom); // long move lacks promo specification!
4883 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4884 if(appData.debugMode)
4885 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4886 safeStrCpy(move_str, buf, MSG_SIZ);
4888 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4889 &fromX, &fromY, &toX, &toY, &promoChar)
4890 || ParseOneMove(buf, moveNum - 1, &moveType,
4891 &fromX, &fromY, &toX, &toY, &promoChar);
4892 // end of long SAN patch
4894 (void) CoordsToAlgebraic(boards[moveNum - 1],
4895 PosFlags(moveNum - 1),
4896 fromY, fromX, toY, toX, promoChar,
4897 parseList[moveNum-1]);
4898 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4904 if(!IS_SHOGI(gameInfo.variant))
4905 strcat(parseList[moveNum - 1], "+");
4908 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4909 strcat(parseList[moveNum - 1], "#");
4912 strcat(parseList[moveNum - 1], " ");
4913 strcat(parseList[moveNum - 1], elapsed_time);
4914 /* currentMoveString is set as a side-effect of ParseOneMove */
4915 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4916 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4917 strcat(moveList[moveNum - 1], "\n");
4919 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4920 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4921 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4922 ChessSquare old, new = boards[moveNum][k][j];
4923 if(new == EmptySquare || fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4924 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4925 if(old == new) continue;
4926 if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4927 else if(new == WhiteWazir || new == BlackWazir) {
4928 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4929 boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4930 else boards[moveNum][k][j] = old; // preserve type of Gold
4931 } else if(old == WhitePawn || old == BlackPawn) // Pawn promotions (but not e.p.capture!)
4932 boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4935 /* Move from ICS was illegal!? Punt. */
4936 if (appData.debugMode) {
4937 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4938 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4940 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4941 strcat(parseList[moveNum - 1], " ");
4942 strcat(parseList[moveNum - 1], elapsed_time);
4943 moveList[moveNum - 1][0] = NULLCHAR;
4944 fromX = fromY = toX = toY = -1;
4947 if (appData.debugMode) {
4948 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4949 setbuf(debugFP, NULL);
4953 /* Send move to chess program (BEFORE animating it). */
4954 if (appData.zippyPlay && !newGame && newMove &&
4955 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4957 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4958 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4959 if (moveList[moveNum - 1][0] == NULLCHAR) {
4960 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4962 DisplayError(str, 0);
4964 if (first.sendTime) {
4965 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4967 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4968 if (firstMove && !bookHit) {
4970 if (first.useColors) {
4971 SendToProgram(gameMode == IcsPlayingWhite ?
4973 "black\ngo\n", &first);
4975 SendToProgram("go\n", &first);
4977 first.maybeThinking = TRUE;
4980 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4981 if (moveList[moveNum - 1][0] == NULLCHAR) {
4982 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4983 DisplayError(str, 0);
4985 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4986 SendMoveToProgram(moveNum - 1, &first);
4993 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4994 /* If move comes from a remote source, animate it. If it
4995 isn't remote, it will have already been animated. */
4996 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4997 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4999 if (!pausing && appData.highlightLastMove) {
5000 SetHighlights(fromX, fromY, toX, toY);
5004 /* Start the clocks */
5005 whiteFlag = blackFlag = FALSE;
5006 appData.clockMode = !(basetime == 0 && increment == 0);
5008 ics_clock_paused = TRUE;
5010 } else if (ticking == 1) {
5011 ics_clock_paused = FALSE;
5013 if (gameMode == IcsIdle ||
5014 relation == RELATION_OBSERVING_STATIC ||
5015 relation == RELATION_EXAMINING ||
5017 DisplayBothClocks();
5021 /* Display opponents and material strengths */
5022 if (gameInfo.variant != VariantBughouse &&
5023 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5024 if (tinyLayout || smallLayout) {
5025 if(gameInfo.variant == VariantNormal)
5026 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5027 gameInfo.white, white_stren, gameInfo.black, black_stren,
5028 basetime, increment);
5030 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5031 gameInfo.white, white_stren, gameInfo.black, black_stren,
5032 basetime, increment, (int) gameInfo.variant);
5034 if(gameInfo.variant == VariantNormal)
5035 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5036 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5037 basetime, increment);
5039 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5040 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5041 basetime, increment, VariantName(gameInfo.variant));
5044 if (appData.debugMode) {
5045 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5050 /* Display the board */
5051 if (!pausing && !appData.noGUI) {
5053 if (appData.premove)
5055 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5056 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5057 ClearPremoveHighlights();
5059 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5060 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5061 DrawPosition(j, boards[currentMove]);
5063 DisplayMove(moveNum - 1);
5064 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5065 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5066 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
5067 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5071 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5073 if(bookHit) { // [HGM] book: simulate book reply
5074 static char bookMove[MSG_SIZ]; // a bit generous?
5076 programStats.nodes = programStats.depth = programStats.time =
5077 programStats.score = programStats.got_only_move = 0;
5078 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5080 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5081 strcat(bookMove, bookHit);
5082 HandleMachineMove(bookMove, &first);
5091 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5092 ics_getting_history = H_REQUESTED;
5093 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5099 SendToBoth (char *msg)
5100 { // to make it easy to keep two engines in step in dual analysis
5101 SendToProgram(msg, &first);
5102 if(second.analyzing) SendToProgram(msg, &second);
5106 AnalysisPeriodicEvent (int force)
5108 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5109 && !force) || !appData.periodicUpdates)
5112 /* Send . command to Crafty to collect stats */
5115 /* Don't send another until we get a response (this makes
5116 us stop sending to old Crafty's which don't understand
5117 the "." command (sending illegal cmds resets node count & time,
5118 which looks bad)) */
5119 programStats.ok_to_send = 0;
5123 ics_update_width (int new_width)
5125 ics_printf("set width %d\n", new_width);
5129 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5133 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5134 if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5135 sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5136 SendToProgram(buf, cps);
5139 // null move in variant where engine does not understand it (for analysis purposes)
5140 SendBoard(cps, moveNum + 1); // send position after move in stead.
5143 if (cps->useUsermove) {
5144 SendToProgram("usermove ", cps);
5148 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5149 int len = space - parseList[moveNum];
5150 memcpy(buf, parseList[moveNum], len);
5152 buf[len] = NULLCHAR;
5154 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5156 SendToProgram(buf, cps);
5158 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5159 AlphaRank(moveList[moveNum], 4);
5160 SendToProgram(moveList[moveNum], cps);
5161 AlphaRank(moveList[moveNum], 4); // and back
5163 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5164 * the engine. It would be nice to have a better way to identify castle
5166 if(appData.fischerCastling && cps->useOOCastle) {
5167 int fromX = moveList[moveNum][0] - AAA;
5168 int fromY = moveList[moveNum][1] - ONE;
5169 int toX = moveList[moveNum][2] - AAA;
5170 int toY = moveList[moveNum][3] - ONE;
5171 if((boards[moveNum][fromY][fromX] == WhiteKing
5172 && boards[moveNum][toY][toX] == WhiteRook)
5173 || (boards[moveNum][fromY][fromX] == BlackKing
5174 && boards[moveNum][toY][toX] == BlackRook)) {
5175 if(toX > fromX) SendToProgram("O-O\n", cps);
5176 else SendToProgram("O-O-O\n", cps);
5178 else SendToProgram(moveList[moveNum], cps);
5180 if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5181 char *m = moveList[moveNum];
5183 *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5184 if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5185 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5188 m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5189 else if(*c && m[8] != '\n') { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5190 *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5191 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
5196 m[2], m[3] - '0', c);
5198 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5201 m[2], m[3] - '0', c);
5202 SendToProgram(buf, cps);
5204 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5205 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5206 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5207 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5208 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5210 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5211 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5212 SendToProgram(buf, cps);
5214 else SendToProgram(moveList[moveNum], cps);
5215 /* End of additions by Tord */
5218 /* [HGM] setting up the opening has brought engine in force mode! */
5219 /* Send 'go' if we are in a mode where machine should play. */
5220 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5221 (gameMode == TwoMachinesPlay ||
5223 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5225 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5226 SendToProgram("go\n", cps);
5227 if (appData.debugMode) {
5228 fprintf(debugFP, "(extra)\n");
5231 setboardSpoiledMachineBlack = 0;
5235 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5237 char user_move[MSG_SIZ];
5240 if(gameInfo.variant == VariantSChess && promoChar) {
5241 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5242 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5243 } else suffix[0] = NULLCHAR;
5247 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5248 (int)moveType, fromX, fromY, toX, toY);
5249 DisplayError(user_move + strlen("say "), 0);
5251 case WhiteKingSideCastle:
5252 case BlackKingSideCastle:
5253 case WhiteQueenSideCastleWild:
5254 case BlackQueenSideCastleWild:
5256 case WhiteHSideCastleFR:
5257 case BlackHSideCastleFR:
5259 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5261 case WhiteQueenSideCastle:
5262 case BlackQueenSideCastle:
5263 case WhiteKingSideCastleWild:
5264 case BlackKingSideCastleWild:
5266 case WhiteASideCastleFR:
5267 case BlackASideCastleFR:
5269 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5271 case WhiteNonPromotion:
5272 case BlackNonPromotion:
5273 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5275 case WhitePromotion:
5276 case BlackPromotion:
5277 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5278 gameInfo.variant == VariantMakruk)
5279 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5280 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5281 PieceToChar(WhiteFerz));
5282 else if(gameInfo.variant == VariantGreat)
5283 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5284 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5285 PieceToChar(WhiteMan));
5287 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5288 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5294 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5295 ToUpper(PieceToChar((ChessSquare) fromX)),
5296 AAA + toX, ONE + toY);
5298 case IllegalMove: /* could be a variant we don't quite understand */
5299 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5301 case WhiteCapturesEnPassant:
5302 case BlackCapturesEnPassant:
5303 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5304 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5307 SendToICS(user_move);
5308 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5309 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5314 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5315 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5316 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5317 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5318 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5321 if(gameMode != IcsExamining) { // is this ever not the case?
5322 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5324 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5325 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5326 } else { // on FICS we must first go to general examine mode
5327 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5329 if(gameInfo.variant != VariantNormal) {
5330 // try figure out wild number, as xboard names are not always valid on ICS
5331 for(i=1; i<=36; i++) {
5332 snprintf(buf, MSG_SIZ, "wild/%d", i);
5333 if(StringToVariant(buf) == gameInfo.variant) break;
5335 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5336 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5337 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5338 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5339 SendToICS(ics_prefix);
5341 if(startedFromSetupPosition || backwardMostMove != 0) {
5342 fen = PositionToFEN(backwardMostMove, NULL, 1);
5343 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5344 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5346 } else { // FICS: everything has to set by separate bsetup commands
5347 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5348 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5350 if(!WhiteOnMove(backwardMostMove)) {
5351 SendToICS("bsetup tomove black\n");
5353 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5354 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5356 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5357 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5359 i = boards[backwardMostMove][EP_STATUS];
5360 if(i >= 0) { // set e.p.
5361 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5367 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5368 SendToICS("bsetup done\n"); // switch to normal examining.
5370 for(i = backwardMostMove; i<last; i++) {
5372 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5373 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5374 int len = strlen(moveList[i]);
5375 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5376 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5380 SendToICS(ics_prefix);
5381 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5384 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5388 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5390 if (rf == DROP_RANK) {
5391 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5392 sprintf(move, "%c@%c%c\n",
5393 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5395 if (promoChar == 'x' || promoChar == NULLCHAR) {
5396 sprintf(move, "%c%c%c%c\n",
5397 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5398 if(killX >= 0 && killY >= 0) {
5399 sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5400 if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5403 sprintf(move, "%c%c%c%c%c\n",
5404 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5405 if(killX >= 0 && killY >= 0) {
5406 sprintf(move+4, ";%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5407 if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5414 ProcessICSInitScript (FILE *f)
5418 while (fgets(buf, MSG_SIZ, f)) {
5419 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5426 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5428 static ClickType lastClickType;
5431 PieceInString (char *s, ChessSquare piece)
5433 char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5434 while((p = strchr(s, ID))) {
5435 if(!suffix || p[1] == suffix) return TRUE;
5442 Partner (ChessSquare *p)
5443 { // change piece into promotion partner if one shogi-promotes to the other
5444 ChessSquare partner = promoPartner[*p];
5445 if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5446 if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5454 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5455 static int toggleFlag;
5456 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5457 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5458 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5459 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5460 if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5461 if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5463 if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5464 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5465 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5466 else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5467 else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5468 if(!step) step = -1;
5469 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5470 !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5471 promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it
5472 promoSweep == pawn ||
5473 appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5474 (promoSweep == WhiteLion || promoSweep == BlackLion)));
5476 int victim = boards[currentMove][toY][toX];
5477 boards[currentMove][toY][toX] = promoSweep;
5478 DrawPosition(FALSE, boards[currentMove]);
5479 boards[currentMove][toY][toX] = victim;
5481 ChangeDragPiece(promoSweep);
5485 PromoScroll (int x, int y)
5489 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5490 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5491 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5492 if(!step) return FALSE;
5493 lastX = x; lastY = y;
5494 if((promoSweep < BlackPawn) == flipView) step = -step;
5495 if(step > 0) selectFlag = 1;
5496 if(!selectFlag) Sweep(step);
5501 NextPiece (int step)
5503 ChessSquare piece = boards[currentMove][toY][toX];
5506 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5507 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5508 if(!step) step = -1;
5509 } while(PieceToChar(pieceSweep) == '.');
5510 boards[currentMove][toY][toX] = pieceSweep;
5511 DrawPosition(FALSE, boards[currentMove]);
5512 boards[currentMove][toY][toX] = piece;
5514 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5516 AlphaRank (char *move, int n)
5518 // char *p = move, c; int x, y;
5520 if (appData.debugMode) {
5521 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5525 move[2]>='0' && move[2]<='9' &&
5526 move[3]>='a' && move[3]<='x' ) {
5528 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5529 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5531 if(move[0]>='0' && move[0]<='9' &&
5532 move[1]>='a' && move[1]<='x' &&
5533 move[2]>='0' && move[2]<='9' &&
5534 move[3]>='a' && move[3]<='x' ) {
5535 /* input move, Shogi -> normal */
5536 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5537 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5538 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5539 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5542 move[3]>='0' && move[3]<='9' &&
5543 move[2]>='a' && move[2]<='x' ) {
5545 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5546 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5549 move[0]>='a' && move[0]<='x' &&
5550 move[3]>='0' && move[3]<='9' &&
5551 move[2]>='a' && move[2]<='x' ) {
5552 /* output move, normal -> Shogi */
5553 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5554 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5555 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5556 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5557 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5559 if (appData.debugMode) {
5560 fprintf(debugFP, " out = '%s'\n", move);
5564 char yy_textstr[8000];
5566 /* Parser for moves from gnuchess, ICS, or user typein box */
5568 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5570 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5572 switch (*moveType) {
5573 case WhitePromotion:
5574 case BlackPromotion:
5575 case WhiteNonPromotion:
5576 case BlackNonPromotion:
5579 case WhiteCapturesEnPassant:
5580 case BlackCapturesEnPassant:
5581 case WhiteKingSideCastle:
5582 case WhiteQueenSideCastle:
5583 case BlackKingSideCastle:
5584 case BlackQueenSideCastle:
5585 case WhiteKingSideCastleWild:
5586 case WhiteQueenSideCastleWild:
5587 case BlackKingSideCastleWild:
5588 case BlackQueenSideCastleWild:
5589 /* Code added by Tord: */
5590 case WhiteHSideCastleFR:
5591 case WhiteASideCastleFR:
5592 case BlackHSideCastleFR:
5593 case BlackASideCastleFR:
5594 /* End of code added by Tord */
5595 case IllegalMove: /* bug or odd chess variant */
5596 if(currentMoveString[1] == '@') { // illegal drop
5597 *fromX = WhiteOnMove(moveNum) ?
5598 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5599 (int) CharToPiece(ToLower(currentMoveString[0]));
5602 *fromX = currentMoveString[0] - AAA;
5603 *fromY = currentMoveString[1] - ONE;
5604 *toX = currentMoveString[2] - AAA;
5605 *toY = currentMoveString[3] - ONE;
5606 *promoChar = currentMoveString[4];
5607 if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5608 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5609 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5610 if (appData.debugMode) {
5611 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5613 *fromX = *fromY = *toX = *toY = 0;
5616 if (appData.testLegality) {
5617 return (*moveType != IllegalMove);
5619 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5620 // [HGM] lion: if this is a double move we are less critical
5621 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5626 *fromX = *moveType == WhiteDrop ?
5627 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5628 (int) CharToPiece(ToLower(currentMoveString[0]));
5631 *toX = currentMoveString[2] - AAA;
5632 *toY = currentMoveString[3] - ONE;
5633 *promoChar = NULLCHAR;
5637 case ImpossibleMove:
5647 if (appData.debugMode) {
5648 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5651 *fromX = *fromY = *toX = *toY = 0;
5652 *promoChar = NULLCHAR;
5657 Boolean pushed = FALSE;
5658 char *lastParseAttempt;
5661 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5662 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5663 int fromX, fromY, toX, toY; char promoChar;
5668 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5669 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5670 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5673 endPV = forwardMostMove;
5675 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5676 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5677 lastParseAttempt = pv;
5678 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5679 if(!valid && nr == 0 &&
5680 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5681 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5682 // Hande case where played move is different from leading PV move
5683 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5684 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5685 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5686 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5687 endPV += 2; // if position different, keep this
5688 moveList[endPV-1][0] = fromX + AAA;
5689 moveList[endPV-1][1] = fromY + ONE;
5690 moveList[endPV-1][2] = toX + AAA;
5691 moveList[endPV-1][3] = toY + ONE;
5692 parseList[endPV-1][0] = NULLCHAR;
5693 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5696 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5697 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5698 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5699 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5700 valid++; // allow comments in PV
5704 if(endPV+1 > framePtr) break; // no space, truncate
5707 CopyBoard(boards[endPV], boards[endPV-1]);
5708 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5709 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5710 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5711 CoordsToAlgebraic(boards[endPV - 1],
5712 PosFlags(endPV - 1),
5713 fromY, fromX, toY, toX, promoChar,
5714 parseList[endPV - 1]);
5716 if(atEnd == 2) return; // used hidden, for PV conversion
5717 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5718 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5719 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5720 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5721 DrawPosition(TRUE, boards[currentMove]);
5725 MultiPV (ChessProgramState *cps, int kind)
5726 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5728 for(i=0; i<cps->nrOptions; i++) {
5729 char *s = cps->option[i].name;
5730 if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5731 if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5732 && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5737 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5738 static int multi, pv_margin;
5739 static ChessProgramState *activeCps;
5742 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5744 int startPV, lineStart, origIndex = index;
5745 char *p, buf2[MSG_SIZ];
5746 ChessProgramState *cps = (pane ? &second : &first);
5748 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5749 lastX = x; lastY = y;
5750 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5751 lineStart = startPV = index;
5752 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5753 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5755 do{ while(buf[index] && buf[index] != '\n') index++;
5756 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5758 if(lineStart == 0 && gameMode == AnalyzeMode) {
5760 if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5761 if(n == 0) { // click not on "fewer" or "more"
5762 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5763 pv_margin = cps->option[multi].value;
5764 activeCps = cps; // non-null signals margin adjustment
5766 } else if((multi = MultiPV(cps, 1)) >= 0) {
5767 n += cps->option[multi].value; if(n < 1) n = 1;
5768 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5769 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5770 cps->option[multi].value = n;
5774 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5775 ExcludeClick(origIndex - lineStart);
5777 } else if(!strncmp(buf+lineStart, "dep\t", 4)) { // column headers clicked
5778 Collapse(origIndex - lineStart);
5781 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5782 *start = startPV; *end = index-1;
5783 extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5790 static char buf[10*MSG_SIZ];
5791 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5793 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5794 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5795 for(i = forwardMostMove; i<endPV; i++){
5796 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5797 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5800 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5801 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5802 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5808 LoadPV (int x, int y)
5809 { // called on right mouse click to load PV
5810 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5811 lastX = x; lastY = y;
5812 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5820 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5822 if(pv_margin != activeCps->option[multi].value) {
5824 snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5825 SendToProgram(buf, activeCps);
5826 activeCps->option[multi].value = pv_margin;
5831 if(endPV < 0) return;
5832 if(appData.autoCopyPV) CopyFENToClipboard();
5834 if(extendGame && currentMove > forwardMostMove) {
5835 Boolean saveAnimate = appData.animate;
5837 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5838 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5839 } else storedGames--; // abandon shelved tail of original game
5842 forwardMostMove = currentMove;
5843 currentMove = oldFMM;
5844 appData.animate = FALSE;
5845 ToNrEvent(forwardMostMove);
5846 appData.animate = saveAnimate;
5848 currentMove = forwardMostMove;
5849 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5850 ClearPremoveHighlights();
5851 DrawPosition(TRUE, boards[currentMove]);
5855 MovePV (int x, int y, int h)
5856 { // step through PV based on mouse coordinates (called on mouse move)
5857 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5859 if(activeCps) { // adjusting engine's multi-pv margin
5860 if(x > lastX) pv_margin++; else
5861 if(x < lastX) pv_margin -= (pv_margin > 0);
5864 snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5865 DisplayMessage(buf, "");
5870 // we must somehow check if right button is still down (might be released off board!)
5871 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5872 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5873 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5875 lastX = x; lastY = y;
5877 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5878 if(endPV < 0) return;
5879 if(y < margin) step = 1; else
5880 if(y > h - margin) step = -1;
5881 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5882 currentMove += step;
5883 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5884 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5885 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5886 DrawPosition(FALSE, boards[currentMove]);
5890 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5891 // All positions will have equal probability, but the current method will not provide a unique
5892 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5898 int piecesLeft[(int)BlackPawn];
5899 int seed, nrOfShuffles;
5902 GetPositionNumber ()
5903 { // sets global variable seed
5906 seed = appData.defaultFrcPosition;
5907 if(seed < 0) { // randomize based on time for negative FRC position numbers
5908 for(i=0; i<50; i++) seed += random();
5909 seed = random() ^ random() >> 8 ^ random() << 8;
5910 if(seed<0) seed = -seed;
5915 put (Board board, int pieceType, int rank, int n, int shade)
5916 // put the piece on the (n-1)-th empty squares of the given shade
5920 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5921 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5922 board[rank][i] = (ChessSquare) pieceType;
5923 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5925 piecesLeft[pieceType]--;
5934 AddOnePiece (Board board, int pieceType, int rank, int shade)
5935 // calculate where the next piece goes, (any empty square), and put it there
5939 i = seed % squaresLeft[shade];
5940 nrOfShuffles *= squaresLeft[shade];
5941 seed /= squaresLeft[shade];
5942 put(board, pieceType, rank, i, shade);
5946 AddTwoPieces (Board board, int pieceType, int rank)
5947 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5949 int i, n=squaresLeft[ANY], j=n-1, k;
5951 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5952 i = seed % k; // pick one
5955 while(i >= j) i -= j--;
5956 j = n - 1 - j; i += j;
5957 put(board, pieceType, rank, j, ANY);
5958 put(board, pieceType, rank, i, ANY);
5962 SetUpShuffle (Board board, int number)
5966 GetPositionNumber(); nrOfShuffles = 1;
5968 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5969 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5970 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5972 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5974 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5975 p = (int) board[0][i];
5976 if(p < (int) BlackPawn) piecesLeft[p] ++;
5977 board[0][i] = EmptySquare;
5980 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5981 // shuffles restricted to allow normal castling put KRR first
5982 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5983 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5984 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5985 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5986 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5987 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5988 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5989 put(board, WhiteRook, 0, 0, ANY);
5990 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5993 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5994 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5995 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5996 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5997 while(piecesLeft[p] >= 2) {
5998 AddOnePiece(board, p, 0, LITE);
5999 AddOnePiece(board, p, 0, DARK);
6001 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
6004 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
6005 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
6006 // but we leave King and Rooks for last, to possibly obey FRC restriction
6007 if(p == (int)WhiteRook) continue;
6008 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
6009 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
6012 // now everything is placed, except perhaps King (Unicorn) and Rooks
6014 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
6015 // Last King gets castling rights
6016 while(piecesLeft[(int)WhiteUnicorn]) {
6017 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6018 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
6021 while(piecesLeft[(int)WhiteKing]) {
6022 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6023 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
6028 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
6029 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6032 // Only Rooks can be left; simply place them all
6033 while(piecesLeft[(int)WhiteRook]) {
6034 i = put(board, WhiteRook, 0, 0, ANY);
6035 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6038 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
6040 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
6043 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6044 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6047 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6051 ptclen (const char *s, char *escapes)
6054 if(!*escapes) return strlen(s);
6055 while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6060 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6061 /* [HGM] moved here from winboard.c because of its general usefulness */
6062 /* Basically a safe strcpy that uses the last character as King */
6064 int result = FALSE; int NrPieces;
6065 unsigned char partner[EmptySquare];
6067 if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6068 && NrPieces >= 12 && !(NrPieces&1)) {
6069 int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6071 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6072 for( i=offs=0; i<NrPieces/2-1; i++ ) {
6074 if(map[j] == '/') offs = WhitePBishop - i, j++;
6075 if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6076 table[i+offs] = map[j++];
6077 if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6078 if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6079 if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6081 table[(int) WhiteKing] = map[j++];
6082 for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6084 if(map[j] == '/') offs = WhitePBishop - ii, j++;
6085 i = WHITE_TO_BLACK ii;
6086 if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6087 table[i+offs] = map[j++];
6088 if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6089 if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6090 if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6092 table[(int) BlackKing] = map[j++];
6095 if(*escapes) { // set up promotion pairing
6096 for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6097 // pieceToChar entirely filled, so we can look up specified partners
6098 for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6100 if(c == '^' || c == '-') { // has specified partner
6102 for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6103 if(c == '^') table[i] = '+';
6104 if(p < EmptySquare) {
6105 if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6106 if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6107 promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6109 } else if(c == '*') {
6110 table[i] = partner[i];
6111 promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6123 SetCharTable (unsigned char *table, const char * map)
6125 return SetCharTableEsc(table, map, "");
6129 Prelude (Board board)
6130 { // [HGM] superchess: random selection of exo-pieces
6131 int i, j, k; ChessSquare p;
6132 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6134 GetPositionNumber(); // use FRC position number
6136 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6137 SetCharTable(pieceToChar, appData.pieceToCharTable);
6138 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6139 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6142 j = seed%4; seed /= 4;
6143 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6144 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6145 board[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-1-k][1]++;
6146 j = seed%3 + (seed%3 >= j); seed /= 3;
6147 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6148 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6149 board[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-1-k][1]++;
6150 j = seed%3; seed /= 3;
6151 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6152 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6153 board[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-1-k][1]++;
6154 j = seed%2 + (seed%2 >= j); seed /= 2;
6155 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6156 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6157 board[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-1-k][1]++;
6158 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
6159 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
6160 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6161 put(board, exoPieces[0], 0, 0, ANY);
6162 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6166 InitPosition (int redraw)
6168 ChessSquare (* pieces)[BOARD_FILES];
6169 int i, j, pawnRow=1, pieceRows=1, overrule,
6170 oldx = gameInfo.boardWidth,
6171 oldy = gameInfo.boardHeight,
6172 oldh = gameInfo.holdingsWidth;
6175 if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6177 /* [AS] Initialize pv info list [HGM] and game status */
6179 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6180 pvInfoList[i].depth = 0;
6181 boards[i][EP_STATUS] = EP_NONE;
6182 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6185 initialRulePlies = 0; /* 50-move counter start */
6187 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6188 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6192 /* [HGM] logic here is completely changed. In stead of full positions */
6193 /* the initialized data only consist of the two backranks. The switch */
6194 /* selects which one we will use, which is than copied to the Board */
6195 /* initialPosition, which for the rest is initialized by Pawns and */
6196 /* empty squares. This initial position is then copied to boards[0], */
6197 /* possibly after shuffling, so that it remains available. */
6199 gameInfo.holdingsWidth = 0; /* default board sizes */
6200 gameInfo.boardWidth = 8;
6201 gameInfo.boardHeight = 8;
6202 gameInfo.holdingsSize = 0;
6203 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6204 for(i=0; i<BOARD_FILES-6; i++)
6205 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6206 initialPosition[EP_STATUS] = EP_NONE;
6207 initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6208 SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6209 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6210 SetCharTable(pieceNickName, appData.pieceNickNames);
6211 else SetCharTable(pieceNickName, "............");
6214 switch (gameInfo.variant) {
6215 case VariantFischeRandom:
6216 shuffleOpenings = TRUE;
6217 appData.fischerCastling = TRUE;
6220 case VariantShatranj:
6221 pieces = ShatranjArray;
6222 nrCastlingRights = 0;
6223 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6226 pieces = makrukArray;
6227 nrCastlingRights = 0;
6228 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6231 pieces = aseanArray;
6232 nrCastlingRights = 0;
6233 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6235 case VariantTwoKings:
6236 pieces = twoKingsArray;
6239 pieces = GrandArray;
6240 nrCastlingRights = 0;
6241 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6242 gameInfo.boardWidth = 10;
6243 gameInfo.boardHeight = 10;
6244 gameInfo.holdingsSize = 7;
6246 case VariantCapaRandom:
6247 shuffleOpenings = TRUE;
6248 appData.fischerCastling = TRUE;
6249 case VariantCapablanca:
6250 pieces = CapablancaArray;
6251 gameInfo.boardWidth = 10;
6252 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6255 pieces = GothicArray;
6256 gameInfo.boardWidth = 10;
6257 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6260 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6261 gameInfo.holdingsSize = 7;
6262 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6265 pieces = JanusArray;
6266 gameInfo.boardWidth = 10;
6267 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6268 nrCastlingRights = 6;
6269 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6270 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6271 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6272 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6273 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6274 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6277 pieces = FalconArray;
6278 gameInfo.boardWidth = 10;
6279 SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6281 case VariantXiangqi:
6282 pieces = XiangqiArray;
6283 gameInfo.boardWidth = 9;
6284 gameInfo.boardHeight = 10;
6285 nrCastlingRights = 0;
6286 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6289 pieces = ShogiArray;
6290 gameInfo.boardWidth = 9;
6291 gameInfo.boardHeight = 9;
6292 gameInfo.holdingsSize = 7;
6293 nrCastlingRights = 0;
6294 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6297 pieces = ChuArray; pieceRows = 3;
6298 gameInfo.boardWidth = 12;
6299 gameInfo.boardHeight = 12;
6300 nrCastlingRights = 0;
6301 // SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6302 // "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6303 SetCharTableEsc(pieceToChar, "P.BRQSEXOG...HD..^DLI^HNV........^T..^L.C...A^AFT/^F^G^M.^E^X^O^I.^P.^B^R..M^S^C^VK"
6304 "p.brqsexog...hd..^dli^hnv........^t..^l.c...a^aft/^f^g^m.^e^x^o^i.^p.^b^r..m^s^c^vk", SUFFIXES);
6306 case VariantCourier:
6307 pieces = CourierArray;
6308 gameInfo.boardWidth = 12;
6309 nrCastlingRights = 0;
6310 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6312 case VariantKnightmate:
6313 pieces = KnightmateArray;
6314 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6316 case VariantSpartan:
6317 pieces = SpartanArray;
6318 SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6322 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6324 case VariantChuChess:
6325 pieces = ChuChessArray;
6326 gameInfo.boardWidth = 10;
6327 gameInfo.boardHeight = 10;
6328 SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6331 pieces = fairyArray;
6332 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6335 pieces = GreatArray;
6336 gameInfo.boardWidth = 10;
6337 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6338 gameInfo.holdingsSize = 8;
6342 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6343 gameInfo.holdingsSize = 8;
6344 startedFromSetupPosition = TRUE;
6346 case VariantCrazyhouse:
6347 case VariantBughouse:
6349 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6350 gameInfo.holdingsSize = 5;
6352 case VariantWildCastle:
6354 /* !!?shuffle with kings guaranteed to be on d or e file */
6355 shuffleOpenings = 1;
6357 case VariantNoCastle:
6358 /* !!?unconstrained back-rank shuffle */
6359 shuffleOpenings = 1;
6360 case VariantSuicide:
6362 nrCastlingRights = 0;
6367 if(appData.NrFiles >= 0) {
6368 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6369 gameInfo.boardWidth = appData.NrFiles;
6371 if(appData.NrRanks >= 0) {
6372 gameInfo.boardHeight = appData.NrRanks;
6374 if(appData.holdingsSize >= 0) {
6375 i = appData.holdingsSize;
6376 // if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6377 gameInfo.holdingsSize = i;
6379 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6380 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6381 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6383 if(!handSize) handSize = BOARD_HEIGHT;
6384 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6385 if(pawnRow < 1) pawnRow = 1;
6386 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6387 gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6388 if(gameInfo.variant == VariantChu) pawnRow = 3;
6390 /* User pieceToChar list overrules defaults */
6391 if(appData.pieceToCharTable != NULL)
6392 SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6394 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6396 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6397 s = (ChessSquare) 0; /* account holding counts in guard band */
6398 for( i=0; i<BOARD_HEIGHT; i++ )
6399 initialPosition[i][j] = s;
6401 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6402 initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6403 initialPosition[pawnRow][j] = WhitePawn;
6404 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6405 if(gameInfo.variant == VariantXiangqi) {
6407 initialPosition[pawnRow][j] =
6408 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6409 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6410 initialPosition[2][j] = WhiteCannon;
6411 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6415 if(gameInfo.variant == VariantChu) {
6416 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6417 initialPosition[pawnRow+1][j] = WhiteCobra,
6418 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6419 for(i=1; i<pieceRows; i++) {
6420 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6421 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6424 if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6425 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6426 initialPosition[0][j] = WhiteRook;
6427 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6430 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth];
6432 if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6433 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6436 initialPosition[1][j] = WhiteBishop;
6437 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6439 initialPosition[1][j] = WhiteRook;
6440 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6443 if( nrCastlingRights == -1) {
6444 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6445 /* This sets default castling rights from none to normal corners */
6446 /* Variants with other castling rights must set them themselves above */
6447 nrCastlingRights = 6;
6449 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6450 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6451 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6452 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6453 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6454 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6457 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6458 if(gameInfo.variant == VariantGreat) { // promotion commoners
6459 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6460 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6461 initialPosition[handSize-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6462 initialPosition[handSize-1-PieceToNumber(WhiteMan)][1] = 9;
6464 if( gameInfo.variant == VariantSChess ) {
6465 initialPosition[1][0] = BlackMarshall;
6466 initialPosition[2][0] = BlackAngel;
6467 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6468 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6469 initialPosition[1][1] = initialPosition[2][1] =
6470 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6472 initialPosition[CHECK_COUNT] = (gameInfo.variant == Variant3Check ? 0x303 : 0);
6473 if (appData.debugMode) {
6474 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6476 if(shuffleOpenings) {
6477 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6478 startedFromSetupPosition = TRUE;
6480 if(startedFromPositionFile) {
6481 /* [HGM] loadPos: use PositionFile for every new game */
6482 CopyBoard(initialPosition, filePosition);
6483 for(i=0; i<nrCastlingRights; i++)
6484 initialRights[i] = filePosition[CASTLING][i];
6485 startedFromSetupPosition = TRUE;
6487 if(*appData.men) LoadPieceDesc(appData.men);
6489 CopyBoard(boards[0], initialPosition);
6491 if(oldx != gameInfo.boardWidth ||
6492 oldy != gameInfo.boardHeight ||
6493 oldv != gameInfo.variant ||
6494 oldh != gameInfo.holdingsWidth
6496 InitDrawingSizes(-2 ,0);
6498 oldv = gameInfo.variant;
6500 DrawPosition(TRUE, boards[currentMove]);
6504 SendBoard (ChessProgramState *cps, int moveNum)
6506 char message[MSG_SIZ];
6508 if (cps->useSetboard) {
6509 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6510 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6511 SendToProgram(message, cps);
6516 int i, j, left=0, right=BOARD_WIDTH;
6517 /* Kludge to set black to move, avoiding the troublesome and now
6518 * deprecated "black" command.
6520 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6521 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
6523 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6525 SendToProgram("edit\n", cps);
6526 SendToProgram("#\n", cps);
6527 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6528 bp = &boards[moveNum][i][left];
6529 for (j = left; j < right; j++, bp++) {
6530 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6531 if ((int) *bp < (int) BlackPawn) {
6532 if(j == BOARD_RGHT+1)
6533 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6534 else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6535 if(message[0] == '+' || message[0] == '~') {
6536 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6537 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6538 AAA + j, ONE + i - '0');
6540 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6541 message[1] = BOARD_RGHT - 1 - j + '1';
6542 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6544 SendToProgram(message, cps);
6549 SendToProgram("c\n", cps);
6550 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6551 bp = &boards[moveNum][i][left];
6552 for (j = left; j < right; j++, bp++) {
6553 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6554 if (((int) *bp != (int) EmptySquare)
6555 && ((int) *bp >= (int) BlackPawn)) {
6556 if(j == BOARD_LEFT-2)
6557 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6558 else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6559 AAA + j, ONE + i - '0');
6560 if(message[0] == '+' || message[0] == '~') {
6561 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6562 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6563 AAA + j, ONE + i - '0');
6565 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6566 message[1] = BOARD_RGHT - 1 - j + '1';
6567 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6569 SendToProgram(message, cps);
6574 SendToProgram(".\n", cps);
6576 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6579 char exclusionHeader[MSG_SIZ];
6580 int exCnt, excludePtr;
6581 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6582 static Exclusion excluTab[200];
6583 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6589 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6590 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6596 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6597 excludePtr = 24; exCnt = 0;
6602 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6603 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6604 char buf[2*MOVE_LEN], *p;
6605 Exclusion *e = excluTab;
6607 for(i=0; i<exCnt; i++)
6608 if(e[i].ff == fromX && e[i].fr == fromY &&
6609 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6610 if(i == exCnt) { // was not in exclude list; add it
6611 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6612 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6613 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6616 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6617 excludePtr++; e[i].mark = excludePtr++;
6618 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6621 exclusionHeader[e[i].mark] = state;
6625 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6626 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6630 if((signed char)promoChar == -1) { // kludge to indicate best move
6631 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6632 return 1; // if unparsable, abort
6634 // update exclusion map (resolving toggle by consulting existing state)
6635 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6637 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6638 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6639 excludeMap[k] |= 1<<j;
6640 else excludeMap[k] &= ~(1<<j);
6642 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6644 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6645 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6647 return (state == '+');
6651 ExcludeClick (int index)
6654 Exclusion *e = excluTab;
6655 if(index < 25) { // none, best or tail clicked
6656 if(index < 13) { // none: include all
6657 WriteMap(0); // clear map
6658 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6659 SendToBoth("include all\n"); // and inform engine
6660 } else if(index > 18) { // tail
6661 if(exclusionHeader[19] == '-') { // tail was excluded
6662 SendToBoth("include all\n");
6663 WriteMap(0); // clear map completely
6664 // now re-exclude selected moves
6665 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6666 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6667 } else { // tail was included or in mixed state
6668 SendToBoth("exclude all\n");
6669 WriteMap(0xFF); // fill map completely
6670 // now re-include selected moves
6671 j = 0; // count them
6672 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6673 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6674 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6677 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6680 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6681 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6682 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6689 DefaultPromoChoice (int white)
6692 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6693 gameInfo.variant == VariantMakruk)
6694 result = WhiteFerz; // no choice
6695 else if(gameInfo.variant == VariantASEAN)
6696 result = WhiteRook; // no choice
6697 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6698 result= WhiteKing; // in Suicide Q is the last thing we want
6699 else if(gameInfo.variant == VariantSpartan)
6700 result = white ? WhiteQueen : WhiteAngel;
6701 else result = WhiteQueen;
6702 if(!white) result = WHITE_TO_BLACK result;
6706 static int autoQueen; // [HGM] oneclick
6709 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6711 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6712 /* [HGM] add Shogi promotions */
6713 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6714 ChessSquare piece, partner;
6718 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6719 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6721 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6722 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6725 if(legal[toY][toX] == 4) return FALSE;
6727 piece = boards[currentMove][fromY][fromX];
6728 if(gameInfo.variant == VariantChu) {
6729 promotionZoneSize = (BOARD_HEIGHT - deadRanks)/3;
6730 if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6731 highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6732 } else if(gameInfo.variant == VariantShogi) {
6733 promotionZoneSize = (BOARD_HEIGHT- deadRanks)/3 +(BOARD_HEIGHT == 8);
6734 highestPromotingPiece = (int)WhiteAlfil;
6735 if(PieceToChar(piece) != '+' || PieceToChar(CHUPROMOTED(piece)) == '+') highestPromotingPiece = piece;
6736 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6737 promotionZoneSize = 3;
6740 // Treat Lance as Pawn when it is not representing Amazon or Lance
6741 if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6742 if(piece == WhiteLance) piece = WhitePawn; else
6743 if(piece == BlackLance) piece = BlackPawn;
6746 // next weed out all moves that do not touch the promotion zone at all
6747 if((int)piece >= BlackPawn) {
6748 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6750 if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6751 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6753 if( toY < BOARD_HEIGHT - deadRanks - promotionZoneSize &&
6754 fromY < BOARD_HEIGHT - deadRanks - promotionZoneSize) return FALSE;
6755 if(fromY >= BOARD_HEIGHT - deadRanks - promotionZoneSize && gameInfo.variant == VariantChuChess)
6759 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6761 // weed out mandatory Shogi promotions
6762 if(gameInfo.variant == VariantShogi) {
6763 if(piece >= BlackPawn) {
6764 if(toY == 0 && piece == BlackPawn ||
6765 toY == 0 && piece == BlackQueen ||
6766 toY <= 1 && piece == BlackKnight) {
6771 if(toY == BOARD_HEIGHT-deadRanks-1 && piece == WhitePawn ||
6772 toY == BOARD_HEIGHT-deadRanks-1 && piece == WhiteQueen ||
6773 toY >= BOARD_HEIGHT-deadRanks-2 && piece == WhiteKnight) {
6780 // weed out obviously illegal Pawn moves
6781 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6782 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6783 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6784 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6785 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6786 // note we are not allowed to test for valid (non-)capture, due to premove
6789 // we either have a choice what to promote to, or (in Shogi) whether to promote
6790 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6791 gameInfo.variant == VariantMakruk) {
6792 ChessSquare p=BlackFerz; // no choice
6793 while(p < EmptySquare) { //but make sure we use piece that exists
6794 *promoChoice = PieceToChar(p++);
6795 if(*promoChoice != '.') break;
6797 if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6799 // no sense asking what we must promote to if it is going to explode...
6800 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6801 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6804 // give caller the default choice even if we will not make it
6805 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6806 partner = piece; // pieces can promote if the pieceToCharTable says so
6807 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6808 else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6809 if( sweepSelect && gameInfo.variant != VariantGreat
6810 && gameInfo.variant != VariantGrand
6811 && gameInfo.variant != VariantSuper) return FALSE;
6812 if(autoQueen) return FALSE; // predetermined
6814 // suppress promotion popup on illegal moves that are not premoves
6815 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6816 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6817 if(appData.testLegality && !premove) {
6818 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6819 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6820 if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6821 if(moveType != WhitePromotion && moveType != BlackPromotion)
6829 InPalace (int row, int column)
6830 { /* [HGM] for Xiangqi */
6831 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6832 column < (BOARD_WIDTH + 4)/2 &&
6833 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6838 PieceForSquare (int x, int y)
6840 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) return -1;
6841 if(x == BOARD_RGHT+1 && handOffsets & 1) y += handSize - BOARD_HEIGHT;
6842 if(x == BOARD_LEFT-2 && !(handOffsets & 2)) y += handSize - BOARD_HEIGHT;
6843 return boards[currentMove][y][x];
6847 More (Board board, int col, int start, int end)
6850 for(k=start; k<end; k++) if(board[k][col]) return (col == 1 ? WhiteMonarch : BlackMonarch); // arrow image
6855 DrawPosition (int repaint, Board board)
6857 Board compactedBoard;
6858 if(handSize > BOARD_HEIGHT && board) {
6860 CopyBoard(compactedBoard, board);
6861 if(handOffsets & 1) {
6862 for(k=0; k<BOARD_HEIGHT; k++) {
6863 compactedBoard[k][BOARD_WIDTH-1] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-1];
6864 compactedBoard[k][BOARD_WIDTH-2] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-2];
6866 compactedBoard[0][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, 0, handSize-BOARD_HEIGHT+1);
6867 } else compactedBoard[BOARD_HEIGHT-1][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, BOARD_HEIGHT-1, handSize);
6868 if(!(handOffsets & 2)) {
6869 for(k=0; k<BOARD_HEIGHT; k++) {
6870 compactedBoard[k][0] = board[k+handSize-BOARD_HEIGHT][0];
6871 compactedBoard[k][1] = board[k+handSize-BOARD_HEIGHT][1];
6873 compactedBoard[0][0] = More(board, 1, 0, handSize-BOARD_HEIGHT+1);
6874 } else compactedBoard[BOARD_HEIGHT-1][0] = More(board, 1, BOARD_HEIGHT-1, handSize);
6875 DrawPositionX(TRUE, compactedBoard);
6876 } else DrawPositionX(repaint, board);
6880 OKToStartUserMove (int x, int y)
6882 ChessSquare from_piece;
6885 if (matchMode) return FALSE;
6886 if (gameMode == EditPosition) return TRUE;
6888 if (x >= 0 && y >= 0)
6889 from_piece = boards[currentMove][y][x];
6891 from_piece = EmptySquare;
6893 if (from_piece == EmptySquare) return FALSE;
6895 white_piece = (int)from_piece >= (int)WhitePawn &&
6896 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6900 case TwoMachinesPlay:
6908 case MachinePlaysWhite:
6909 case IcsPlayingBlack:
6910 if (appData.zippyPlay) return FALSE;
6912 DisplayMoveError(_("You are playing Black"));
6917 case MachinePlaysBlack:
6918 case IcsPlayingWhite:
6919 if (appData.zippyPlay) return FALSE;
6921 DisplayMoveError(_("You are playing White"));
6926 case PlayFromGameFile:
6927 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6930 if (!white_piece && WhiteOnMove(currentMove)) {
6931 DisplayMoveError(_("It is White's turn"));
6934 if (white_piece && !WhiteOnMove(currentMove)) {
6935 DisplayMoveError(_("It is Black's turn"));
6938 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6939 /* Editing correspondence game history */
6940 /* Could disallow this or prompt for confirmation */
6945 case BeginningOfGame:
6946 if (appData.icsActive) return FALSE;
6947 if (!appData.noChessProgram) {
6949 DisplayMoveError(_("You are playing White"));
6956 if (!white_piece && WhiteOnMove(currentMove)) {
6957 DisplayMoveError(_("It is White's turn"));
6960 if (white_piece && !WhiteOnMove(currentMove)) {
6961 DisplayMoveError(_("It is Black's turn"));
6970 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6971 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6972 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6973 && gameMode != AnalyzeFile && gameMode != Training) {
6974 DisplayMoveError(_("Displayed position is not current"));
6981 OnlyMove (int *x, int *y, Boolean captures)
6983 DisambiguateClosure cl;
6984 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6986 case MachinePlaysBlack:
6987 case IcsPlayingWhite:
6988 case BeginningOfGame:
6989 if(!WhiteOnMove(currentMove)) return FALSE;
6991 case MachinePlaysWhite:
6992 case IcsPlayingBlack:
6993 if(WhiteOnMove(currentMove)) return FALSE;
7000 cl.pieceIn = EmptySquare;
7005 cl.promoCharIn = NULLCHAR;
7006 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
7007 if( cl.kind == NormalMove ||
7008 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7009 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7010 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7017 if(cl.kind != ImpossibleMove) return FALSE;
7018 cl.pieceIn = EmptySquare;
7023 cl.promoCharIn = NULLCHAR;
7024 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
7025 if( cl.kind == NormalMove ||
7026 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7027 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7028 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7033 autoQueen = TRUE; // act as if autoQueen on when we click to-square
7039 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
7040 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
7041 int lastLoadGameUseList = FALSE;
7042 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
7043 ChessMove lastLoadGameStart = EndOfFile;
7045 Boolean addToBookFlag;
7046 static Board rightsBoard, nullBoard;
7049 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
7053 int ff=fromX, rf=fromY, ft=toX, rt=toY;
7055 /* Check if the user is playing in turn. This is complicated because we
7056 let the user "pick up" a piece before it is his turn. So the piece he
7057 tried to pick up may have been captured by the time he puts it down!
7058 Therefore we use the color the user is supposed to be playing in this
7059 test, not the color of the piece that is currently on the starting
7060 square---except in EditGame mode, where the user is playing both
7061 sides; fortunately there the capture race can't happen. (It can
7062 now happen in IcsExamining mode, but that's just too bad. The user
7063 will get a somewhat confusing message in that case.)
7068 case TwoMachinesPlay:
7072 /* We switched into a game mode where moves are not accepted,
7073 perhaps while the mouse button was down. */
7076 case MachinePlaysWhite:
7077 /* User is moving for Black */
7078 if (WhiteOnMove(currentMove)) {
7079 DisplayMoveError(_("It is White's turn"));
7084 case MachinePlaysBlack:
7085 /* User is moving for White */
7086 if (!WhiteOnMove(currentMove)) {
7087 DisplayMoveError(_("It is Black's turn"));
7092 case PlayFromGameFile:
7093 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7096 case BeginningOfGame:
7099 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7100 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7101 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7102 /* User is moving for Black */
7103 if (WhiteOnMove(currentMove)) {
7104 DisplayMoveError(_("It is White's turn"));
7108 /* User is moving for White */
7109 if (!WhiteOnMove(currentMove)) {
7110 DisplayMoveError(_("It is Black's turn"));
7116 case IcsPlayingBlack:
7117 /* User is moving for Black */
7118 if (WhiteOnMove(currentMove)) {
7119 if (!appData.premove) {
7120 DisplayMoveError(_("It is White's turn"));
7121 } else if (toX >= 0 && toY >= 0) {
7124 premoveFromX = fromX;
7125 premoveFromY = fromY;
7126 premovePromoChar = promoChar;
7128 if (appData.debugMode)
7129 fprintf(debugFP, "Got premove: fromX %d,"
7130 "fromY %d, toX %d, toY %d\n",
7131 fromX, fromY, toX, toY);
7133 DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7138 case IcsPlayingWhite:
7139 /* User is moving for White */
7140 if (!WhiteOnMove(currentMove)) {
7141 if (!appData.premove) {
7142 DisplayMoveError(_("It is Black's turn"));
7143 } else if (toX >= 0 && toY >= 0) {
7146 premoveFromX = fromX;
7147 premoveFromY = fromY;
7148 premovePromoChar = promoChar;
7150 if (appData.debugMode)
7151 fprintf(debugFP, "Got premove: fromX %d,"
7152 "fromY %d, toX %d, toY %d\n",
7153 fromX, fromY, toX, toY);
7155 DrawPosition(TRUE, boards[currentMove]);
7164 /* EditPosition, empty square, or different color piece;
7165 click-click move is possible */
7166 if (toX == -2 || toY == -2) {
7167 boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7168 DrawPosition(FALSE, boards[currentMove]);
7170 } else if (toX >= 0 && toY >= 0) {
7171 if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7172 ChessSquare p = boards[0][rf][ff];
7173 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7174 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7175 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7176 int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7177 DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7180 } else rightsBoard[toY][toX] = 0; // revoke rights on moving
7181 boards[0][toY][toX] = boards[0][fromY][fromX];
7182 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7183 if(boards[0][fromY][0] != EmptySquare) {
7184 if(boards[0][fromY][1]) boards[0][fromY][1]--;
7185 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
7188 if(fromX == BOARD_RGHT+1) {
7189 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7190 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7191 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7194 boards[0][fromY][fromX] = gatingPiece;
7196 DrawPosition(FALSE, boards[currentMove]);
7202 if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7203 pup = boards[currentMove][toY][toX];
7205 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7206 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7207 if( pup != EmptySquare ) return;
7208 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7209 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7210 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7211 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7212 if(fromX == 0) fromY = handSize-1 - fromY; // black holdings upside-down
7213 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7214 while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7218 /* [HGM] always test for legality, to get promotion info */
7219 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7220 fromY, fromX, toY, toX, promoChar);
7222 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7224 if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7226 /* [HGM] but possibly ignore an IllegalMove result */
7227 if (appData.testLegality) {
7228 if (moveType == IllegalMove || moveType == ImpossibleMove) {
7229 DisplayMoveError(_("Illegal move"));
7234 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7235 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7236 ClearPremoveHighlights(); // was included
7237 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
7238 DrawPosition(FALSE, NULL);
7242 if(addToBookFlag) { // adding moves to book
7243 char buf[MSG_SIZ], move[MSG_SIZ];
7244 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7245 if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7246 killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7247 snprintf(buf, MSG_SIZ, " 0.0%% 1 %s\n", move);
7249 addToBookFlag = FALSE;
7254 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7257 /* Common tail of UserMoveEvent and DropMenuEvent */
7259 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7263 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7264 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7265 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7266 if(WhiteOnMove(currentMove)) {
7267 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7269 if(!boards[currentMove][handSize-1-k][1]) return 0;
7273 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7274 move type in caller when we know the move is a legal promotion */
7275 if(moveType == NormalMove && promoChar)
7276 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7278 /* [HGM] <popupFix> The following if has been moved here from
7279 UserMoveEvent(). Because it seemed to belong here (why not allow
7280 piece drops in training games?), and because it can only be
7281 performed after it is known to what we promote. */
7282 if (gameMode == Training) {
7283 /* compare the move played on the board to the next move in the
7284 * game. If they match, display the move and the opponent's response.
7285 * If they don't match, display an error message.
7289 CopyBoard(testBoard, boards[currentMove]);
7290 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7292 if (CompareBoards(testBoard, boards[currentMove+1])) {
7293 ForwardInner(currentMove+1);
7295 /* Autoplay the opponent's response.
7296 * if appData.animate was TRUE when Training mode was entered,
7297 * the response will be animated.
7299 saveAnimate = appData.animate;
7300 appData.animate = animateTraining;
7301 ForwardInner(currentMove+1);
7302 appData.animate = saveAnimate;
7304 /* check for the end of the game */
7305 if (currentMove >= forwardMostMove) {
7306 gameMode = PlayFromGameFile;
7308 SetTrainingModeOff();
7309 DisplayInformation(_("End of game"));
7312 DisplayError(_("Incorrect move"), 0);
7317 /* Ok, now we know that the move is good, so we can kill
7318 the previous line in Analysis Mode */
7319 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7320 && currentMove < forwardMostMove) {
7321 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7322 else forwardMostMove = currentMove;
7327 /* If we need the chess program but it's dead, restart it */
7328 ResurrectChessProgram();
7330 /* A user move restarts a paused game*/
7334 thinkOutput[0] = NULLCHAR;
7336 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7338 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7339 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7343 if (gameMode == BeginningOfGame) {
7344 if (appData.noChessProgram) {
7345 gameMode = EditGame;
7349 gameMode = MachinePlaysBlack;
7352 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7354 if (first.sendName) {
7355 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7356 SendToProgram(buf, &first);
7363 /* Relay move to ICS or chess engine */
7364 if (appData.icsActive) {
7365 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7366 gameMode == IcsExamining) {
7367 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7368 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7370 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7372 // also send plain move, in case ICS does not understand atomic claims
7373 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7377 if (first.sendTime && (gameMode == BeginningOfGame ||
7378 gameMode == MachinePlaysWhite ||
7379 gameMode == MachinePlaysBlack)) {
7380 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7382 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7383 // [HGM] book: if program might be playing, let it use book
7384 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7385 first.maybeThinking = TRUE;
7386 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7387 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7388 SendBoard(&first, currentMove+1);
7389 if(second.analyzing) {
7390 if(!second.useSetboard) SendToProgram("undo\n", &second);
7391 SendBoard(&second, currentMove+1);
7394 SendMoveToProgram(forwardMostMove-1, &first);
7395 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7397 if (currentMove == cmailOldMove + 1) {
7398 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7402 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7406 if(appData.testLegality)
7407 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7413 if (WhiteOnMove(currentMove)) {
7414 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7416 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7420 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7425 case MachinePlaysBlack:
7426 case MachinePlaysWhite:
7427 /* disable certain menu options while machine is thinking */
7428 SetMachineThinkingEnables();
7435 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7436 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7438 if(bookHit) { // [HGM] book: simulate book reply
7439 static char bookMove[MSG_SIZ]; // a bit generous?
7441 programStats.nodes = programStats.depth = programStats.time =
7442 programStats.score = programStats.got_only_move = 0;
7443 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7445 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7446 strcat(bookMove, bookHit);
7447 HandleMachineMove(bookMove, &first);
7453 MarkByFEN(char *fen)
7456 if(!appData.markers || !appData.highlightDragging) return;
7457 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7458 r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7461 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7462 if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7463 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7464 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7465 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7466 if(*fen == 'T') marker[r][f++] = 0; else
7467 if(*fen == 'Y') marker[r][f++] = 1; else
7468 if(*fen == 'G') marker[r][f++] = 3; else
7469 if(*fen == 'B') marker[r][f++] = 4; else
7470 if(*fen == 'C') marker[r][f++] = 5; else
7471 if(*fen == 'M') marker[r][f++] = 6; else
7472 if(*fen == 'W') marker[r][f++] = 7; else
7473 if(*fen == 'D') marker[r][f++] = 8; else
7474 if(*fen == 'R') marker[r][f++] = 2; else {
7475 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7478 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7482 DrawPosition(TRUE, NULL);
7485 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7488 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7490 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7491 Markers *m = (Markers *) closure;
7492 if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7493 kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7494 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7495 || kind == WhiteCapturesEnPassant
7496 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7497 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7500 static int hoverSavedValid;
7503 MarkTargetSquares (int clear)
7506 if(clear) { // no reason to ever suppress clearing
7507 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7508 hoverSavedValid = 0;
7509 if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7512 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7513 !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7514 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7515 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7516 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7518 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = legal[y][x] = 0;
7521 DrawPosition(FALSE, NULL);
7525 Explode (Board board, int fromX, int fromY, int toX, int toY)
7527 if(gameInfo.variant == VariantAtomic &&
7528 (board[toY][toX] != EmptySquare || // capture?
7529 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7530 board[fromY][fromX] == BlackPawn )
7532 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7538 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7541 CanPromote (ChessSquare piece, int y)
7543 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7544 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7545 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7546 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7547 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7548 (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7549 gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7550 return (piece == BlackPawn && y <= zone ||
7551 piece == WhitePawn && y >= BOARD_HEIGHT-1-deadRanks-zone ||
7552 piece == BlackLance && y <= zone ||
7553 piece == WhiteLance && y >= BOARD_HEIGHT-1-deadRanks-zone );
7557 HoverEvent (int xPix, int yPix, int x, int y)
7559 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7561 if(!first.highlight) return;
7562 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7563 if(x == oldX && y == oldY) return; // only do something if we enter new square
7564 oldFromX = fromX; oldFromY = fromY;
7565 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7566 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7567 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7568 hoverSavedValid = 1;
7569 } else if(oldX != x || oldY != y) {
7570 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7571 if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7572 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7573 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7574 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7576 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7577 SendToProgram(buf, &first);
7580 // SetHighlights(fromX, fromY, x, y);
7584 void ReportClick(char *action, int x, int y)
7586 char buf[MSG_SIZ]; // Inform engine of what user does
7588 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7589 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7590 legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7591 if(!first.highlight || gameMode == EditPosition) return;
7592 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7593 SendToProgram(buf, &first);
7596 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7597 Boolean deferChoice;
7598 int createX = -1, createY = -1; // square where we last created a piece in EditPosition mode
7601 LeftClick (ClickType clickType, int xPix, int yPix)
7604 static Boolean saveAnimate;
7605 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7606 char promoChoice = NULLCHAR;
7608 static TimeMark lastClickTime, prevClickTime;
7610 if(flashing) return;
7612 if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7613 x = EventToSquare(xPix, BOARD_WIDTH);
7614 y = EventToSquare(yPix, BOARD_HEIGHT);
7615 if (!flipView && y >= 0) {
7616 y = BOARD_HEIGHT - 1 - y;
7618 if (flipView && x >= 0) {
7619 x = BOARD_WIDTH - 1 - x;
7622 // map clicks in offsetted holdings back to true coords (or switch the offset)
7623 if(x == BOARD_RGHT+1) {
7624 if(handOffsets & 1) {
7625 if(y == 0) { handOffsets &= ~1; DrawPosition(TRUE, boards[currentMove]); return; }
7626 y += handSize - BOARD_HEIGHT;
7627 } else if(y == BOARD_HEIGHT-1) { handOffsets |= 1; DrawPosition(TRUE, boards[currentMove]); return; }
7629 if(x == BOARD_LEFT-2) {
7630 if(!(handOffsets & 2)) {
7631 if(y == 0) { handOffsets |= 2; DrawPosition(TRUE, boards[currentMove]); return; }
7632 y += handSize - BOARD_HEIGHT;
7633 } else if(y == BOARD_HEIGHT-1) { handOffsets &= ~2; DrawPosition(TRUE, boards[currentMove]); return; }
7636 if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press &&
7637 (boards[currentMove][y][x] == EmptySquare || x == createX && y == createY) ) {
7639 RightClick(clickType, xPix, yPix, &dummy, &dummy);
7644 createX = createY = -1;
7646 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7648 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7650 if (clickType == Press) ErrorPopDown();
7651 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7653 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7654 defaultPromoChoice = promoSweep;
7655 promoSweep = EmptySquare; // terminate sweep
7656 promoDefaultAltered = TRUE;
7657 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7660 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7661 if(clickType == Release) return; // ignore upclick of click-click destination
7662 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7663 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7664 if(gameInfo.holdingsWidth &&
7665 (WhiteOnMove(currentMove)
7666 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7667 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7668 // click in right holdings, for determining promotion piece
7669 ChessSquare p = boards[currentMove][y][x];
7670 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7671 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7672 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7673 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7678 DrawPosition(FALSE, boards[currentMove]);
7682 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7683 if(clickType == Press
7684 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7685 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7686 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7689 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7690 // could be static click on premove from-square: abort premove
7692 ClearPremoveHighlights();
7695 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7696 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7698 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7699 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7700 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7701 defaultPromoChoice = DefaultPromoChoice(side);
7704 autoQueen = appData.alwaysPromoteToQueen;
7708 gatingPiece = EmptySquare;
7709 if (clickType != Press) {
7710 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7711 DragPieceEnd(xPix, yPix); dragging = 0;
7712 DrawPosition(FALSE, NULL);
7716 doubleClick = FALSE;
7717 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7718 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7720 fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7721 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7722 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7723 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7725 if (OKToStartUserMove(fromX, fromY)) {
7727 ReportClick("lift", x, y);
7728 MarkTargetSquares(0);
7729 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7730 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7731 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7732 promoSweep = defaultPromoChoice;
7733 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7734 Sweep(0); // Pawn that is going to promote: preview promotion piece
7735 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7737 if (appData.highlightDragging) {
7738 SetHighlights(fromX, fromY, -1, -1);
7742 } else fromX = fromY = -1;
7748 if (clickType == Press && gameMode != EditPosition) {
7753 // ignore off-board to clicks
7754 if(y < 0 || x < 0) return;
7756 /* Check if clicking again on the same color piece */
7757 fromP = boards[currentMove][fromY][fromX];
7758 toP = boards[currentMove][y][x];
7759 frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7760 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7761 marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7762 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7763 WhitePawn <= toP && toP <= WhiteKing &&
7764 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7765 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7766 (BlackPawn <= fromP && fromP <= BlackKing &&
7767 BlackPawn <= toP && toP <= BlackKing &&
7768 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7769 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7770 /* Clicked again on same color piece -- changed his mind */
7771 second = (x == fromX && y == fromY);
7772 killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7773 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7774 second = FALSE; // first double-click rather than scond click
7775 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7777 promoDefaultAltered = FALSE;
7778 if(!second) MarkTargetSquares(1);
7779 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7780 if (appData.highlightDragging) {
7781 SetHighlights(x, y, -1, -1);
7785 if (OKToStartUserMove(x, y)) {
7786 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7787 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7788 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7789 gatingPiece = boards[currentMove][fromY][fromX];
7790 else gatingPiece = doubleClick ? fromP : EmptySquare;
7792 fromY = y; dragging = 1;
7793 if(!second) ReportClick("lift", x, y);
7794 MarkTargetSquares(0);
7795 DragPieceBegin(xPix, yPix, FALSE);
7796 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7797 promoSweep = defaultPromoChoice;
7798 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7799 Sweep(0); // Pawn that is going to promote: preview promotion piece
7803 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7806 // ignore clicks on holdings
7807 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7810 if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7811 gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7812 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7816 if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7817 DragPieceEnd(xPix, yPix); dragging = 0;
7819 // a deferred attempt to click-click move an empty square on top of a piece
7820 boards[currentMove][y][x] = EmptySquare;
7822 DrawPosition(FALSE, boards[currentMove]);
7823 fromX = fromY = -1; clearFlag = 0;
7826 if (appData.animateDragging) {
7827 /* Undo animation damage if any */
7828 DrawPosition(FALSE, NULL);
7831 /* Second up/down in same square; just abort move */
7834 gatingPiece = EmptySquare;
7837 ClearPremoveHighlights();
7838 MarkTargetSquares(-1);
7839 DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7841 /* First upclick in same square; start click-click mode */
7842 SetHighlights(x, y, -1, -1);
7849 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7850 fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7851 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7852 DisplayMessage(_("only marked squares are legal"),"");
7853 DrawPosition(TRUE, NULL);
7854 return; // ignore to-click
7857 /* we now have a different from- and (possibly off-board) to-square */
7858 /* Completed move */
7859 if(!sweepSelecting) {
7864 piece = boards[currentMove][fromY][fromX];
7866 saveAnimate = appData.animate;
7867 if (clickType == Press) {
7868 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7869 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7870 // must be Edit Position mode with empty-square selected
7871 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7872 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7875 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7878 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7879 killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7881 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7882 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7883 if(appData.sweepSelect) {
7884 promoSweep = defaultPromoChoice;
7885 if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7886 selectFlag = 0; lastX = xPix; lastY = yPix;
7887 ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7888 saveFlash = appData.flashCount; appData.flashCount = 0;
7889 Sweep(0); // Pawn that is going to promote: preview promotion piece
7891 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7892 MarkTargetSquares(1);
7894 return; // promo popup appears on up-click
7896 /* Finish clickclick move */
7897 if (appData.animate || appData.highlightLastMove) {
7898 SetHighlights(fromX, fromY, toX, toY);
7902 MarkTargetSquares(1);
7903 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7904 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7905 *promoRestrict = 0; appData.flashCount = saveFlash;
7906 if (appData.animate || appData.highlightLastMove) {
7907 SetHighlights(fromX, fromY, toX, toY);
7911 MarkTargetSquares(1);
7914 // [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
7915 /* Finish drag move */
7916 if (appData.highlightLastMove) {
7917 SetHighlights(fromX, fromY, toX, toY);
7922 if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7923 defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7924 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7925 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7926 dragging *= 2; // flag button-less dragging if we are dragging
7927 MarkTargetSquares(1);
7928 if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7930 kill2X = killX; kill2Y = killY;
7931 killX = x; killY = y; // remember this square as intermediate
7932 ReportClick("put", x, y); // and inform engine
7933 ReportClick("lift", x, y);
7934 MarkTargetSquares(0);
7938 DragPieceEnd(xPix, yPix); dragging = 0;
7939 /* Don't animate move and drag both */
7940 appData.animate = FALSE;
7941 MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7944 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7945 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7946 ChessSquare piece = boards[currentMove][fromY][fromX];
7947 if(gameMode == EditPosition && piece != EmptySquare &&
7948 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7951 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7952 n = PieceToNumber(piece - (int)BlackPawn);
7953 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7954 boards[currentMove][handSize-1 - n][0] = piece;
7955 boards[currentMove][handSize-1 - n][1]++;
7957 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7958 n = PieceToNumber(piece);
7959 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7960 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7961 boards[currentMove][n][BOARD_WIDTH-2]++;
7963 boards[currentMove][fromY][fromX] = EmptySquare;
7967 MarkTargetSquares(1);
7968 DrawPosition(TRUE, boards[currentMove]);
7972 // off-board moves should not be highlighted
7973 if(x < 0 || y < 0) {
7975 DrawPosition(FALSE, NULL);
7976 } else ReportClick("put", x, y);
7978 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7981 if(legal[toY][toX] == 2) { // highlight-induced promotion
7982 if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7983 else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7984 } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7985 if(!*promoRestrict) { // but has not done that yet
7986 deferChoice = TRUE; // set up retry for when it does
7987 return; // and wait for that
7989 promoChoice = ToLower(*promoRestrict); // force engine's choice
7990 deferChoice = FALSE;
7993 if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7994 SetHighlights(fromX, fromY, toX, toY);
7995 MarkTargetSquares(1);
7996 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7997 // [HGM] super: promotion to captured piece selected from holdings
7998 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7999 promotionChoice = TRUE;
8000 // kludge follows to temporarily execute move on display, without promoting yet
8001 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
8002 boards[currentMove][toY][toX] = p;
8003 DrawPosition(FALSE, boards[currentMove]);
8004 boards[currentMove][fromY][fromX] = p; // take back, but display stays
8005 boards[currentMove][toY][toX] = q;
8006 DisplayMessage("Click in holdings to choose piece", "");
8009 DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
8010 PromotionPopUp(promoChoice);
8012 int oldMove = currentMove;
8013 flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
8014 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
8015 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
8016 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
8017 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
8018 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
8019 DrawPosition(TRUE, boards[currentMove]);
8020 else DrawPosition(FALSE, NULL);
8024 appData.animate = saveAnimate;
8025 if (appData.animate || appData.animateDragging) {
8026 /* Undo animation damage if needed */
8027 // DrawPosition(FALSE, NULL);
8032 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
8033 { // front-end-free part taken out of PieceMenuPopup
8034 int whichMenu; int xSqr, ySqr;
8036 if(seekGraphUp) { // [HGM] seekgraph
8037 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
8038 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
8042 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
8043 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
8044 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
8045 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
8046 if(action == Press) {
8047 originalFlip = flipView;
8048 flipView = !flipView; // temporarily flip board to see game from partners perspective
8049 DrawPosition(TRUE, partnerBoard);
8050 DisplayMessage(partnerStatus, "");
8052 } else if(action == Release) {
8053 flipView = originalFlip;
8054 DrawPosition(TRUE, boards[currentMove]);
8060 xSqr = EventToSquare(x, BOARD_WIDTH);
8061 ySqr = EventToSquare(y, BOARD_HEIGHT);
8062 if (action == Release) {
8063 if(pieceSweep != EmptySquare) {
8064 EditPositionMenuEvent(pieceSweep, toX, toY);
8065 pieceSweep = EmptySquare;
8066 } else UnLoadPV(); // [HGM] pv
8068 if (action != Press) return -2; // return code to be ignored
8071 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8073 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8074 if (xSqr < 0 || ySqr < 0) return -1;
8075 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8076 if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8077 if(xSqr == createX && ySqr == createY && xSqr != BOARD_LEFT-2 && xSqr != BOARD_RGHT+1) {
8078 ChessSquare p = boards[currentMove][ySqr][xSqr];
8079 do { if(++p == EmptySquare) p = WhitePawn; } while(PieceToChar(p) == '.');
8080 boards[currentMove][ySqr][xSqr] = p; DrawPosition(FALSE, boards[currentMove]);
8083 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
8084 createX = toX = xSqr; createY = toY = ySqr; lastX = x, lastY = y;
8088 if(!appData.icsEngineAnalyze) return -1;
8089 case IcsPlayingWhite:
8090 case IcsPlayingBlack:
8091 if(!appData.zippyPlay) goto noZip;
8094 case MachinePlaysWhite:
8095 case MachinePlaysBlack:
8096 case TwoMachinesPlay: // [HGM] pv: use for showing PV
8097 if (!appData.dropMenu) {
8099 return 2; // flag front-end to grab mouse events
8101 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8102 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8105 if (xSqr < 0 || ySqr < 0) return -1;
8106 if (!appData.dropMenu || appData.testLegality &&
8107 gameInfo.variant != VariantBughouse &&
8108 gameInfo.variant != VariantCrazyhouse) return -1;
8109 whichMenu = 1; // drop menu
8115 if (((*fromX = xSqr) < 0) ||
8116 ((*fromY = ySqr) < 0)) {
8117 *fromX = *fromY = -1;
8121 *fromX = BOARD_WIDTH - 1 - *fromX;
8123 *fromY = BOARD_HEIGHT - 1 - *fromY;
8129 Wheel (int dir, int x, int y)
8131 if(gameMode == EditPosition) {
8132 int xSqr = EventToSquare(x, BOARD_WIDTH);
8133 int ySqr = EventToSquare(y, BOARD_HEIGHT);
8134 if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8135 if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8137 boards[currentMove][ySqr][xSqr] += dir;
8138 if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8139 if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8140 } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8141 DrawPosition(FALSE, boards[currentMove]);
8142 } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8146 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8148 // char * hint = lastHint;
8149 FrontEndProgramStats stats;
8151 stats.which = cps == &first ? 0 : 1;
8152 stats.depth = cpstats->depth;
8153 stats.nodes = cpstats->nodes;
8154 stats.score = cpstats->score;
8155 stats.time = cpstats->time;
8156 stats.pv = cpstats->movelist;
8157 stats.hint = lastHint;
8158 stats.an_move_index = 0;
8159 stats.an_move_count = 0;
8161 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8162 stats.hint = cpstats->move_name;
8163 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8164 stats.an_move_count = cpstats->nr_moves;
8167 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
8169 if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8170 && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8172 SetProgramStats( &stats );
8176 ClearEngineOutputPane (int which)
8178 static FrontEndProgramStats dummyStats;
8179 dummyStats.which = which;
8180 dummyStats.pv = "#";
8181 SetProgramStats( &dummyStats );
8184 #define MAXPLAYERS 500
8187 TourneyStandings (int display)
8189 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8190 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8191 char result, *p, *names[MAXPLAYERS];
8193 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8194 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8195 names[0] = p = strdup(appData.participants);
8196 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8198 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8200 while(result = appData.results[nr]) {
8201 color = Pairing(nr, nPlayers, &w, &b, &dummy);
8202 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8203 wScore = bScore = 0;
8205 case '+': wScore = 2; break;
8206 case '-': bScore = 2; break;
8207 case '=': wScore = bScore = 1; break;
8209 case '*': return strdup("busy"); // tourney not finished
8217 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8218 for(w=0; w<nPlayers; w++) {
8220 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8221 ranking[w] = b; points[w] = bScore; score[b] = -2;
8223 p = malloc(nPlayers*34+1);
8224 for(w=0; w<nPlayers && w<display; w++)
8225 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8231 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8232 { // count all piece types
8234 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8235 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8236 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8239 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8240 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8241 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8242 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8243 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
8244 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8249 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8251 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8252 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8254 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8255 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8256 if(myPawns == 2 && nMine == 3) // KPP
8257 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8258 if(myPawns == 1 && nMine == 2) // KP
8259 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8260 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8261 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8262 if(myPawns) return FALSE;
8263 if(pCnt[WhiteRook+side])
8264 return pCnt[BlackRook-side] ||
8265 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8266 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8267 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8268 if(pCnt[WhiteCannon+side]) {
8269 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8270 return majorDefense || pCnt[BlackAlfil-side] >= 2;
8272 if(pCnt[WhiteKnight+side])
8273 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8278 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8280 VariantClass v = gameInfo.variant;
8282 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8283 if(v == VariantShatranj) return TRUE; // always winnable through baring
8284 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8285 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8287 if(v == VariantXiangqi) {
8288 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8290 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8291 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8292 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8293 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8294 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8295 if(stale) // we have at least one last-rank P plus perhaps C
8296 return majors // KPKX
8297 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8299 return pCnt[WhiteFerz+side] // KCAK
8300 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8301 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8302 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8304 } else if(v == VariantKnightmate) {
8305 if(nMine == 1) return FALSE;
8306 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8307 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8308 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8310 if(nMine == 1) return FALSE; // bare King
8311 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
8312 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8313 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8314 // by now we have King + 1 piece (or multiple Bishops on the same color)
8315 if(pCnt[WhiteKnight+side])
8316 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8317 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8318 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8320 return (pCnt[BlackKnight-side]); // KBKN, KFKN
8321 if(pCnt[WhiteAlfil+side])
8322 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8323 if(pCnt[WhiteWazir+side])
8324 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8331 CompareWithRights (Board b1, Board b2)
8334 if(!CompareBoards(b1, b2)) return FALSE;
8335 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8336 /* compare castling rights */
8337 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8338 rights++; /* King lost rights, while rook still had them */
8339 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8340 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8341 rights++; /* but at least one rook lost them */
8343 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8345 if( b1[CASTLING][5] != NoRights ) {
8346 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8353 Adjudicate (ChessProgramState *cps)
8354 { // [HGM] some adjudications useful with buggy engines
8355 // [HGM] adjudicate: made into separate routine, which now can be called after every move
8356 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8357 // Actually ending the game is now based on the additional internal condition canAdjudicate.
8358 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8359 int k, drop, count = 0; static int bare = 1;
8360 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8361 Boolean canAdjudicate = !appData.icsActive;
8363 // most tests only when we understand the game, i.e. legality-checking on
8364 if( appData.testLegality )
8365 { /* [HGM] Some more adjudications for obstinate engines */
8366 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8367 static int moveCount = 6;
8369 char *reason = NULL;
8371 /* Count what is on board. */
8372 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8374 /* Some material-based adjudications that have to be made before stalemate test */
8375 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8376 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8377 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8378 if(canAdjudicate && appData.checkMates) {
8380 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8381 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8382 "Xboard adjudication: King destroyed", GE_XBOARD );
8387 /* Bare King in Shatranj (loses) or Losers (wins) */
8388 if( nrW == 1 || nrB == 1) {
8389 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8390 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
8391 if(canAdjudicate && appData.checkMates) {
8393 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8394 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8395 "Xboard adjudication: Bare king", GE_XBOARD );
8399 if( gameInfo.variant == VariantShatranj && --bare < 0)
8401 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8402 if(canAdjudicate && appData.checkMates) {
8403 /* but only adjudicate if adjudication enabled */
8405 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8406 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8407 "Xboard adjudication: Bare king", GE_XBOARD );
8414 // don't wait for engine to announce game end if we can judge ourselves
8415 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8417 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8418 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
8419 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8420 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8423 reason = "Xboard adjudication: 3rd check";
8424 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8435 reason = "Xboard adjudication: Stalemate";
8436 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8437 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8438 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8439 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8440 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8441 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8442 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8443 EP_CHECKMATE : EP_WINS);
8444 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8445 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8449 reason = "Xboard adjudication: Checkmate";
8450 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8451 if(gameInfo.variant == VariantShogi) {
8452 if(forwardMostMove > backwardMostMove
8453 && moveList[forwardMostMove-1][1] == '@'
8454 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8455 reason = "XBoard adjudication: pawn-drop mate";
8456 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8462 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8464 result = GameIsDrawn; break;
8466 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8468 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8472 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8474 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8475 GameEnds( result, reason, GE_XBOARD );
8479 /* Next absolutely insufficient mating material. */
8480 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8481 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8482 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8484 /* always flag draws, for judging claims */
8485 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8487 if(canAdjudicate && appData.materialDraws) {
8488 /* but only adjudicate them if adjudication enabled */
8489 if(engineOpponent) {
8490 SendToProgram("force\n", engineOpponent); // suppress reply
8491 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8493 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8498 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8499 if(gameInfo.variant == VariantXiangqi ?
8500 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8502 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8503 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8504 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8505 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8507 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8508 { /* if the first 3 moves do not show a tactical win, declare draw */
8509 if(engineOpponent) {
8510 SendToProgram("force\n", engineOpponent); // suppress reply
8511 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8513 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8516 } else moveCount = 6;
8518 if(gameInfo.variant == VariantMakruk && // Makruk counting rules
8519 (nrW == 1 || nrB == 1 || nr[WhitePawn] + nr[BlackPawn] == 0)) { // which only kick in when pawnless or bare King
8520 int maxcnt, his, mine, c, wom = WhiteOnMove(forwardMostMove);
8521 count = forwardMostMove;
8522 while(count >= backwardMostMove) {
8523 int np = nr[WhitePawn] + nr[BlackPawn];
8524 if(wom) mine = nrW, his = nrB, c = BlackPawn;
8525 else mine = nrB, his = nrW, c = WhitePawn;
8526 if(mine > 1 && np) { count++; break; }
8527 if(mine > 1) maxcnt = 64; else
8528 maxcnt = (nr[WhiteRook+c] > 1 ? 8 : nr[WhiteRook+c] ? 16 : nr[WhiteMan+c] > 1 ? 22 :
8529 nr[WhiteKnight+c] > 1 ? 32 : nr[WhiteMan+c] ? 44 : 64) - his - 1;
8530 while(boards[count][EP_STATUS] != EP_CAPTURE && count > backwardMostMove) count--; // seek previous character
8531 if(count == backwardMostMove) break;
8532 if(forwardMostMove - count >= 2*maxcnt + 1 - (mine == 1)) break;
8533 Count(boards[--count], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8535 if(forwardMostMove - count >= 2*maxcnt + 1 - (mine == 1)) {
8536 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8537 if(canAdjudicate && appData.ruleMoves >= 0) {
8538 GameEnds( GameIsDrawn, "Xboard adjudication: counting rule", GE_XBOARD );
8545 // Repetition draws and 50-move rule can be applied independently of legality testing
8547 /* Check for rep-draws */
8549 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8550 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8551 for(k = forwardMostMove-2;
8552 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8553 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8554 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8557 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8558 /* compare castling rights */
8559 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8560 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8561 rights++; /* King lost rights, while rook still had them */
8562 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8563 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8564 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8565 rights++; /* but at least one rook lost them */
8567 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8568 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8570 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8571 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8572 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8575 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8576 && appData.drawRepeats > 1) {
8577 /* adjudicate after user-specified nr of repeats */
8578 int result = GameIsDrawn;
8579 char *details = "XBoard adjudication: repetition draw";
8580 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8581 // [HGM] xiangqi: check for forbidden perpetuals
8582 int m, ourPerpetual = 1, hisPerpetual = 1;
8583 for(m=forwardMostMove; m>k; m-=2) {
8584 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8585 ourPerpetual = 0; // the current mover did not always check
8586 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8587 hisPerpetual = 0; // the opponent did not always check
8589 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8590 ourPerpetual, hisPerpetual);
8591 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8592 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8593 details = "Xboard adjudication: perpetual checking";
8595 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8596 break; // (or we would have caught him before). Abort repetition-checking loop.
8598 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8599 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8601 details = "Xboard adjudication: repetition";
8603 } else // it must be XQ
8604 // Now check for perpetual chases
8605 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8606 hisPerpetual = PerpetualChase(k, forwardMostMove);
8607 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8608 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8609 static char resdet[MSG_SIZ];
8610 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8612 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8614 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8615 break; // Abort repetition-checking loop.
8617 // if neither of us is checking or chasing all the time, or both are, it is draw
8619 if(engineOpponent) {
8620 SendToProgram("force\n", engineOpponent); // suppress reply
8621 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8623 GameEnds( result, details, GE_XBOARD );
8626 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8627 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8631 /* Now we test for 50-move draws. Determine ply count */
8632 count = forwardMostMove;
8633 /* look for last irreversble move */
8634 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8636 /* if we hit starting position, add initial plies */
8637 if( count == backwardMostMove )
8638 count -= initialRulePlies;
8639 count = forwardMostMove - count;
8640 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8641 // adjust reversible move counter for checks in Xiangqi
8642 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8643 if(i < backwardMostMove) i = backwardMostMove;
8644 while(i <= forwardMostMove) {
8645 lastCheck = inCheck; // check evasion does not count
8646 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8647 if(inCheck || lastCheck) count--; // check does not count
8651 if( count >= 100 && gameInfo.variant != VariantMakruk) // do not accept 50-move claims in Makruk
8652 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8653 /* this is used to judge if draw claims are legal */
8654 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8655 if(engineOpponent) {
8656 SendToProgram("force\n", engineOpponent); // suppress reply
8657 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8659 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8663 /* if draw offer is pending, treat it as a draw claim
8664 * when draw condition present, to allow engines a way to
8665 * claim draws before making their move to avoid a race
8666 * condition occurring after their move
8668 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8670 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8671 p = "Draw claim: 50-move rule";
8672 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8673 p = "Draw claim: 3-fold repetition";
8674 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8675 p = "Draw claim: insufficient mating material";
8676 if( p != NULL && canAdjudicate) {
8677 if(engineOpponent) {
8678 SendToProgram("force\n", engineOpponent); // suppress reply
8679 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8681 GameEnds( GameIsDrawn, p, GE_XBOARD );
8686 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8687 if(engineOpponent) {
8688 SendToProgram("force\n", engineOpponent); // suppress reply
8689 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8691 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8697 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8698 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8699 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8704 int pieces[10], squares[10], cnt=0, r, f, res;
8706 static PPROBE_EGBB probeBB;
8707 if(!appData.testLegality) return 10;
8708 if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8709 if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8710 if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8711 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8712 ChessSquare piece = boards[forwardMostMove][r][f];
8713 int black = (piece >= BlackPawn);
8714 int type = piece - black*BlackPawn;
8715 if(piece == EmptySquare) continue;
8716 if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8717 if(type == WhiteKing) type = WhiteQueen + 1;
8718 type = egbbCode[type];
8719 squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8720 pieces[cnt] = type + black*6;
8721 if(++cnt > 5) return 11;
8723 pieces[cnt] = squares[cnt] = 0;
8725 if(loaded == 2) return 13; // loading failed before
8727 char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8730 loaded = 2; // prepare for failure
8731 if(!path) return 13; // no egbb installed
8732 strncpy(buf, path + 8, MSG_SIZ);
8733 if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8734 snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8735 lib = LoadLibrary(buf);
8736 if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8737 loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8738 probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8739 if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8740 p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8741 loaded = 1; // success!
8743 res = probeBB(forwardMostMove & 1, pieces, squares);
8744 return res > 0 ? 1 : res < 0 ? -1 : 0;
8748 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8749 { // [HGM] book: this routine intercepts moves to simulate book replies
8750 char *bookHit = NULL;
8752 if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8754 snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8755 SendToProgram(buf, cps);
8757 //first determine if the incoming move brings opponent into his book
8758 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8759 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8760 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8761 if(bookHit != NULL && !cps->bookSuspend) {
8762 // make sure opponent is not going to reply after receiving move to book position
8763 SendToProgram("force\n", cps);
8764 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8766 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8767 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8768 // now arrange restart after book miss
8770 // after a book hit we never send 'go', and the code after the call to this routine
8771 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8772 char buf[MSG_SIZ], *move = bookHit;
8774 int fromX, fromY, toX, toY;
8778 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8779 &fromX, &fromY, &toX, &toY, &promoChar)) {
8780 (void) CoordsToAlgebraic(boards[forwardMostMove],
8781 PosFlags(forwardMostMove),
8782 fromY, fromX, toY, toX, promoChar, move);
8784 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8788 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8789 SendToProgram(buf, cps);
8790 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8791 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8792 SendToProgram("go\n", cps);
8793 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8794 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8795 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8796 SendToProgram("go\n", cps);
8797 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8799 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8803 LoadError (char *errmess, ChessProgramState *cps)
8804 { // unloads engine and switches back to -ncp mode if it was first
8805 if(cps->initDone) return FALSE;
8806 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8807 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8810 appData.noChessProgram = TRUE;
8811 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8812 gameMode = BeginningOfGame; ModeHighlight();
8815 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8816 DisplayMessage("", ""); // erase waiting message
8817 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8822 ChessProgramState *savedState;
8824 DeferredBookMove (void)
8826 if(savedState->lastPing != savedState->lastPong)
8827 ScheduleDelayedEvent(DeferredBookMove, 10);
8829 HandleMachineMove(savedMessage, savedState);
8832 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8833 static ChessProgramState *stalledEngine;
8834 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8837 HandleMachineMove (char *message, ChessProgramState *cps)
8839 static char firstLeg[20], legs;
8840 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8841 char realname[MSG_SIZ];
8842 int fromX, fromY, toX, toY;
8844 char promoChar, roar;
8849 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8850 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8851 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8852 DisplayError(_("Invalid pairing from pairing engine"), 0);
8855 pairingReceived = 1;
8857 return; // Skim the pairing messages here.
8860 oldError = cps->userError; cps->userError = 0;
8862 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8864 * Kludge to ignore BEL characters
8866 while (*message == '\007') message++;
8869 * [HGM] engine debug message: ignore lines starting with '#' character
8871 if(cps->debug && *message == '#') return;
8874 * Look for book output
8876 if (cps == &first && bookRequested) {
8877 if (message[0] == '\t' || message[0] == ' ') {
8878 /* Part of the book output is here; append it */
8879 strcat(bookOutput, message);
8880 strcat(bookOutput, " \n");
8882 } else if (bookOutput[0] != NULLCHAR) {
8883 /* All of book output has arrived; display it */
8884 char *p = bookOutput;
8885 while (*p != NULLCHAR) {
8886 if (*p == '\t') *p = ' ';
8889 DisplayInformation(bookOutput);
8890 bookRequested = FALSE;
8891 /* Fall through to parse the current output */
8896 * Look for machine move.
8898 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8899 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8901 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8902 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8903 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8904 stalledEngine = cps;
8905 if(appData.ponderNextMove) { // bring opponent out of ponder
8906 if(gameMode == TwoMachinesPlay) {
8907 if(cps->other->pause)
8908 PauseEngine(cps->other);
8910 SendToProgram("easy\n", cps->other);
8919 /* This method is only useful on engines that support ping */
8920 if(abortEngineThink) {
8921 if (appData.debugMode) {
8922 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8924 SendToProgram("undo\n", cps);
8928 if (cps->lastPing != cps->lastPong) {
8929 /* Extra move from before last new; ignore */
8930 if (appData.debugMode) {
8931 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8938 int machineWhite = FALSE;
8941 case BeginningOfGame:
8942 /* Extra move from before last reset; ignore */
8943 if (appData.debugMode) {
8944 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8951 /* Extra move after we tried to stop. The mode test is
8952 not a reliable way of detecting this problem, but it's
8953 the best we can do on engines that don't support ping.
8955 if (appData.debugMode) {
8956 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8957 cps->which, gameMode);
8959 SendToProgram("undo\n", cps);
8962 case MachinePlaysWhite:
8963 case IcsPlayingWhite:
8964 machineWhite = TRUE;
8967 case MachinePlaysBlack:
8968 case IcsPlayingBlack:
8969 machineWhite = FALSE;
8972 case TwoMachinesPlay:
8973 machineWhite = (cps->twoMachinesColor[0] == 'w');
8976 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8977 if (appData.debugMode) {
8979 "Ignoring move out of turn by %s, gameMode %d"
8980 ", forwardMost %d\n",
8981 cps->which, gameMode, forwardMostMove);
8987 if(cps->alphaRank) AlphaRank(machineMove, 4);
8989 // [HGM] lion: (some very limited) support for Alien protocol
8990 killX = killY = kill2X = kill2Y = -1;
8991 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8992 if(legs++) return; // middle leg contains only redundant info, ignore (but count it)
8993 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8996 if(p = strchr(machineMove, ',')) { // we got both legs in one (happens on book move)
8997 char *q = strchr(p+1, ','); // second comma?
8998 safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8999 if(q) legs = 2, p = q; else legs = 1; // with 3-leg move we clipof first two legs!
9000 safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
9002 if(firstLeg[0]) { // there was a previous leg;
9003 // only support case where same piece makes two step
9004 char buf[20], *p = machineMove+1, *q = buf+1, f;
9005 safeStrCpy(buf, machineMove, 20);
9006 while(isdigit(*q)) q++; // find start of to-square
9007 safeStrCpy(machineMove, firstLeg, 20);
9008 while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
9009 if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg
9010 else if(*p == *buf) // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling)
9011 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
9012 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
9013 firstLeg[0] = NULLCHAR; legs = 0;
9016 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
9017 &fromX, &fromY, &toX, &toY, &promoChar)) {
9018 /* Machine move could not be parsed; ignore it. */
9019 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
9020 machineMove, _(cps->which));
9021 DisplayMoveError(buf1);
9022 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
9023 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
9024 if (gameMode == TwoMachinesPlay) {
9025 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9031 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
9032 /* So we have to redo legality test with true e.p. status here, */
9033 /* to make sure an illegal e.p. capture does not slip through, */
9034 /* to cause a forfeit on a justified illegal-move complaint */
9035 /* of the opponent. */
9036 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
9038 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
9039 fromY, fromX, toY, toX, promoChar);
9040 if(moveType == IllegalMove) {
9041 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
9042 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
9043 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9046 } else if(!appData.fischerCastling)
9047 /* [HGM] Kludge to handle engines that send FRC-style castling
9048 when they shouldn't (like TSCP-Gothic) */
9050 case WhiteASideCastleFR:
9051 case BlackASideCastleFR:
9053 currentMoveString[2]++;
9055 case WhiteHSideCastleFR:
9056 case BlackHSideCastleFR:
9058 currentMoveString[2]--;
9060 default: ; // nothing to do, but suppresses warning of pedantic compilers
9063 hintRequested = FALSE;
9064 lastHint[0] = NULLCHAR;
9065 bookRequested = FALSE;
9066 /* Program may be pondering now */
9067 cps->maybeThinking = TRUE;
9068 if (cps->sendTime == 2) cps->sendTime = 1;
9069 if (cps->offeredDraw) cps->offeredDraw--;
9071 /* [AS] Save move info*/
9072 pvInfoList[ forwardMostMove ].score = programStats.score;
9073 pvInfoList[ forwardMostMove ].depth = programStats.depth;
9074 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
9076 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
9078 /* Test suites abort the 'game' after one move */
9079 if(*appData.finger) {
9081 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
9082 if(!f) f = fopen(appData.finger, "w");
9083 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
9084 else { DisplayFatalError("Bad output file", errno, 0); return; }
9086 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9089 if(solvingTime >= 0) {
9090 snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
9091 totalTime += solvingTime; first.matchWins++; solvingTime = -1;
9093 snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
9094 if(solvingTime == -2) second.matchWins++;
9096 OutputKibitz(2, buf1);
9097 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9100 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
9101 if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9104 while( count < adjudicateLossPlies ) {
9105 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9108 score = -score; /* Flip score for winning side */
9111 if( score > appData.adjudicateLossThreshold ) {
9118 if( count >= adjudicateLossPlies ) {
9119 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9121 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9122 "Xboard adjudication",
9129 if(Adjudicate(cps)) {
9130 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9131 return; // [HGM] adjudicate: for all automatic game ends
9135 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9137 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9138 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9140 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9142 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9144 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9145 char buf[3*MSG_SIZ];
9147 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9148 programStats.score / 100.,
9150 programStats.time / 100.,
9151 (unsigned int)programStats.nodes,
9152 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9153 programStats.movelist);
9159 /* [AS] Clear stats for next move */
9160 ClearProgramStats();
9161 thinkOutput[0] = NULLCHAR;
9162 hiddenThinkOutputState = 0;
9165 if (gameMode == TwoMachinesPlay) {
9166 /* [HGM] relaying draw offers moved to after reception of move */
9167 /* and interpreting offer as claim if it brings draw condition */
9168 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9169 SendToProgram("draw\n", cps->other);
9171 if (cps->other->sendTime) {
9172 SendTimeRemaining(cps->other,
9173 cps->other->twoMachinesColor[0] == 'w');
9175 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9176 if (firstMove && !bookHit) {
9178 if (cps->other->useColors) {
9179 SendToProgram(cps->other->twoMachinesColor, cps->other);
9181 SendToProgram("go\n", cps->other);
9183 cps->other->maybeThinking = TRUE;
9186 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9188 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9190 if (!pausing && appData.ringBellAfterMoves) {
9191 if(!roar) RingBell();
9195 * Reenable menu items that were disabled while
9196 * machine was thinking
9198 if (gameMode != TwoMachinesPlay)
9199 SetUserThinkingEnables();
9201 // [HGM] book: after book hit opponent has received move and is now in force mode
9202 // force the book reply into it, and then fake that it outputted this move by jumping
9203 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9205 static char bookMove[MSG_SIZ]; // a bit generous?
9207 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9208 strcat(bookMove, bookHit);
9211 programStats.nodes = programStats.depth = programStats.time =
9212 programStats.score = programStats.got_only_move = 0;
9213 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9215 if(cps->lastPing != cps->lastPong) {
9216 savedMessage = message; // args for deferred call
9218 ScheduleDelayedEvent(DeferredBookMove, 10);
9227 /* Set special modes for chess engines. Later something general
9228 * could be added here; for now there is just one kludge feature,
9229 * needed because Crafty 15.10 and earlier don't ignore SIGINT
9230 * when "xboard" is given as an interactive command.
9232 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9233 cps->useSigint = FALSE;
9234 cps->useSigterm = FALSE;
9236 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9237 ParseFeatures(message+8, cps); if(tryNr < 3) tryNr = 3;
9238 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9241 if (!strncmp(message, "setup ", 6) &&
9242 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9243 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9244 ) { // [HGM] allow first engine to define opening position
9245 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9246 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9248 if(sscanf(message, "setup (%s", buf) == 1) {
9249 s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9250 ASSIGN(appData.pieceToCharTable, buf);
9252 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9254 while(message[s] && message[s++] != ' ');
9255 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9256 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9257 // if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9258 if(hand > h) handSize = hand; else handSize = h;
9259 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9260 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
9261 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9262 if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9263 startedFromSetupPosition = FALSE;
9266 if(startedFromSetupPosition) return;
9267 ParseFEN(boards[0], &dummy, message+s, FALSE);
9268 DrawPosition(TRUE, boards[0]);
9269 CopyBoard(initialPosition, boards[0]);
9270 startedFromSetupPosition = TRUE;
9273 if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9274 ChessSquare piece = WhitePawn;
9275 char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9276 if(*p == '+') promoted++, ID = *++p;
9277 if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9278 piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9279 if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9280 /* always accept definition of */ && piece != WhiteFalcon && piece != BlackFalcon
9281 /* wild-card pieces. */ && piece != WhiteCobra && piece != BlackCobra
9282 /* For variants we don't have */ && gameInfo.variant != VariantBerolina
9283 /* correct rules for, we cannot */ && gameInfo.variant != VariantCylinder
9284 /* enforce legality on our own! */ && gameInfo.variant != VariantUnknown
9285 && gameInfo.variant != VariantGreat
9286 && gameInfo.variant != VariantFairy ) return;
9287 if(piece < EmptySquare) {
9289 ASSIGN(pieceDesc[piece], buf1);
9290 if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9294 if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9296 LeftClick(Press, 0, 0); // finish the click that was interrupted
9297 } else if(promoSweep != EmptySquare) {
9298 promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9299 if(strlen(promoRestrict) > 1) Sweep(0);
9303 /* [HGM] Allow engine to set up a position. Don't ask me why one would
9304 * want this, I was asked to put it in, and obliged.
9306 if (!strncmp(message, "setboard ", 9)) {
9307 Board initial_position;
9309 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9311 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9312 DisplayError(_("Bad FEN received from engine"), 0);
9316 CopyBoard(boards[0], initial_position);
9317 initialRulePlies = FENrulePlies;
9318 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9319 else gameMode = MachinePlaysBlack;
9320 DrawPosition(FALSE, boards[currentMove]);
9326 * Look for communication commands
9328 if (!strncmp(message, "telluser ", 9)) {
9329 if(message[9] == '\\' && message[10] == '\\')
9330 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9332 DisplayNote(message + 9);
9335 if (!strncmp(message, "tellusererror ", 14)) {
9337 if(message[14] == '\\' && message[15] == '\\')
9338 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9340 DisplayError(message + 14, 0);
9343 if (!strncmp(message, "tellopponent ", 13)) {
9344 if (appData.icsActive) {
9346 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9350 DisplayNote(message + 13);
9354 if (!strncmp(message, "tellothers ", 11)) {
9355 if (appData.icsActive) {
9357 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9360 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9363 if (!strncmp(message, "tellall ", 8)) {
9364 if (appData.icsActive) {
9366 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9370 DisplayNote(message + 8);
9374 if (strncmp(message, "warning", 7) == 0) {
9375 /* Undocumented feature, use tellusererror in new code */
9376 DisplayError(message, 0);
9379 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9380 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9381 strcat(realname, " query");
9382 AskQuestion(realname, buf2, buf1, cps->pr);
9385 /* Commands from the engine directly to ICS. We don't allow these to be
9386 * sent until we are logged on. Crafty kibitzes have been known to
9387 * interfere with the login process.
9390 if (!strncmp(message, "tellics ", 8)) {
9391 SendToICS(message + 8);
9395 if (!strncmp(message, "tellicsnoalias ", 15)) {
9396 SendToICS(ics_prefix);
9397 SendToICS(message + 15);
9401 /* The following are for backward compatibility only */
9402 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9403 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9404 SendToICS(ics_prefix);
9410 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9411 if(initPing == cps->lastPong) {
9412 if(gameInfo.variant == VariantUnknown) {
9413 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9414 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9415 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9419 if(cps->lastPing == cps->lastPong && abortEngineThink) {
9420 abortEngineThink = FALSE;
9421 DisplayMessage("", "");
9426 if(!strncmp(message, "highlight ", 10)) {
9427 if(appData.testLegality && !*engineVariant && appData.markers) return;
9428 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9431 if(!strncmp(message, "click ", 6)) {
9432 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9433 if(appData.testLegality || !appData.oneClick) return;
9434 sscanf(message+6, "%c%d%c", &f, &y, &c);
9435 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9436 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9437 x = x*squareSize + (x+1)*lineGap + squareSize/2;
9438 y = y*squareSize + (y+1)*lineGap + squareSize/2;
9439 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9440 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9441 LeftClick(Release, lastLeftX, lastLeftY);
9442 controlKey = (c == ',');
9443 LeftClick(Press, x, y);
9444 LeftClick(Release, x, y);
9445 first.highlight = f;
9448 if(strncmp(message, "uciok", 5) == 0) { // response to "uci" probe
9449 appData.isUCI[0] = isUCI = 1;
9450 ReplaceEngine(&first, 0); // retry install as UCI
9454 * If the move is illegal, cancel it and redraw the board.
9455 * Also deal with other error cases. Matching is rather loose
9456 * here to accommodate engines written before the spec.
9458 if (strncmp(message + 1, "llegal move", 11) == 0 ||
9459 strncmp(message, "Error", 5) == 0) {
9460 if (StrStr(message, "name") ||
9461 StrStr(message, "rating") || StrStr(message, "?") ||
9462 StrStr(message, "result") || StrStr(message, "board") ||
9463 StrStr(message, "bk") || StrStr(message, "computer") ||
9464 StrStr(message, "variant") || StrStr(message, "hint") ||
9465 StrStr(message, "random") || StrStr(message, "depth") ||
9466 StrStr(message, "accepted")) {
9469 if (StrStr(message, "protover")) {
9470 /* Program is responding to input, so it's apparently done
9471 initializing, and this error message indicates it is
9472 protocol version 1. So we don't need to wait any longer
9473 for it to initialize and send feature commands. */
9474 FeatureDone(cps, 1);
9475 cps->protocolVersion = 1;
9478 cps->maybeThinking = FALSE;
9480 if (StrStr(message, "draw")) {
9481 /* Program doesn't have "draw" command */
9482 cps->sendDrawOffers = 0;
9485 if (cps->sendTime != 1 &&
9486 (StrStr(message, "time") || StrStr(message, "otim"))) {
9487 /* Program apparently doesn't have "time" or "otim" command */
9491 if (StrStr(message, "analyze")) {
9492 cps->analysisSupport = FALSE;
9493 cps->analyzing = FALSE;
9494 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9495 EditGameEvent(); // [HGM] try to preserve loaded game
9496 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9497 DisplayError(buf2, 0);
9500 if (StrStr(message, "(no matching move)st")) {
9501 /* Special kludge for GNU Chess 4 only */
9502 cps->stKludge = TRUE;
9503 SendTimeControl(cps, movesPerSession, timeControl,
9504 timeIncrement, appData.searchDepth,
9508 if (StrStr(message, "(no matching move)sd")) {
9509 /* Special kludge for GNU Chess 4 only */
9510 cps->sdKludge = TRUE;
9511 SendTimeControl(cps, movesPerSession, timeControl,
9512 timeIncrement, appData.searchDepth,
9516 if (!StrStr(message, "llegal")) {
9519 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9520 gameMode == IcsIdle) return;
9521 if (forwardMostMove <= backwardMostMove) return;
9522 if (pausing) PauseEvent();
9523 if(appData.forceIllegal) {
9524 // [HGM] illegal: machine refused move; force position after move into it
9525 SendToProgram("force\n", cps);
9526 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9527 // we have a real problem now, as SendBoard will use the a2a3 kludge
9528 // when black is to move, while there might be nothing on a2 or black
9529 // might already have the move. So send the board as if white has the move.
9530 // But first we must change the stm of the engine, as it refused the last move
9531 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9532 if(WhiteOnMove(forwardMostMove)) {
9533 SendToProgram("a7a6\n", cps); // for the engine black still had the move
9534 SendBoard(cps, forwardMostMove); // kludgeless board
9536 SendToProgram("a2a3\n", cps); // for the engine white still had the move
9537 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9538 SendBoard(cps, forwardMostMove+1); // kludgeless board
9540 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9541 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9542 gameMode == TwoMachinesPlay)
9543 SendToProgram("go\n", cps);
9546 if (gameMode == PlayFromGameFile) {
9547 /* Stop reading this game file */
9548 gameMode = EditGame;
9551 /* [HGM] illegal-move claim should forfeit game when Xboard */
9552 /* only passes fully legal moves */
9553 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9554 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9555 "False illegal-move claim", GE_XBOARD );
9556 return; // do not take back move we tested as valid
9558 currentMove = forwardMostMove-1;
9559 DisplayMove(currentMove-1); /* before DisplayMoveError */
9560 SwitchClocks(forwardMostMove-1); // [HGM] race
9561 DisplayBothClocks();
9562 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9563 parseList[currentMove], _(cps->which));
9564 DisplayMoveError(buf1);
9565 DrawPosition(FALSE, boards[currentMove]);
9567 SetUserThinkingEnables();
9570 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9571 /* Program has a broken "time" command that
9572 outputs a string not ending in newline.
9576 if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9577 if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9578 sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1 ) return;
9582 * If chess program startup fails, exit with an error message.
9583 * Attempts to recover here are futile. [HGM] Well, we try anyway
9585 if ((StrStr(message, "unknown host") != NULL)
9586 || (StrStr(message, "No remote directory") != NULL)
9587 || (StrStr(message, "not found") != NULL)
9588 || (StrStr(message, "No such file") != NULL)
9589 || (StrStr(message, "can't alloc") != NULL)
9590 || (StrStr(message, "Permission denied") != NULL)) {
9592 cps->maybeThinking = FALSE;
9593 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9594 _(cps->which), cps->program, cps->host, message);
9595 RemoveInputSource(cps->isr);
9596 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9597 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9598 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9604 * Look for hint output
9606 if (sscanf(message, "Hint: %s", buf1) == 1) {
9607 if (cps == &first && hintRequested) {
9608 hintRequested = FALSE;
9609 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9610 &fromX, &fromY, &toX, &toY, &promoChar)) {
9611 (void) CoordsToAlgebraic(boards[forwardMostMove],
9612 PosFlags(forwardMostMove),
9613 fromY, fromX, toY, toX, promoChar, buf1);
9614 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9615 DisplayInformation(buf2);
9617 /* Hint move could not be parsed!? */
9618 snprintf(buf2, sizeof(buf2),
9619 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9620 buf1, _(cps->which));
9621 DisplayError(buf2, 0);
9624 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9630 * Ignore other messages if game is not in progress
9632 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9633 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9636 * look for win, lose, draw, or draw offer
9638 if (strncmp(message, "1-0", 3) == 0) {
9639 char *p, *q, *r = "";
9640 p = strchr(message, '{');
9648 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9650 } else if (strncmp(message, "0-1", 3) == 0) {
9651 char *p, *q, *r = "";
9652 p = strchr(message, '{');
9660 /* Kludge for Arasan 4.1 bug */
9661 if (strcmp(r, "Black resigns") == 0) {
9662 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9665 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9667 } else if (strncmp(message, "1/2", 3) == 0) {
9668 char *p, *q, *r = "";
9669 p = strchr(message, '{');
9678 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9681 } else if (strncmp(message, "White resign", 12) == 0) {
9682 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9684 } else if (strncmp(message, "Black resign", 12) == 0) {
9685 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9687 } else if (strncmp(message, "White matches", 13) == 0 ||
9688 strncmp(message, "Black matches", 13) == 0 ) {
9689 /* [HGM] ignore GNUShogi noises */
9691 } else if (strncmp(message, "White", 5) == 0 &&
9692 message[5] != '(' &&
9693 StrStr(message, "Black") == NULL) {
9694 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9696 } else if (strncmp(message, "Black", 5) == 0 &&
9697 message[5] != '(') {
9698 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9700 } else if (strcmp(message, "resign") == 0 ||
9701 strcmp(message, "computer resigns") == 0) {
9703 case MachinePlaysBlack:
9704 case IcsPlayingBlack:
9705 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9707 case MachinePlaysWhite:
9708 case IcsPlayingWhite:
9709 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9711 case TwoMachinesPlay:
9712 if (cps->twoMachinesColor[0] == 'w')
9713 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9715 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9722 } else if (strncmp(message, "opponent mates", 14) == 0) {
9724 case MachinePlaysBlack:
9725 case IcsPlayingBlack:
9726 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9728 case MachinePlaysWhite:
9729 case IcsPlayingWhite:
9730 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9732 case TwoMachinesPlay:
9733 if (cps->twoMachinesColor[0] == 'w')
9734 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9736 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9743 } else if (strncmp(message, "computer mates", 14) == 0) {
9745 case MachinePlaysBlack:
9746 case IcsPlayingBlack:
9747 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9749 case MachinePlaysWhite:
9750 case IcsPlayingWhite:
9751 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9753 case TwoMachinesPlay:
9754 if (cps->twoMachinesColor[0] == 'w')
9755 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9757 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9764 } else if (strncmp(message, "checkmate", 9) == 0) {
9765 if (WhiteOnMove(forwardMostMove)) {
9766 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9768 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9771 } else if (strstr(message, "Draw") != NULL ||
9772 strstr(message, "game is a draw") != NULL) {
9773 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9775 } else if (strstr(message, "offer") != NULL &&
9776 strstr(message, "draw") != NULL) {
9778 if (appData.zippyPlay && first.initDone) {
9779 /* Relay offer to ICS */
9780 SendToICS(ics_prefix);
9781 SendToICS("draw\n");
9784 cps->offeredDraw = 2; /* valid until this engine moves twice */
9785 if (gameMode == TwoMachinesPlay) {
9786 if (cps->other->offeredDraw) {
9787 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9788 /* [HGM] in two-machine mode we delay relaying draw offer */
9789 /* until after we also have move, to see if it is really claim */
9791 } else if (gameMode == MachinePlaysWhite ||
9792 gameMode == MachinePlaysBlack) {
9793 if (userOfferedDraw) {
9794 DisplayInformation(_("Machine accepts your draw offer"));
9795 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9797 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9804 * Look for thinking output
9806 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9807 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9809 int plylev, mvleft, mvtot, curscore, time;
9810 char mvname[MOVE_LEN];
9814 int prefixHint = FALSE;
9815 mvname[0] = NULLCHAR;
9818 case MachinePlaysBlack:
9819 case IcsPlayingBlack:
9820 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9822 case MachinePlaysWhite:
9823 case IcsPlayingWhite:
9824 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9829 case IcsObserving: /* [DM] icsEngineAnalyze */
9830 if (!appData.icsEngineAnalyze) ignore = TRUE;
9832 case TwoMachinesPlay:
9833 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9843 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9846 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9847 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9848 char score_buf[MSG_SIZ];
9850 if(nodes>>32 == u64Const(0xFFFFFFFF)) // [HGM] negative node count read
9851 nodes += u64Const(0x100000000);
9853 if (plyext != ' ' && plyext != '\t') {
9857 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9858 if( cps->scoreIsAbsolute &&
9859 ( gameMode == MachinePlaysBlack ||
9860 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9861 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9862 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9863 !WhiteOnMove(currentMove)
9866 curscore = -curscore;
9869 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9871 if(*bestMove) { // rememer time best EPD move was first found
9872 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9873 ChessMove mt; char *p = bestMove;
9874 int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9876 while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9877 if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9878 solvingTime = (solvingTime < 0 ? time : solvingTime);
9882 while(*p && *p != ' ') p++;
9883 while(*p == ' ') p++;
9885 if(!solved) solvingTime = -1;
9887 if(*avoidMove && !solved) {
9888 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9889 ChessMove mt; char *p = avoidMove, solved = 1;
9890 int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9891 while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9892 if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9893 solved = 0; solvingTime = -2;
9896 while(*p && *p != ' ') p++;
9897 while(*p == ' ') p++;
9899 if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9902 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9905 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9906 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9907 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9908 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9909 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9910 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9914 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9915 DisplayError(_("failed writing PV"), 0);
9918 tempStats.depth = plylev;
9919 tempStats.nodes = nodes;
9920 tempStats.time = time;
9921 tempStats.score = curscore;
9922 tempStats.got_only_move = 0;
9924 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9927 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9928 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9929 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9930 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9931 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9932 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9933 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9934 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9937 /* Buffer overflow protection */
9938 if (pv[0] != NULLCHAR) {
9939 if (strlen(pv) >= sizeof(tempStats.movelist)
9940 && appData.debugMode) {
9942 "PV is too long; using the first %u bytes.\n",
9943 (unsigned) sizeof(tempStats.movelist) - 1);
9946 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9948 sprintf(tempStats.movelist, " no PV\n");
9951 if (tempStats.seen_stat) {
9952 tempStats.ok_to_send = 1;
9955 if (strchr(tempStats.movelist, '(') != NULL) {
9956 tempStats.line_is_book = 1;
9957 tempStats.nr_moves = 0;
9958 tempStats.moves_left = 0;
9960 tempStats.line_is_book = 0;
9963 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9964 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9966 SendProgramStatsToFrontend( cps, &tempStats );
9969 [AS] Protect the thinkOutput buffer from overflow... this
9970 is only useful if buf1 hasn't overflowed first!
9972 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9973 if(curscore >= MATE_SCORE)
9974 snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9975 else if(curscore <= -MATE_SCORE)
9976 snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9978 snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9979 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9981 (gameMode == TwoMachinesPlay ?
9982 ToUpper(cps->twoMachinesColor[0]) : ' '),
9984 prefixHint ? lastHint : "",
9985 prefixHint ? " " : "" );
9987 if( buf1[0] != NULLCHAR ) {
9988 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9990 if( strlen(pv) > max_len ) {
9991 if( appData.debugMode) {
9992 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9994 pv[max_len+1] = '\0';
9997 strcat( thinkOutput, pv);
10000 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
10001 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10002 DisplayMove(currentMove - 1);
10006 } else if ((p=StrStr(message, "(only move)")) != NULL) {
10007 /* crafty (9.25+) says "(only move) <move>"
10008 * if there is only 1 legal move
10010 sscanf(p, "(only move) %s", buf1);
10011 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
10012 sprintf(programStats.movelist, "%s (only move)", buf1);
10013 programStats.depth = 1;
10014 programStats.nr_moves = 1;
10015 programStats.moves_left = 1;
10016 programStats.nodes = 1;
10017 programStats.time = 1;
10018 programStats.got_only_move = 1;
10020 /* Not really, but we also use this member to
10021 mean "line isn't going to change" (Crafty
10022 isn't searching, so stats won't change) */
10023 programStats.line_is_book = 1;
10025 SendProgramStatsToFrontend( cps, &programStats );
10027 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10028 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10029 DisplayMove(currentMove - 1);
10032 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
10033 &time, &nodes, &plylev, &mvleft,
10034 &mvtot, mvname) >= 5) {
10035 /* The stat01: line is from Crafty (9.29+) in response
10036 to the "." command */
10037 programStats.seen_stat = 1;
10038 cps->maybeThinking = TRUE;
10040 if (programStats.got_only_move || !appData.periodicUpdates)
10043 programStats.depth = plylev;
10044 programStats.time = time;
10045 programStats.nodes = nodes;
10046 programStats.moves_left = mvleft;
10047 programStats.nr_moves = mvtot;
10048 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
10049 programStats.ok_to_send = 1;
10050 programStats.movelist[0] = '\0';
10052 SendProgramStatsToFrontend( cps, &programStats );
10056 } else if (strncmp(message,"++",2) == 0) {
10057 /* Crafty 9.29+ outputs this */
10058 programStats.got_fail = 2;
10061 } else if (strncmp(message,"--",2) == 0) {
10062 /* Crafty 9.29+ outputs this */
10063 programStats.got_fail = 1;
10066 } else if (thinkOutput[0] != NULLCHAR &&
10067 strncmp(message, " ", 4) == 0) {
10068 unsigned message_len;
10071 while (*p && *p == ' ') p++;
10073 message_len = strlen( p );
10075 /* [AS] Avoid buffer overflow */
10076 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
10077 strcat(thinkOutput, " ");
10078 strcat(thinkOutput, p);
10081 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
10082 strcat(programStats.movelist, " ");
10083 strcat(programStats.movelist, p);
10086 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10087 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10088 DisplayMove(currentMove - 1);
10094 buf1[0] = NULLCHAR;
10096 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
10097 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
10099 ChessProgramStats cpstats;
10101 if (plyext != ' ' && plyext != '\t') {
10105 /* [AS] Negate score if machine is playing black and reporting absolute scores */
10106 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
10107 curscore = -curscore;
10110 cpstats.depth = plylev;
10111 cpstats.nodes = nodes;
10112 cpstats.time = time;
10113 cpstats.score = curscore;
10114 cpstats.got_only_move = 0;
10115 cpstats.movelist[0] = '\0';
10117 if (buf1[0] != NULLCHAR) {
10118 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10121 cpstats.ok_to_send = 0;
10122 cpstats.line_is_book = 0;
10123 cpstats.nr_moves = 0;
10124 cpstats.moves_left = 0;
10126 SendProgramStatsToFrontend( cps, &cpstats );
10133 /* Parse a game score from the character string "game", and
10134 record it as the history of the current game. The game
10135 score is NOT assumed to start from the standard position.
10136 The display is not updated in any way.
10139 ParseGameHistory (char *game)
10141 ChessMove moveType;
10142 int fromX, fromY, toX, toY, boardIndex, mask;
10147 if (appData.debugMode)
10148 fprintf(debugFP, "Parsing game history: %s\n", game);
10150 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10151 gameInfo.site = StrSave(appData.icsHost);
10152 gameInfo.date = PGNDate();
10153 gameInfo.round = StrSave("-");
10155 /* Parse out names of players */
10156 while (*game == ' ') game++;
10158 while (*game != ' ') *p++ = *game++;
10160 gameInfo.white = StrSave(buf);
10161 while (*game == ' ') game++;
10163 while (*game != ' ' && *game != '\n') *p++ = *game++;
10165 gameInfo.black = StrSave(buf);
10168 boardIndex = blackPlaysFirst ? 1 : 0;
10171 yyboardindex = boardIndex;
10172 moveType = (ChessMove) Myylex();
10173 switch (moveType) {
10174 case IllegalMove: /* maybe suicide chess, etc. */
10175 if (appData.debugMode) {
10176 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10177 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10178 setbuf(debugFP, NULL);
10180 case WhitePromotion:
10181 case BlackPromotion:
10182 case WhiteNonPromotion:
10183 case BlackNonPromotion:
10186 case WhiteCapturesEnPassant:
10187 case BlackCapturesEnPassant:
10188 case WhiteKingSideCastle:
10189 case WhiteQueenSideCastle:
10190 case BlackKingSideCastle:
10191 case BlackQueenSideCastle:
10192 case WhiteKingSideCastleWild:
10193 case WhiteQueenSideCastleWild:
10194 case BlackKingSideCastleWild:
10195 case BlackQueenSideCastleWild:
10197 case WhiteHSideCastleFR:
10198 case WhiteASideCastleFR:
10199 case BlackHSideCastleFR:
10200 case BlackASideCastleFR:
10202 fromX = currentMoveString[0] - AAA;
10203 fromY = currentMoveString[1] - ONE;
10204 toX = currentMoveString[2] - AAA;
10205 toY = currentMoveString[3] - ONE;
10206 promoChar = currentMoveString[4];
10210 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10211 fromX = moveType == WhiteDrop ?
10212 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10213 (int) CharToPiece(ToLower(currentMoveString[0]));
10215 toX = currentMoveString[2] - AAA;
10216 toY = currentMoveString[3] - ONE;
10217 promoChar = NULLCHAR;
10219 case AmbiguousMove:
10221 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10222 if (appData.debugMode) {
10223 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10224 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10225 setbuf(debugFP, NULL);
10227 DisplayError(buf, 0);
10229 case ImpossibleMove:
10231 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10232 if (appData.debugMode) {
10233 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10234 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10235 setbuf(debugFP, NULL);
10237 DisplayError(buf, 0);
10240 if (boardIndex < backwardMostMove) {
10241 /* Oops, gap. How did that happen? */
10242 DisplayError(_("Gap in move list"), 0);
10245 backwardMostMove = blackPlaysFirst ? 1 : 0;
10246 if (boardIndex > forwardMostMove) {
10247 forwardMostMove = boardIndex;
10251 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10252 strcat(parseList[boardIndex-1], " ");
10253 strcat(parseList[boardIndex-1], yy_text);
10265 case GameUnfinished:
10266 if (gameMode == IcsExamining) {
10267 if (boardIndex < backwardMostMove) {
10268 /* Oops, gap. How did that happen? */
10271 backwardMostMove = blackPlaysFirst ? 1 : 0;
10274 gameInfo.result = moveType;
10275 p = strchr(yy_text, '{');
10276 if (p == NULL) p = strchr(yy_text, '(');
10279 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10281 q = strchr(p, *p == '{' ? '}' : ')');
10282 if (q != NULL) *q = NULLCHAR;
10285 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10286 gameInfo.resultDetails = StrSave(p);
10289 if (boardIndex >= forwardMostMove &&
10290 !(gameMode == IcsObserving && ics_gamenum == -1)) {
10291 backwardMostMove = blackPlaysFirst ? 1 : 0;
10294 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10295 fromY, fromX, toY, toX, promoChar,
10296 parseList[boardIndex]);
10297 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10298 /* currentMoveString is set as a side-effect of yylex */
10299 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10300 strcat(moveList[boardIndex], "\n");
10302 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10303 mask = (WhiteOnMove(boardIndex) ? 0xFF : 0xFF00);
10304 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10310 if(boards[boardIndex][CHECK_COUNT]) boards[boardIndex][CHECK_COUNT] -= mask & 0x101;
10311 if(!boards[boardIndex][CHECK_COUNT] || boards[boardIndex][CHECK_COUNT] & mask) {
10312 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+");
10317 strcat(parseList[boardIndex - 1], "#");
10324 /* Apply a move to the given board */
10326 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10328 ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10329 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10331 /* [HGM] compute & store e.p. status and castling rights for new position */
10332 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10334 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10335 oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8;
10336 board[EP_STATUS] = EP_NONE;
10337 board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
10339 if (fromY == DROP_RANK) {
10340 /* must be first */
10341 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10342 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10345 piece = board[toY][toX] = (ChessSquare) fromX;
10347 // ChessSquare victim;
10350 if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10351 // victim = board[killY][killX],
10352 killed = board[killY][killX],
10353 board[killY][killX] = EmptySquare,
10354 board[EP_STATUS] = EP_CAPTURE;
10355 if( kill2X >= 0 && kill2Y >= 0)
10356 killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10359 if( board[toY][toX] != EmptySquare ) {
10360 board[EP_STATUS] = EP_CAPTURE;
10361 if( (fromX != toX || fromY != toY) && // not igui!
10362 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10363 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
10364 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10368 pawn = board[fromY][fromX];
10369 if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10370 if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10371 captured = board[lastRank][lastFile]; // remove victim
10372 board[lastRank][lastFile] = EmptySquare;
10373 pawn = EmptySquare; // kludge to suppress old e.p. code
10376 if( pawn == WhiteLance || pawn == BlackLance ) {
10377 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10378 if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10379 else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10382 if( pawn == WhitePawn ) {
10383 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10384 board[EP_STATUS] = EP_PAWN_MOVE;
10385 if( toY-fromY>=2) {
10386 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10387 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
10388 gameInfo.variant != VariantBerolina || toX < fromX)
10389 board[EP_STATUS] = toX | berolina;
10390 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10391 gameInfo.variant != VariantBerolina || toX > fromX)
10392 board[EP_STATUS] = toX;
10393 board[LAST_TO] = toX + 256*toY;
10396 if( pawn == BlackPawn ) {
10397 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10398 board[EP_STATUS] = EP_PAWN_MOVE;
10399 if( toY-fromY<= -2) {
10400 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10401 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
10402 gameInfo.variant != VariantBerolina || toX < fromX)
10403 board[EP_STATUS] = toX | berolina;
10404 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10405 gameInfo.variant != VariantBerolina || toX > fromX)
10406 board[EP_STATUS] = toX;
10407 board[LAST_TO] = toX + 256*toY;
10411 if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10412 if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10413 if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10414 if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10416 for(i=0; i<nrCastlingRights; i++) {
10417 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10418 board[CASTLING][i] == toX && castlingRank[i] == toY
10419 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10422 if(gameInfo.variant == VariantSChess) { // update virginity
10423 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10424 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10425 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
10426 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
10429 if (fromX == toX && fromY == toY && killX < 0) return;
10431 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10432 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10433 if(gameInfo.variant == VariantKnightmate)
10434 king += (int) WhiteUnicorn - (int) WhiteKing;
10436 if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10437 && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) { // and tramples own
10438 board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10439 board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10440 board[EP_STATUS] = EP_NONE; // capture was fake!
10442 if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10443 board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10444 board[toY][toX] = piece;
10445 board[EP_STATUS] = EP_NONE; // capture was fake!
10447 /* Code added by Tord: */
10448 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10449 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10450 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10451 board[EP_STATUS] = EP_NONE; // capture was fake!
10452 board[fromY][fromX] = EmptySquare;
10453 board[toY][toX] = EmptySquare;
10454 if((toX > fromX) != (piece == WhiteRook)) {
10455 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10457 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10459 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10460 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10461 board[EP_STATUS] = EP_NONE;
10462 board[fromY][fromX] = EmptySquare;
10463 board[toY][toX] = EmptySquare;
10464 if((toX > fromX) != (piece == BlackRook)) {
10465 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10467 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10469 /* End of code added by Tord */
10471 } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10472 board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10473 board[toY][toX] = piece;
10474 } else if (board[fromY][fromX] == king
10475 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10476 && toY == fromY && toX > fromX+1) {
10477 for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10478 ; // castle with nearest piece
10479 board[fromY][toX-1] = board[fromY][rookX];
10480 board[fromY][rookX] = EmptySquare;
10481 board[fromY][fromX] = EmptySquare;
10482 board[toY][toX] = king;
10483 } else if (board[fromY][fromX] == king
10484 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10485 && toY == fromY && toX < fromX-1) {
10486 for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10487 ; // castle with nearest piece
10488 board[fromY][toX+1] = board[fromY][rookX];
10489 board[fromY][rookX] = EmptySquare;
10490 board[fromY][fromX] = EmptySquare;
10491 board[toY][toX] = king;
10492 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10493 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10494 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10496 /* white pawn promotion */
10497 board[toY][toX] = CharToPiece(ToUpper(promoChar));
10498 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10499 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10500 board[fromY][fromX] = EmptySquare;
10501 } else if ((fromY >= BOARD_HEIGHT>>1)
10502 && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10504 && gameInfo.variant != VariantXiangqi
10505 && gameInfo.variant != VariantBerolina
10506 && (pawn == WhitePawn)
10507 && (board[toY][toX] == EmptySquare)) {
10508 if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10509 board[fromY][fromX] = EmptySquare;
10510 board[toY][toX] = piece;
10511 captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10512 } else if ((fromY == BOARD_HEIGHT-4)
10514 && gameInfo.variant == VariantBerolina
10515 && (board[fromY][fromX] == WhitePawn)
10516 && (board[toY][toX] == EmptySquare)) {
10517 board[fromY][fromX] = EmptySquare;
10518 board[toY][toX] = WhitePawn;
10519 if(oldEP & EP_BEROLIN_A) {
10520 captured = board[fromY][fromX-1];
10521 board[fromY][fromX-1] = EmptySquare;
10522 }else{ captured = board[fromY][fromX+1];
10523 board[fromY][fromX+1] = EmptySquare;
10525 } else if (board[fromY][fromX] == king
10526 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10527 && toY == fromY && toX > fromX+1) {
10528 for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10530 board[fromY][toX-1] = board[fromY][rookX];
10531 board[fromY][rookX] = EmptySquare;
10532 board[fromY][fromX] = EmptySquare;
10533 board[toY][toX] = king;
10534 } else if (board[fromY][fromX] == king
10535 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10536 && toY == fromY && toX < fromX-1) {
10537 for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10539 board[fromY][toX+1] = board[fromY][rookX];
10540 board[fromY][rookX] = EmptySquare;
10541 board[fromY][fromX] = EmptySquare;
10542 board[toY][toX] = king;
10543 } else if (fromY == 7 && fromX == 3
10544 && board[fromY][fromX] == BlackKing
10545 && toY == 7 && toX == 5) {
10546 board[fromY][fromX] = EmptySquare;
10547 board[toY][toX] = BlackKing;
10548 board[fromY][7] = EmptySquare;
10549 board[toY][4] = BlackRook;
10550 } else if (fromY == 7 && fromX == 3
10551 && board[fromY][fromX] == BlackKing
10552 && toY == 7 && toX == 1) {
10553 board[fromY][fromX] = EmptySquare;
10554 board[toY][toX] = BlackKing;
10555 board[fromY][0] = EmptySquare;
10556 board[toY][2] = BlackRook;
10557 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10558 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10559 && toY < promoRank && promoChar
10561 /* black pawn promotion */
10562 board[toY][toX] = CharToPiece(ToLower(promoChar));
10563 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10564 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10565 board[fromY][fromX] = EmptySquare;
10566 } else if ((fromY < BOARD_HEIGHT>>1)
10567 && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10569 && gameInfo.variant != VariantXiangqi
10570 && gameInfo.variant != VariantBerolina
10571 && (pawn == BlackPawn)
10572 && (board[toY][toX] == EmptySquare)) {
10573 if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10574 board[fromY][fromX] = EmptySquare;
10575 board[toY][toX] = piece;
10576 captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10577 } else if ((fromY == 3)
10579 && gameInfo.variant == VariantBerolina
10580 && (board[fromY][fromX] == BlackPawn)
10581 && (board[toY][toX] == EmptySquare)) {
10582 board[fromY][fromX] = EmptySquare;
10583 board[toY][toX] = BlackPawn;
10584 if(oldEP & EP_BEROLIN_A) {
10585 captured = board[fromY][fromX-1];
10586 board[fromY][fromX-1] = EmptySquare;
10587 }else{ captured = board[fromY][fromX+1];
10588 board[fromY][fromX+1] = EmptySquare;
10591 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10592 board[fromY][fromX] = EmptySquare;
10593 board[toY][toX] = piece;
10597 if (gameInfo.holdingsWidth != 0) {
10599 /* !!A lot more code needs to be written to support holdings */
10600 /* [HGM] OK, so I have written it. Holdings are stored in the */
10601 /* penultimate board files, so they are automaticlly stored */
10602 /* in the game history. */
10603 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10604 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10605 /* Delete from holdings, by decreasing count */
10606 /* and erasing image if necessary */
10607 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10608 if(p < (int) BlackPawn) { /* white drop */
10609 p -= (int)WhitePawn;
10610 p = PieceToNumber((ChessSquare)p);
10611 if(p >= gameInfo.holdingsSize) p = 0;
10612 if(--board[p][BOARD_WIDTH-2] <= 0)
10613 board[p][BOARD_WIDTH-1] = EmptySquare;
10614 if((int)board[p][BOARD_WIDTH-2] < 0)
10615 board[p][BOARD_WIDTH-2] = 0;
10616 } else { /* black drop */
10617 p -= (int)BlackPawn;
10618 p = PieceToNumber((ChessSquare)p);
10619 if(p >= gameInfo.holdingsSize) p = 0;
10620 if(--board[handSize-1-p][1] <= 0)
10621 board[handSize-1-p][0] = EmptySquare;
10622 if((int)board[handSize-1-p][1] < 0)
10623 board[handSize-1-p][1] = 0;
10626 if (captured != EmptySquare && gameInfo.holdingsSize > 0
10627 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
10628 /* [HGM] holdings: Add to holdings, if holdings exist */
10629 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10630 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10631 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10633 p = (int) captured;
10634 if (p >= (int) BlackPawn) {
10635 p -= (int)BlackPawn;
10636 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10637 /* Restore shogi-promoted piece to its original first */
10638 captured = (ChessSquare) (DEMOTED(captured));
10641 p = PieceToNumber((ChessSquare)p);
10642 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10643 board[p][BOARD_WIDTH-2]++;
10644 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10646 p -= (int)WhitePawn;
10647 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10648 captured = (ChessSquare) (DEMOTED(captured));
10651 p = PieceToNumber((ChessSquare)p);
10652 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10653 board[handSize-1-p][1]++;
10654 board[handSize-1-p][0] = WHITE_TO_BLACK captured;
10657 } else if (gameInfo.variant == VariantAtomic) {
10658 if (captured != EmptySquare) {
10660 for (y = toY-1; y <= toY+1; y++) {
10661 for (x = toX-1; x <= toX+1; x++) {
10662 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10663 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10664 board[y][x] = EmptySquare;
10668 board[toY][toX] = EmptySquare;
10672 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10673 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10675 if(promoChar == '+') {
10676 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10677 board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10678 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10679 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10680 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10681 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10682 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10683 && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10684 board[toY][toX] = newPiece;
10686 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10687 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10688 // [HGM] superchess: take promotion piece out of holdings
10689 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10690 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10691 if(!--board[k][BOARD_WIDTH-2])
10692 board[k][BOARD_WIDTH-1] = EmptySquare;
10694 if(!--board[handSize-1-k][1])
10695 board[handSize-1-k][0] = EmptySquare;
10700 /* Updates forwardMostMove */
10702 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10704 int x = toX, y = toY, mask;
10705 char *s = parseList[forwardMostMove];
10706 ChessSquare p = boards[forwardMostMove][toY][toX];
10707 // forwardMostMove++; // [HGM] bare: moved downstream
10709 if(kill2X >= 0) x = kill2X, y = kill2Y; else
10710 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10711 (void) CoordsToAlgebraic(boards[forwardMostMove],
10712 PosFlags(forwardMostMove),
10713 fromY, fromX, y, x, (killX < 0)*promoChar,
10715 if(kill2X >= 0 && kill2Y >= 0)
10716 sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10717 if(killX >= 0 && killY >= 0)
10718 sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10719 toX + AAA, toY + ONE - '0', promoChar);
10721 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10722 int timeLeft; static int lastLoadFlag=0; int king, piece;
10723 piece = boards[forwardMostMove][fromY][fromX];
10724 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10725 if(gameInfo.variant == VariantKnightmate)
10726 king += (int) WhiteUnicorn - (int) WhiteKing;
10727 if(forwardMostMove == 0) {
10728 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10729 fprintf(serverMoves, "%s;", UserName());
10730 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10731 fprintf(serverMoves, "%s;", second.tidy);
10732 fprintf(serverMoves, "%s;", first.tidy);
10733 if(gameMode == MachinePlaysWhite)
10734 fprintf(serverMoves, "%s;", UserName());
10735 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10736 fprintf(serverMoves, "%s;", second.tidy);
10737 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10738 lastLoadFlag = loadFlag;
10740 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10741 // print castling suffix
10742 if( toY == fromY && piece == king ) {
10744 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10746 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10749 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10750 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10751 boards[forwardMostMove][toY][toX] == EmptySquare
10752 && fromX != toX && fromY != toY)
10753 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10754 // promotion suffix
10755 if(promoChar != NULLCHAR) {
10756 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10757 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10758 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10759 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10762 char buf[MOVE_LEN*2], *p; int len;
10763 fprintf(serverMoves, "/%d/%d",
10764 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10765 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10766 else timeLeft = blackTimeRemaining/1000;
10767 fprintf(serverMoves, "/%d", timeLeft);
10768 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10769 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10770 if(p = strchr(buf, '=')) *p = NULLCHAR;
10771 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10772 fprintf(serverMoves, "/%s", buf);
10774 fflush(serverMoves);
10777 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10778 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10781 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10782 if (commentList[forwardMostMove+1] != NULL) {
10783 free(commentList[forwardMostMove+1]);
10784 commentList[forwardMostMove+1] = NULL;
10786 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10787 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10788 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10789 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10790 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10791 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10792 adjustedClock = FALSE;
10793 gameInfo.result = GameUnfinished;
10794 if (gameInfo.resultDetails != NULL) {
10795 free(gameInfo.resultDetails);
10796 gameInfo.resultDetails = NULL;
10798 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10799 moveList[forwardMostMove - 1]);
10800 mask = (WhiteOnMove(forwardMostMove) ? 0xFF : 0xFF00);
10801 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10807 if(boards[forwardMostMove][CHECK_COUNT]) boards[forwardMostMove][CHECK_COUNT] -= mask & 0x101;
10808 if(!boards[forwardMostMove][CHECK_COUNT] || boards[forwardMostMove][CHECK_COUNT] & mask) {
10809 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+");
10814 strcat(parseList[forwardMostMove - 1], "#");
10819 /* Updates currentMove if not pausing */
10821 ShowMove (int fromX, int fromY, int toX, int toY)
10823 int instant = (gameMode == PlayFromGameFile) ?
10824 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10825 if(appData.noGUI) return;
10826 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10828 if (forwardMostMove == currentMove + 1) {
10829 AnimateMove(boards[forwardMostMove - 1],
10830 fromX, fromY, toX, toY);
10833 currentMove = forwardMostMove;
10836 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10838 if (instant) return;
10840 DisplayMove(currentMove - 1);
10841 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10842 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10843 SetHighlights(fromX, fromY, toX, toY);
10846 DrawPosition(FALSE, boards[currentMove]);
10847 DisplayBothClocks();
10848 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10852 SendEgtPath (ChessProgramState *cps)
10853 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10854 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10856 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10859 char c, *q = name+1, *r, *s;
10861 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10862 while(*p && *p != ',') *q++ = *p++;
10863 *q++ = ':'; *q = 0;
10864 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10865 strcmp(name, ",nalimov:") == 0 ) {
10866 // take nalimov path from the menu-changeable option first, if it is defined
10867 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10868 SendToProgram(buf,cps); // send egtbpath command for nalimov
10870 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10871 (s = StrStr(appData.egtFormats, name)) != NULL) {
10872 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10873 s = r = StrStr(s, ":") + 1; // beginning of path info
10874 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10875 c = *r; *r = 0; // temporarily null-terminate path info
10876 *--q = 0; // strip of trailig ':' from name
10877 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10879 SendToProgram(buf,cps); // send egtbpath command for this format
10881 if(*p == ',') p++; // read away comma to position for next format name
10886 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10888 int width = 8, height = 8, holdings = 0; // most common sizes
10889 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10890 // correct the deviations default for each variant
10891 if( v == VariantXiangqi ) width = 9, height = 10;
10892 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10893 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10894 if( v == VariantCapablanca || v == VariantCapaRandom ||
10895 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10897 if( v == VariantCourier ) width = 12;
10898 if( v == VariantSuper ) holdings = 8;
10899 if( v == VariantGreat ) width = 10, holdings = 8;
10900 if( v == VariantSChess ) holdings = 7;
10901 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10902 if( v == VariantChuChess) width = 10, height = 10;
10903 if( v == VariantChu ) width = 12, height = 12;
10904 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10905 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10906 holdingsSize >= 0 && holdingsSize != holdings;
10909 char variantError[MSG_SIZ];
10912 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10913 { // returns error message (recognizable by upper-case) if engine does not support the variant
10914 char *p, *variant = VariantName(v);
10915 static char b[MSG_SIZ];
10916 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10917 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10918 holdingsSize, variant); // cook up sized variant name
10919 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10920 if(StrStr(list, b) == NULL) {
10921 // specific sized variant not known, check if general sizing allowed
10922 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10923 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10924 boardWidth, boardHeight, holdingsSize, engine);
10927 /* [HGM] here we really should compare with the maximum supported board size */
10929 } else snprintf(b, MSG_SIZ,"%s", variant);
10930 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10931 p = StrStr(list, b);
10932 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10934 // occurs not at all in list, or only as sub-string
10935 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10936 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10937 int l = strlen(variantError);
10939 while(p != list && p[-1] != ',') p--;
10940 q = strchr(p, ',');
10941 if(q) *q = NULLCHAR;
10942 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10951 InitChessProgram (ChessProgramState *cps, int setup)
10952 /* setup needed to setup FRC opening position */
10954 char buf[MSG_SIZ], *b;
10955 if (appData.noChessProgram) return;
10956 hintRequested = FALSE;
10957 bookRequested = FALSE;
10959 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10960 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10961 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10962 if(cps->memSize) { /* [HGM] memory */
10963 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10964 SendToProgram(buf, cps);
10966 SendEgtPath(cps); /* [HGM] EGT */
10967 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10968 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10969 SendToProgram(buf, cps);
10972 setboardSpoiledMachineBlack = FALSE;
10973 SendToProgram(cps->initString, cps);
10974 if (gameInfo.variant != VariantNormal &&
10975 gameInfo.variant != VariantLoadable
10976 /* [HGM] also send variant if board size non-standard */
10977 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10979 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10980 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10984 char c, *q = cps->variants, *p = strchr(q, ',');
10985 if(p) *p = NULLCHAR;
10986 v = StringToVariant(q);
10987 DisplayError(variantError, 0);
10988 if(v != VariantUnknown && cps == &first) {
10990 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10991 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10992 ASSIGN(appData.variant, q);
10993 Reset(TRUE, FALSE);
10999 snprintf(buf, MSG_SIZ, "variant %s\n", b);
11000 SendToProgram(buf, cps);
11002 currentlyInitializedVariant = gameInfo.variant;
11004 /* [HGM] send opening position in FRC to first engine */
11006 SendToProgram("force\n", cps);
11008 /* engine is now in force mode! Set flag to wake it up after first move. */
11009 setboardSpoiledMachineBlack = 1;
11012 if (cps->sendICS) {
11013 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
11014 SendToProgram(buf, cps);
11016 cps->maybeThinking = FALSE;
11017 cps->offeredDraw = 0;
11018 if (!appData.icsActive) {
11019 SendTimeControl(cps, movesPerSession, timeControl,
11020 timeIncrement, appData.searchDepth,
11023 if (appData.showThinking
11024 // [HGM] thinking: four options require thinking output to be sent
11025 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
11027 SendToProgram("post\n", cps);
11029 SendToProgram("hard\n", cps);
11030 if (!appData.ponderNextMove) {
11031 /* Warning: "easy" is a toggle in GNU Chess, so don't send
11032 it without being sure what state we are in first. "hard"
11033 is not a toggle, so that one is OK.
11035 SendToProgram("easy\n", cps);
11037 if (cps->usePing) {
11038 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
11039 SendToProgram(buf, cps);
11041 cps->initDone = TRUE;
11042 ClearEngineOutputPane(cps == &second);
11047 ResendOptions (ChessProgramState *cps, int toEngine)
11048 { // send the stored value of the options
11050 static char buf2[MSG_SIZ*10];
11051 char buf[MSG_SIZ], *p = buf2;
11052 Option *opt = cps->option;
11054 for(i=0; i<cps->nrOptions; i++, opt++) {
11056 switch(opt->type) {
11060 if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11061 snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
11064 if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11065 snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
11068 if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
11069 snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
11077 snprintf(buf2, MSG_SIZ, "option %s\n", buf);
11078 SendToProgram(buf2, cps);
11080 if(p != buf2) *p++ = ',';
11081 strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
11090 StartChessProgram (ChessProgramState *cps)
11095 if (appData.noChessProgram) return;
11096 cps->initDone = FALSE;
11098 if (strcmp(cps->host, "localhost") == 0) {
11099 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
11100 } else if (*appData.remoteShell == NULLCHAR) {
11101 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
11103 if (*appData.remoteUser == NULLCHAR) {
11104 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
11107 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
11108 cps->host, appData.remoteUser, cps->program);
11110 err = StartChildProcess(buf, "", &cps->pr);
11114 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11115 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11116 if(cps != &first) return;
11117 appData.noChessProgram = TRUE;
11120 // DisplayFatalError(buf, err, 1);
11121 // cps->pr = NoProc;
11122 // cps->isr = NULL;
11126 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11127 if (cps->protocolVersion > 1) {
11128 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11129 if(!cps->reload) { // do not clear options when reloading because of -xreuse
11130 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11131 cps->comboCnt = 0; // and values of combo boxes
11133 SendToProgram(buf, cps);
11134 if(cps->reload) ResendOptions(cps, TRUE);
11136 SendToProgram("xboard\n", cps);
11141 TwoMachinesEventIfReady P((void))
11143 static int curMess = 0;
11144 if (first.lastPing != first.lastPong) {
11145 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11146 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11149 if (second.lastPing != second.lastPong) {
11150 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11151 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11154 DisplayMessage("", ""); curMess = 0;
11155 TwoMachinesEvent();
11159 MakeName (char *template)
11163 static char buf[MSG_SIZ];
11167 clock = time((time_t *)NULL);
11168 tm = localtime(&clock);
11170 while(*p++ = *template++) if(p[-1] == '%') {
11171 switch(*template++) {
11172 case 0: *p = 0; return buf;
11173 case 'Y': i = tm->tm_year+1900; break;
11174 case 'y': i = tm->tm_year-100; break;
11175 case 'M': i = tm->tm_mon+1; break;
11176 case 'd': i = tm->tm_mday; break;
11177 case 'h': i = tm->tm_hour; break;
11178 case 'm': i = tm->tm_min; break;
11179 case 's': i = tm->tm_sec; break;
11182 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11188 CountPlayers (char *p)
11191 while(p = strchr(p, '\n')) p++, n++; // count participants
11196 WriteTourneyFile (char *results, FILE *f)
11197 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11198 if(f == NULL) f = fopen(appData.tourneyFile, "w");
11199 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11200 // create a file with tournament description
11201 fprintf(f, "-participants {%s}\n", appData.participants);
11202 fprintf(f, "-seedBase %d\n", appData.seedBase);
11203 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11204 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11205 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11206 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11207 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11208 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11209 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11210 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11211 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11212 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11213 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11214 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11215 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11216 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11217 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11218 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11219 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11220 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11221 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11222 fprintf(f, "-smpCores %d\n", appData.smpCores);
11224 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11226 fprintf(f, "-mps %d\n", appData.movesPerSession);
11227 fprintf(f, "-tc %s\n", appData.timeControl);
11228 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11230 fprintf(f, "-results \"%s\"\n", results);
11235 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11238 Substitute (char *participants, int expunge)
11240 int i, changed, changes=0, nPlayers=0;
11241 char *p, *q, *r, buf[MSG_SIZ];
11242 if(participants == NULL) return;
11243 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11244 r = p = participants; q = appData.participants;
11245 while(*p && *p == *q) {
11246 if(*p == '\n') r = p+1, nPlayers++;
11249 if(*p) { // difference
11250 while(*p && *p++ != '\n')
11252 while(*q && *q++ != '\n')
11254 changed = nPlayers;
11255 changes = 1 + (strcmp(p, q) != 0);
11257 if(changes == 1) { // a single engine mnemonic was changed
11258 q = r; while(*q) nPlayers += (*q++ == '\n');
11259 p = buf; while(*r && (*p = *r++) != '\n') p++;
11261 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11262 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11263 if(mnemonic[i]) { // The substitute is valid
11265 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11266 flock(fileno(f), LOCK_EX);
11267 ParseArgsFromFile(f);
11268 fseek(f, 0, SEEK_SET);
11269 FREE(appData.participants); appData.participants = participants;
11270 if(expunge) { // erase results of replaced engine
11271 int len = strlen(appData.results), w, b, dummy;
11272 for(i=0; i<len; i++) {
11273 Pairing(i, nPlayers, &w, &b, &dummy);
11274 if((w == changed || b == changed) && appData.results[i] == '*') {
11275 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11280 for(i=0; i<len; i++) {
11281 Pairing(i, nPlayers, &w, &b, &dummy);
11282 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11285 WriteTourneyFile(appData.results, f);
11286 fclose(f); // release lock
11289 } else DisplayError(_("No engine with the name you gave is installed"), 0);
11291 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11292 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
11293 free(participants);
11298 CheckPlayers (char *participants)
11301 char buf[MSG_SIZ], *p;
11302 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11303 while(p = strchr(participants, '\n')) {
11305 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11307 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11309 DisplayError(buf, 0);
11313 participants = p + 1;
11319 CreateTourney (char *name)
11322 if(matchMode && strcmp(name, appData.tourneyFile)) {
11323 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11325 if(name[0] == NULLCHAR) {
11326 if(appData.participants[0])
11327 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11330 f = fopen(name, "r");
11331 if(f) { // file exists
11332 ASSIGN(appData.tourneyFile, name);
11333 ParseArgsFromFile(f); // parse it
11335 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11336 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11337 DisplayError(_("Not enough participants"), 0);
11340 if(CheckPlayers(appData.participants)) return 0;
11341 ASSIGN(appData.tourneyFile, name);
11342 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11343 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11346 appData.noChessProgram = FALSE;
11347 appData.clockMode = TRUE;
11353 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11355 char buf[2*MSG_SIZ], *p, *q;
11356 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11357 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11358 skip = !all && group[0]; // if group requested, we start in skip mode
11359 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11360 p = names; q = buf; header = 0;
11361 while(*p && *p != '\n') *q++ = *p++;
11363 if(*p == '\n') p++;
11364 if(buf[0] == '#') {
11365 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11366 depth++; // we must be entering a new group
11367 if(all) continue; // suppress printing group headers when complete list requested
11369 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11371 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11372 if(engineList[i]) free(engineList[i]);
11373 engineList[i] = strdup(buf);
11374 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11375 if(engineMnemonic[i]) free(engineMnemonic[i]);
11376 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11378 sscanf(q + 8, "%s", buf + strlen(buf));
11381 engineMnemonic[i] = strdup(buf);
11384 engineList[i] = engineMnemonic[i] = NULL;
11389 SaveEngineSettings (int n)
11391 int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11392 if(!currentEngine[n] || !currentEngine[n][0]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
11393 p = strstr(firstChessProgramNames, currentEngine[n]);
11394 if(!p) { DisplayMessage("saving failed: engine not found in list", ""); return; } // sanity check; engine could be deleted from list after loading
11395 optionSettings = ResendOptions(n ? &second : &first, FALSE);
11396 len = strlen(currentEngine[n]);
11397 q = p + len; *p = 0; // cut list into head and tail piece
11398 s = strstr(currentEngine[n], "firstOptions");
11399 if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11401 while(*r && *r != s[13]) r++;
11402 s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11403 snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11404 } else if(*optionSettings) {
11405 snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11407 ASSIGN(currentEngine[n], buf); // updated engine line
11408 len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11410 snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11411 FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11414 // following implemented as macro to avoid type limitations
11415 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11418 SwapEngines (int n)
11419 { // swap settings for first engine and other engine (so far only some selected options)
11424 SWAP(chessProgram, p)
11426 SWAP(hasOwnBookUCI, h)
11427 SWAP(protocolVersion, h)
11429 SWAP(scoreIsAbsolute, h)
11434 SWAP(engOptions, p)
11435 SWAP(engInitString, p)
11436 SWAP(computerString, p)
11438 SWAP(fenOverride, p)
11440 SWAP(accumulateTC, h)
11447 GetEngineLine (char *s, int n)
11451 extern char *icsNames;
11452 if(!s || !*s) return 0;
11453 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11454 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11455 if(!mnemonic[i]) return 0;
11456 if(n == 11) return 1; // just testing if there was a match
11457 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11458 if(n == 1) SwapEngines(n);
11459 ParseArgsFromString(buf);
11460 if(n == 1) SwapEngines(n);
11461 if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
11462 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11463 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11464 ParseArgsFromString(buf);
11470 SetPlayer (int player, char *p)
11471 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
11473 char buf[MSG_SIZ], *engineName;
11474 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11475 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11476 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11478 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11479 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11480 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11481 ParseArgsFromString(buf);
11482 } else { // no engine with this nickname is installed!
11483 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11484 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11485 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11487 DisplayError(buf, 0);
11494 char *recentEngines;
11497 RecentEngineEvent (int nr)
11500 // SwapEngines(1); // bump first to second
11501 // ReplaceEngine(&second, 1); // and load it there
11502 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11503 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11504 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11505 ReplaceEngine(&first, 0);
11506 FloatToFront(&appData.recentEngineList, command[n]);
11507 ASSIGN(currentEngine[0], command[n]);
11512 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11513 { // determine players from game number
11514 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11516 if(appData.tourneyType == 0) {
11517 roundsPerCycle = (nPlayers - 1) | 1;
11518 pairingsPerRound = nPlayers / 2;
11519 } else if(appData.tourneyType > 0) {
11520 roundsPerCycle = nPlayers - appData.tourneyType;
11521 pairingsPerRound = appData.tourneyType;
11523 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11524 gamesPerCycle = gamesPerRound * roundsPerCycle;
11525 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11526 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11527 curRound = nr / gamesPerRound; nr %= gamesPerRound;
11528 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11529 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11530 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11532 if(appData.cycleSync) *syncInterval = gamesPerCycle;
11533 if(appData.roundSync) *syncInterval = gamesPerRound;
11535 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11537 if(appData.tourneyType == 0) {
11538 if(curPairing == (nPlayers-1)/2 ) {
11539 *whitePlayer = curRound;
11540 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11542 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11543 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11544 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11545 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11547 } else if(appData.tourneyType > 1) {
11548 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11549 *whitePlayer = curRound + appData.tourneyType;
11550 } else if(appData.tourneyType > 0) {
11551 *whitePlayer = curPairing;
11552 *blackPlayer = curRound + appData.tourneyType;
11555 // take care of white/black alternation per round.
11556 // For cycles and games this is already taken care of by default, derived from matchGame!
11557 return curRound & 1;
11561 NextTourneyGame (int nr, int *swapColors)
11562 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11564 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11566 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11567 tf = fopen(appData.tourneyFile, "r");
11568 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11569 ParseArgsFromFile(tf); fclose(tf);
11570 InitTimeControls(); // TC might be altered from tourney file
11572 nPlayers = CountPlayers(appData.participants); // count participants
11573 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11574 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11577 p = q = appData.results;
11578 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11579 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11580 DisplayMessage(_("Waiting for other game(s)"),"");
11581 waitingForGame = TRUE;
11582 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11585 waitingForGame = FALSE;
11588 if(appData.tourneyType < 0) {
11589 if(nr>=0 && !pairingReceived) {
11591 if(pairing.pr == NoProc) {
11592 if(!appData.pairingEngine[0]) {
11593 DisplayFatalError(_("No pairing engine specified"), 0, 1);
11596 StartChessProgram(&pairing); // starts the pairing engine
11598 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11599 SendToProgram(buf, &pairing);
11600 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11601 SendToProgram(buf, &pairing);
11602 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11604 pairingReceived = 0; // ... so we continue here
11606 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11607 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11608 matchGame = 1; roundNr = nr / syncInterval + 1;
11611 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11613 // redefine engines, engine dir, etc.
11614 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11615 if(first.pr == NoProc) {
11616 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11617 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
11619 if(second.pr == NoProc) {
11621 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11622 SwapEngines(1); // and make that valid for second engine by swapping
11623 InitEngine(&second, 1);
11625 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
11626 UpdateLogos(FALSE); // leave display to ModeHiglight()
11632 { // performs game initialization that does not invoke engines, and then tries to start the game
11633 int res, firstWhite, swapColors = 0;
11634 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11635 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
11637 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11638 if(strcmp(buf, currentDebugFile)) { // name has changed
11639 FILE *f = fopen(buf, "w");
11640 if(f) { // if opening the new file failed, just keep using the old one
11641 ASSIGN(currentDebugFile, buf);
11645 if(appData.serverFileName) {
11646 if(serverFP) fclose(serverFP);
11647 serverFP = fopen(appData.serverFileName, "w");
11648 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11649 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11653 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11654 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11655 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11656 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11657 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11658 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11659 Reset(FALSE, first.pr != NoProc);
11660 res = LoadGameOrPosition(matchGame); // setup game
11661 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11662 if(!res) return; // abort when bad game/pos file
11663 if(appData.epd) {// in EPD mode we make sure first engine is to move
11664 firstWhite = !(forwardMostMove & 1);
11665 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11666 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11668 TwoMachinesEvent();
11672 UserAdjudicationEvent (int result)
11674 ChessMove gameResult = GameIsDrawn;
11677 gameResult = WhiteWins;
11679 else if( result < 0 ) {
11680 gameResult = BlackWins;
11683 if( gameMode == TwoMachinesPlay ) {
11684 GameEnds( gameResult, "User adjudication", GE_XBOARD );
11689 // [HGM] save: calculate checksum of game to make games easily identifiable
11691 StringCheckSum (char *s)
11694 if(s==NULL) return 0;
11695 while(*s) i = i*259 + *s++;
11703 for(i=backwardMostMove; i<forwardMostMove; i++) {
11704 sum += pvInfoList[i].depth;
11705 sum += StringCheckSum(parseList[i]);
11706 sum += StringCheckSum(commentList[i]);
11709 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11710 return sum + StringCheckSum(commentList[i]);
11711 } // end of save patch
11714 GameEnds (ChessMove result, char *resultDetails, int whosays)
11716 GameMode nextGameMode;
11718 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11720 if(endingGame) return; /* [HGM] crash: forbid recursion */
11722 if(twoBoards) { // [HGM] dual: switch back to one board
11723 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11724 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11726 if (appData.debugMode) {
11727 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11728 result, resultDetails ? resultDetails : "(null)", whosays);
11731 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11733 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11735 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11736 /* If we are playing on ICS, the server decides when the
11737 game is over, but the engine can offer to draw, claim
11741 if (appData.zippyPlay && first.initDone) {
11742 if (result == GameIsDrawn) {
11743 /* In case draw still needs to be claimed */
11744 SendToICS(ics_prefix);
11745 SendToICS("draw\n");
11746 } else if (StrCaseStr(resultDetails, "resign")) {
11747 SendToICS(ics_prefix);
11748 SendToICS("resign\n");
11752 endingGame = 0; /* [HGM] crash */
11756 /* If we're loading the game from a file, stop */
11757 if (whosays == GE_FILE) {
11758 (void) StopLoadGameTimer();
11762 /* Cancel draw offers */
11763 first.offeredDraw = second.offeredDraw = 0;
11765 /* If this is an ICS game, only ICS can really say it's done;
11766 if not, anyone can. */
11767 isIcsGame = (gameMode == IcsPlayingWhite ||
11768 gameMode == IcsPlayingBlack ||
11769 gameMode == IcsObserving ||
11770 gameMode == IcsExamining);
11772 if (!isIcsGame || whosays == GE_ICS) {
11773 /* OK -- not an ICS game, or ICS said it was done */
11775 if (!isIcsGame && !appData.noChessProgram)
11776 SetUserThinkingEnables();
11778 /* [HGM] if a machine claims the game end we verify this claim */
11779 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11780 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11782 ChessMove trueResult = (ChessMove) -1;
11784 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11785 first.twoMachinesColor[0] :
11786 second.twoMachinesColor[0] ;
11788 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11789 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11790 /* [HGM] verify: engine mate claims accepted if they were flagged */
11791 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11793 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11794 /* [HGM] verify: engine mate claims accepted if they were flagged */
11795 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11797 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11798 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11801 // now verify win claims, but not in drop games, as we don't understand those yet
11802 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11803 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11804 (result == WhiteWins && claimer == 'w' ||
11805 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11806 if (appData.debugMode) {
11807 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11808 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11810 if(result != trueResult) {
11811 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11812 result = claimer == 'w' ? BlackWins : WhiteWins;
11813 resultDetails = buf;
11816 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11817 && (forwardMostMove <= backwardMostMove ||
11818 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11819 (claimer=='b')==(forwardMostMove&1))
11821 /* [HGM] verify: draws that were not flagged are false claims */
11822 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11823 result = claimer == 'w' ? BlackWins : WhiteWins;
11824 resultDetails = buf;
11826 /* (Claiming a loss is accepted no questions asked!) */
11827 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11828 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11829 result = GameUnfinished;
11830 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11832 /* [HGM] bare: don't allow bare King to win */
11833 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11834 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11835 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11836 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11837 && result != GameIsDrawn)
11838 { int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11839 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11840 int p = (int)boards[forwardMostMove][i][j] - color;
11841 if(p >= 0 && p <= (int)WhiteKing) k++;
11842 oppoKings += (p + color == WhiteKing + BlackPawn - color);
11844 if (appData.debugMode) {
11845 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11846 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11848 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11849 result = GameIsDrawn;
11850 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11851 resultDetails = buf;
11857 if(serverMoves != NULL && !loadFlag) { char c = '=';
11858 if(result==WhiteWins) c = '+';
11859 if(result==BlackWins) c = '-';
11860 if(resultDetails != NULL)
11861 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11863 if (resultDetails != NULL) {
11864 gameInfo.result = result;
11865 gameInfo.resultDetails = StrSave(resultDetails);
11867 /* display last move only if game was not loaded from file */
11868 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11869 DisplayMove(currentMove - 1);
11871 if (forwardMostMove != 0) {
11872 if (gameMode != PlayFromGameFile && gameMode != EditGame
11873 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11875 if (*appData.saveGameFile != NULLCHAR) {
11876 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11877 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11879 SaveGameToFile(appData.saveGameFile, TRUE);
11880 } else if (appData.autoSaveGames) {
11881 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11883 if (*appData.savePositionFile != NULLCHAR) {
11884 SavePositionToFile(appData.savePositionFile);
11886 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11890 /* Tell program how game ended in case it is learning */
11891 /* [HGM] Moved this to after saving the PGN, just in case */
11892 /* engine died and we got here through time loss. In that */
11893 /* case we will get a fatal error writing the pipe, which */
11894 /* would otherwise lose us the PGN. */
11895 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11896 /* output during GameEnds should never be fatal anymore */
11897 if (gameMode == MachinePlaysWhite ||
11898 gameMode == MachinePlaysBlack ||
11899 gameMode == TwoMachinesPlay ||
11900 gameMode == IcsPlayingWhite ||
11901 gameMode == IcsPlayingBlack ||
11902 gameMode == BeginningOfGame) {
11904 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11906 if (first.pr != NoProc) {
11907 SendToProgram(buf, &first);
11909 if (second.pr != NoProc &&
11910 gameMode == TwoMachinesPlay) {
11911 SendToProgram(buf, &second);
11916 if (appData.icsActive) {
11917 if (appData.quietPlay &&
11918 (gameMode == IcsPlayingWhite ||
11919 gameMode == IcsPlayingBlack)) {
11920 SendToICS(ics_prefix);
11921 SendToICS("set shout 1\n");
11923 nextGameMode = IcsIdle;
11924 ics_user_moved = FALSE;
11925 /* clean up premove. It's ugly when the game has ended and the
11926 * premove highlights are still on the board.
11929 gotPremove = FALSE;
11930 ClearPremoveHighlights();
11931 DrawPosition(FALSE, boards[currentMove]);
11933 if (whosays == GE_ICS) {
11936 if (gameMode == IcsPlayingWhite)
11938 else if(gameMode == IcsPlayingBlack)
11939 PlayIcsLossSound();
11942 if (gameMode == IcsPlayingBlack)
11944 else if(gameMode == IcsPlayingWhite)
11945 PlayIcsLossSound();
11948 PlayIcsDrawSound();
11951 PlayIcsUnfinishedSound();
11954 if(appData.quitNext) { ExitEvent(0); return; }
11955 } else if (gameMode == EditGame ||
11956 gameMode == PlayFromGameFile ||
11957 gameMode == AnalyzeMode ||
11958 gameMode == AnalyzeFile) {
11959 nextGameMode = gameMode;
11961 nextGameMode = EndOfGame;
11966 nextGameMode = gameMode;
11969 if (appData.noChessProgram) {
11970 gameMode = nextGameMode;
11972 endingGame = 0; /* [HGM] crash */
11977 /* Put first chess program into idle state */
11978 if (first.pr != NoProc &&
11979 (gameMode == MachinePlaysWhite ||
11980 gameMode == MachinePlaysBlack ||
11981 gameMode == TwoMachinesPlay ||
11982 gameMode == IcsPlayingWhite ||
11983 gameMode == IcsPlayingBlack ||
11984 gameMode == BeginningOfGame)) {
11985 SendToProgram("force\n", &first);
11986 if (first.usePing) {
11988 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11989 SendToProgram(buf, &first);
11992 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11993 /* Kill off first chess program */
11994 if (first.isr != NULL)
11995 RemoveInputSource(first.isr);
11998 if (first.pr != NoProc) {
12000 DoSleep( appData.delayBeforeQuit );
12001 SendToProgram("quit\n", &first);
12002 DestroyChildProcess(first.pr, 4 + first.useSigterm);
12003 first.reload = TRUE;
12007 if (second.reuse) {
12008 /* Put second chess program into idle state */
12009 if (second.pr != NoProc &&
12010 gameMode == TwoMachinesPlay) {
12011 SendToProgram("force\n", &second);
12012 if (second.usePing) {
12014 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
12015 SendToProgram(buf, &second);
12018 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
12019 /* Kill off second chess program */
12020 if (second.isr != NULL)
12021 RemoveInputSource(second.isr);
12024 if (second.pr != NoProc) {
12025 DoSleep( appData.delayBeforeQuit );
12026 SendToProgram("quit\n", &second);
12027 DestroyChildProcess(second.pr, 4 + second.useSigterm);
12028 second.reload = TRUE;
12030 second.pr = NoProc;
12033 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
12034 char resChar = '=';
12038 if (first.twoMachinesColor[0] == 'w') {
12041 second.matchWins++;
12046 if (first.twoMachinesColor[0] == 'b') {
12049 second.matchWins++;
12052 case GameUnfinished:
12058 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
12059 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
12060 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
12061 ReserveGame(nextGame, resChar); // sets nextGame
12062 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
12063 else ranking = strdup("busy"); //suppress popup when aborted but not finished
12064 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
12066 if (nextGame <= appData.matchGames && !abortMatch) {
12067 gameMode = nextGameMode;
12068 matchGame = nextGame; // this will be overruled in tourney mode!
12069 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
12070 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
12071 endingGame = 0; /* [HGM] crash */
12074 gameMode = nextGameMode;
12076 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
12077 OutputKibitz(2, buf);
12078 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
12079 OutputKibitz(2, buf);
12080 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
12081 if(second.matchWins) OutputKibitz(2, buf);
12082 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
12083 OutputKibitz(2, buf);
12085 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
12086 first.tidy, second.tidy,
12087 first.matchWins, second.matchWins,
12088 appData.matchGames - (first.matchWins + second.matchWins));
12089 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
12090 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
12091 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
12092 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
12093 first.twoMachinesColor = "black\n";
12094 second.twoMachinesColor = "white\n";
12096 first.twoMachinesColor = "white\n";
12097 second.twoMachinesColor = "black\n";
12101 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
12102 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
12104 gameMode = nextGameMode;
12106 endingGame = 0; /* [HGM] crash */
12107 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
12108 if(matchMode == TRUE) { // match through command line: exit with or without popup
12110 ToNrEvent(forwardMostMove);
12111 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
12113 } else DisplayFatalError(buf, 0, 0);
12114 } else { // match through menu; just stop, with or without popup
12115 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12118 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12119 } else DisplayNote(buf);
12121 if(ranking) free(ranking);
12125 /* Assumes program was just initialized (initString sent).
12126 Leaves program in force mode. */
12128 FeedMovesToProgram (ChessProgramState *cps, int upto)
12132 if (appData.debugMode)
12133 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12134 startedFromSetupPosition ? "position and " : "",
12135 backwardMostMove, upto, cps->which);
12136 if(currentlyInitializedVariant != gameInfo.variant) {
12138 // [HGM] variantswitch: make engine aware of new variant
12139 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12140 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12141 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12142 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12143 SendToProgram(buf, cps);
12144 currentlyInitializedVariant = gameInfo.variant;
12146 SendToProgram("force\n", cps);
12147 if (startedFromSetupPosition) {
12148 SendBoard(cps, backwardMostMove);
12149 if (appData.debugMode) {
12150 fprintf(debugFP, "feedMoves\n");
12153 for (i = backwardMostMove; i < upto; i++) {
12154 SendMoveToProgram(i, cps);
12160 ResurrectChessProgram ()
12162 /* The chess program may have exited.
12163 If so, restart it and feed it all the moves made so far. */
12164 static int doInit = 0;
12166 if (appData.noChessProgram) return 1;
12168 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12169 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12170 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12171 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12173 if (first.pr != NoProc) return 1;
12174 StartChessProgram(&first);
12176 InitChessProgram(&first, FALSE);
12177 FeedMovesToProgram(&first, currentMove);
12179 if (!first.sendTime) {
12180 /* can't tell gnuchess what its clock should read,
12181 so we bow to its notion. */
12183 timeRemaining[0][currentMove] = whiteTimeRemaining;
12184 timeRemaining[1][currentMove] = blackTimeRemaining;
12187 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12188 appData.icsEngineAnalyze) && first.analysisSupport) {
12189 SendToProgram("analyze\n", &first);
12190 first.analyzing = TRUE;
12196 * Button procedures
12199 Reset (int redraw, int init)
12203 if (appData.debugMode) {
12204 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12205 redraw, init, gameMode);
12207 pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12208 deadRanks = 0; // assume entire board is used
12210 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12211 CleanupTail(); // [HGM] vari: delete any stored variations
12212 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12213 pausing = pauseExamInvalid = FALSE;
12214 startedFromSetupPosition = blackPlaysFirst = FALSE;
12216 whiteFlag = blackFlag = FALSE;
12217 userOfferedDraw = FALSE;
12218 hintRequested = bookRequested = FALSE;
12219 first.maybeThinking = FALSE;
12220 second.maybeThinking = FALSE;
12221 first.bookSuspend = FALSE; // [HGM] book
12222 second.bookSuspend = FALSE;
12223 thinkOutput[0] = NULLCHAR;
12224 lastHint[0] = NULLCHAR;
12225 ClearGameInfo(&gameInfo);
12226 gameInfo.variant = StringToVariant(appData.variant);
12227 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12228 gameInfo.variant = VariantUnknown;
12229 strncpy(engineVariant, appData.variant, MSG_SIZ);
12231 ics_user_moved = ics_clock_paused = FALSE;
12232 ics_getting_history = H_FALSE;
12234 white_holding[0] = black_holding[0] = NULLCHAR;
12235 ClearProgramStats();
12236 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12240 flipView = appData.flipView;
12241 ClearPremoveHighlights();
12242 gotPremove = FALSE;
12243 alarmSounded = FALSE;
12244 killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12246 GameEnds(EndOfFile, NULL, GE_PLAYER);
12247 if(appData.serverMovesName != NULL) {
12248 /* [HGM] prepare to make moves file for broadcasting */
12249 clock_t t = clock();
12250 if(serverMoves != NULL) fclose(serverMoves);
12251 serverMoves = fopen(appData.serverMovesName, "r");
12252 if(serverMoves != NULL) {
12253 fclose(serverMoves);
12254 /* delay 15 sec before overwriting, so all clients can see end */
12255 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12257 serverMoves = fopen(appData.serverMovesName, "w");
12261 gameMode = BeginningOfGame;
12263 if(appData.icsActive) gameInfo.variant = VariantNormal;
12264 currentMove = forwardMostMove = backwardMostMove = 0;
12265 MarkTargetSquares(1);
12266 InitPosition(redraw);
12267 for (i = 0; i < MAX_MOVES; i++) {
12268 if (commentList[i] != NULL) {
12269 free(commentList[i]);
12270 commentList[i] = NULL;
12274 timeRemaining[0][0] = whiteTimeRemaining;
12275 timeRemaining[1][0] = blackTimeRemaining;
12277 if (first.pr == NoProc) {
12278 StartChessProgram(&first);
12281 InitChessProgram(&first, startedFromSetupPosition);
12284 DisplayMessage("", "");
12285 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12286 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12287 ClearMap(); // [HGM] exclude: invalidate map
12291 AutoPlayGameLoop ()
12294 if (!AutoPlayOneMove())
12296 if (matchMode || appData.timeDelay == 0)
12298 if (appData.timeDelay < 0)
12300 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12308 ReloadGame(1); // next game
12314 int fromX, fromY, toX, toY;
12316 if (appData.debugMode) {
12317 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12320 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12323 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12324 pvInfoList[currentMove].depth = programStats.depth;
12325 pvInfoList[currentMove].score = programStats.score;
12326 pvInfoList[currentMove].time = 0;
12327 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12328 else { // append analysis of final position as comment
12330 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12331 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12333 programStats.depth = 0;
12336 if (currentMove >= forwardMostMove) {
12337 if(gameMode == AnalyzeFile) {
12338 if(appData.loadGameIndex == -1) {
12339 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12340 ScheduleDelayedEvent(AnalyzeNextGame, 10);
12342 ExitAnalyzeMode(); SendToProgram("force\n", &first);
12345 // gameMode = EndOfGame;
12346 // ModeHighlight();
12348 /* [AS] Clear current move marker at the end of a game */
12349 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12354 toX = moveList[currentMove][2] - AAA;
12355 toY = moveList[currentMove][3] - ONE;
12357 if (moveList[currentMove][1] == '@') {
12358 if (appData.highlightLastMove) {
12359 SetHighlights(-1, -1, toX, toY);
12362 fromX = moveList[currentMove][0] - AAA;
12363 fromY = moveList[currentMove][1] - ONE;
12365 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12367 if(moveList[currentMove][4] == ';') { // multi-leg
12368 killX = moveList[currentMove][5] - AAA;
12369 killY = moveList[currentMove][6] - ONE;
12371 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12372 killX = killY = -1;
12374 if (appData.highlightLastMove) {
12375 SetHighlights(fromX, fromY, toX, toY);
12378 DisplayMove(currentMove);
12379 SendMoveToProgram(currentMove++, &first);
12380 DisplayBothClocks();
12381 DrawPosition(FALSE, boards[currentMove]);
12382 // [HGM] PV info: always display, routine tests if empty
12383 DisplayComment(currentMove - 1, commentList[currentMove]);
12389 LoadGameOneMove (ChessMove readAhead)
12391 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12392 char promoChar = NULLCHAR;
12393 ChessMove moveType;
12394 char move[MSG_SIZ];
12397 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12398 gameMode != AnalyzeMode && gameMode != Training) {
12403 yyboardindex = forwardMostMove;
12404 if (readAhead != EndOfFile) {
12405 moveType = readAhead;
12407 if (gameFileFP == NULL)
12409 moveType = (ChessMove) Myylex();
12413 switch (moveType) {
12415 if (appData.debugMode)
12416 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12419 /* append the comment but don't display it */
12420 AppendComment(currentMove, p, FALSE);
12423 case WhiteCapturesEnPassant:
12424 case BlackCapturesEnPassant:
12425 case WhitePromotion:
12426 case BlackPromotion:
12427 case WhiteNonPromotion:
12428 case BlackNonPromotion:
12431 case WhiteKingSideCastle:
12432 case WhiteQueenSideCastle:
12433 case BlackKingSideCastle:
12434 case BlackQueenSideCastle:
12435 case WhiteKingSideCastleWild:
12436 case WhiteQueenSideCastleWild:
12437 case BlackKingSideCastleWild:
12438 case BlackQueenSideCastleWild:
12440 case WhiteHSideCastleFR:
12441 case WhiteASideCastleFR:
12442 case BlackHSideCastleFR:
12443 case BlackASideCastleFR:
12445 if (appData.debugMode)
12446 fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12447 fromX = currentMoveString[0] - AAA;
12448 fromY = currentMoveString[1] - ONE;
12449 toX = currentMoveString[2] - AAA;
12450 toY = currentMoveString[3] - ONE;
12451 promoChar = currentMoveString[4];
12452 if(promoChar == ';') promoChar = currentMoveString[7];
12457 if (appData.debugMode)
12458 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12459 fromX = moveType == WhiteDrop ?
12460 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12461 (int) CharToPiece(ToLower(currentMoveString[0]));
12463 toX = currentMoveString[2] - AAA;
12464 toY = currentMoveString[3] - ONE;
12470 case GameUnfinished:
12471 if (appData.debugMode)
12472 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12473 p = strchr(yy_text, '{');
12474 if (p == NULL) p = strchr(yy_text, '(');
12477 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12479 q = strchr(p, *p == '{' ? '}' : ')');
12480 if (q != NULL) *q = NULLCHAR;
12483 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12484 GameEnds(moveType, p, GE_FILE);
12486 if (cmailMsgLoaded) {
12488 flipView = WhiteOnMove(currentMove);
12489 if (moveType == GameUnfinished) flipView = !flipView;
12490 if (appData.debugMode)
12491 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12496 if (appData.debugMode)
12497 fprintf(debugFP, "Parser hit end of file\n");
12498 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12504 if (WhiteOnMove(currentMove)) {
12505 GameEnds(BlackWins, "Black mates", GE_FILE);
12507 GameEnds(WhiteWins, "White mates", GE_FILE);
12511 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12517 case MoveNumberOne:
12518 if (lastLoadGameStart == GNUChessGame) {
12519 /* GNUChessGames have numbers, but they aren't move numbers */
12520 if (appData.debugMode)
12521 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12522 yy_text, (int) moveType);
12523 return LoadGameOneMove(EndOfFile); /* tail recursion */
12525 /* else fall thru */
12530 /* Reached start of next game in file */
12531 if (appData.debugMode)
12532 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12533 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12539 if (WhiteOnMove(currentMove)) {
12540 GameEnds(BlackWins, "Black mates", GE_FILE);
12542 GameEnds(WhiteWins, "White mates", GE_FILE);
12546 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12552 case PositionDiagram: /* should not happen; ignore */
12553 case ElapsedTime: /* ignore */
12554 case NAG: /* ignore */
12555 if (appData.debugMode)
12556 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12557 yy_text, (int) moveType);
12558 return LoadGameOneMove(EndOfFile); /* tail recursion */
12561 if (appData.testLegality) {
12562 if (appData.debugMode)
12563 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12564 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12565 (forwardMostMove / 2) + 1,
12566 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12567 DisplayError(move, 0);
12570 if (appData.debugMode)
12571 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12572 yy_text, currentMoveString);
12573 if(currentMoveString[1] == '@') {
12574 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12577 fromX = currentMoveString[0] - AAA;
12578 fromY = currentMoveString[1] - ONE;
12580 toX = currentMoveString[2] - AAA;
12581 toY = currentMoveString[3] - ONE;
12582 promoChar = currentMoveString[4];
12586 case AmbiguousMove:
12587 if (appData.debugMode)
12588 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12589 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12590 (forwardMostMove / 2) + 1,
12591 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12592 DisplayError(move, 0);
12597 case ImpossibleMove:
12598 if (appData.debugMode)
12599 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12600 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12601 (forwardMostMove / 2) + 1,
12602 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12603 DisplayError(move, 0);
12609 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12610 DrawPosition(FALSE, boards[currentMove]);
12611 DisplayBothClocks();
12612 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12613 DisplayComment(currentMove - 1, commentList[currentMove]);
12615 (void) StopLoadGameTimer();
12617 cmailOldMove = forwardMostMove;
12620 /* currentMoveString is set as a side-effect of yylex */
12622 thinkOutput[0] = NULLCHAR;
12623 MakeMove(fromX, fromY, toX, toY, promoChar);
12624 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12625 currentMove = forwardMostMove;
12630 /* Load the nth game from the given file */
12632 LoadGameFromFile (char *filename, int n, char *title, int useList)
12637 if (strcmp(filename, "-") == 0) {
12641 f = fopen(filename, "rb");
12643 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12644 DisplayError(buf, errno);
12648 if (fseek(f, 0, 0) == -1) {
12649 /* f is not seekable; probably a pipe */
12652 if (useList && n == 0) {
12653 int error = GameListBuild(f);
12655 DisplayError(_("Cannot build game list"), error);
12656 } else if (!ListEmpty(&gameList) &&
12657 ((ListGame *) gameList.tailPred)->number > 1) {
12658 GameListPopUp(f, title);
12665 return LoadGame(f, n, title, FALSE);
12670 MakeRegisteredMove ()
12672 int fromX, fromY, toX, toY;
12674 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12675 switch (cmailMoveType[lastLoadGameNumber - 1]) {
12678 if (appData.debugMode)
12679 fprintf(debugFP, "Restoring %s for game %d\n",
12680 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12682 thinkOutput[0] = NULLCHAR;
12683 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12684 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12685 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12686 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12687 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12688 promoChar = cmailMove[lastLoadGameNumber - 1][4];
12689 MakeMove(fromX, fromY, toX, toY, promoChar);
12690 ShowMove(fromX, fromY, toX, toY);
12692 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12699 if (WhiteOnMove(currentMove)) {
12700 GameEnds(BlackWins, "Black mates", GE_PLAYER);
12702 GameEnds(WhiteWins, "White mates", GE_PLAYER);
12707 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12714 if (WhiteOnMove(currentMove)) {
12715 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12717 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12722 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12733 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12735 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12739 if (gameNumber > nCmailGames) {
12740 DisplayError(_("No more games in this message"), 0);
12743 if (f == lastLoadGameFP) {
12744 int offset = gameNumber - lastLoadGameNumber;
12746 cmailMsg[0] = NULLCHAR;
12747 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12748 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12749 nCmailMovesRegistered--;
12751 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12752 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12753 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12756 if (! RegisterMove()) return FALSE;
12760 retVal = LoadGame(f, gameNumber, title, useList);
12762 /* Make move registered during previous look at this game, if any */
12763 MakeRegisteredMove();
12765 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12766 commentList[currentMove]
12767 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12768 DisplayComment(currentMove - 1, commentList[currentMove]);
12774 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12776 ReloadGame (int offset)
12778 int gameNumber = lastLoadGameNumber + offset;
12779 if (lastLoadGameFP == NULL) {
12780 DisplayError(_("No game has been loaded yet"), 0);
12783 if (gameNumber <= 0) {
12784 DisplayError(_("Can't back up any further"), 0);
12787 if (cmailMsgLoaded) {
12788 return CmailLoadGame(lastLoadGameFP, gameNumber,
12789 lastLoadGameTitle, lastLoadGameUseList);
12791 return LoadGame(lastLoadGameFP, gameNumber,
12792 lastLoadGameTitle, lastLoadGameUseList);
12796 int keys[EmptySquare+1];
12799 PositionMatches (Board b1, Board b2)
12802 switch(appData.searchMode) {
12803 case 1: return CompareWithRights(b1, b2);
12805 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12806 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12810 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12811 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12812 sum += keys[b1[r][f]] - keys[b2[r][f]];
12816 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12817 sum += keys[b1[r][f]] - keys[b2[r][f]];
12829 int pieceList[256], quickBoard[256];
12830 ChessSquare pieceType[256] = { EmptySquare };
12831 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12832 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12833 int soughtTotal, turn;
12834 Boolean epOK, flipSearch;
12837 unsigned char piece, to;
12840 #define DSIZE (250000)
12842 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12843 Move *moveDatabase = initialSpace;
12844 unsigned int movePtr, dataSize = DSIZE;
12847 MakePieceList (Board board, int *counts)
12849 int r, f, n=Q_PROMO, total=0;
12850 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12851 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12852 int sq = f + (r<<4);
12853 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12854 quickBoard[sq] = ++n;
12856 pieceType[n] = board[r][f];
12857 counts[board[r][f]]++;
12858 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12859 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12863 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12868 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12870 int sq = fromX + (fromY<<4);
12871 int piece = quickBoard[sq], rook;
12872 quickBoard[sq] = 0;
12873 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12874 if(piece == pieceList[1] && fromY == toY) {
12875 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12876 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12877 moveDatabase[movePtr++].piece = Q_WCASTL;
12878 quickBoard[sq] = piece;
12879 piece = quickBoard[from]; quickBoard[from] = 0;
12880 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12881 } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12882 quickBoard[sq] = 0; // remove Rook
12883 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12884 moveDatabase[movePtr++].piece = Q_WCASTL;
12885 quickBoard[sq] = pieceList[1]; // put King
12887 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12890 if(piece == pieceList[2] && fromY == toY) {
12891 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12892 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12893 moveDatabase[movePtr++].piece = Q_BCASTL;
12894 quickBoard[sq] = piece;
12895 piece = quickBoard[from]; quickBoard[from] = 0;
12896 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12897 } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12898 quickBoard[sq] = 0; // remove Rook
12899 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12900 moveDatabase[movePtr++].piece = Q_BCASTL;
12901 quickBoard[sq] = pieceList[2]; // put King
12903 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12906 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12907 quickBoard[(fromY<<4)+toX] = 0;
12908 moveDatabase[movePtr].piece = Q_EP;
12909 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12910 moveDatabase[movePtr].to = sq;
12912 if(promoPiece != pieceType[piece]) {
12913 moveDatabase[movePtr++].piece = Q_PROMO;
12914 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12916 moveDatabase[movePtr].piece = piece;
12917 quickBoard[sq] = piece;
12922 PackGame (Board board)
12924 Move *newSpace = NULL;
12925 moveDatabase[movePtr].piece = 0; // terminate previous game
12926 if(movePtr > dataSize) {
12927 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12928 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12929 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12932 Move *p = moveDatabase, *q = newSpace;
12933 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12934 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12935 moveDatabase = newSpace;
12936 } else { // calloc failed, we must be out of memory. Too bad...
12937 dataSize = 0; // prevent calloc events for all subsequent games
12938 return 0; // and signal this one isn't cached
12942 MakePieceList(board, counts);
12947 QuickCompare (Board board, int *minCounts, int *maxCounts)
12948 { // compare according to search mode
12950 switch(appData.searchMode)
12952 case 1: // exact position match
12953 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12954 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12955 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12958 case 2: // can have extra material on empty squares
12959 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12960 if(board[r][f] == EmptySquare) continue;
12961 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12964 case 3: // material with exact Pawn structure
12965 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12966 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12967 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12968 } // fall through to material comparison
12969 case 4: // exact material
12970 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12972 case 6: // material range with given imbalance
12973 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12974 // fall through to range comparison
12975 case 5: // material range
12976 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12982 QuickScan (Board board, Move *move)
12983 { // reconstruct game,and compare all positions in it
12984 int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12986 int piece = move->piece;
12987 int to = move->to, from = pieceList[piece];
12988 if(found < 0) { // if already found just scan to game end for final piece count
12989 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12990 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12991 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12992 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12994 static int lastCounts[EmptySquare+1];
12996 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12997 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12998 } else stretch = 0;
12999 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
13000 if(found >= 0 && !appData.minPieces) return found;
13002 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
13003 if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
13004 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
13005 piece = (++move)->piece;
13006 from = pieceList[piece];
13007 counts[pieceType[piece]]--;
13008 pieceType[piece] = (ChessSquare) move->to;
13009 counts[move->to]++;
13010 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
13011 counts[pieceType[quickBoard[to]]]--;
13012 quickBoard[to] = 0; total--;
13015 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
13016 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
13017 from = pieceList[piece]; // so this must be King
13018 quickBoard[from] = 0;
13019 pieceList[piece] = to;
13020 from = pieceList[(++move)->piece]; // for FRC this has to be done here
13021 quickBoard[from] = 0; // rook
13022 quickBoard[to] = piece;
13023 to = move->to; piece = move->piece;
13027 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
13028 if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
13029 quickBoard[from] = 0;
13031 quickBoard[to] = piece;
13032 pieceList[piece] = to;
13042 flipSearch = FALSE;
13043 CopyBoard(soughtBoard, boards[currentMove]);
13044 soughtTotal = MakePieceList(soughtBoard, maxSought);
13045 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
13046 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
13047 CopyBoard(reverseBoard, boards[currentMove]);
13048 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13049 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
13050 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
13051 reverseBoard[r][f] = piece;
13053 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
13054 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
13055 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
13056 || (boards[currentMove][CASTLING][2] == NoRights ||
13057 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
13058 && (boards[currentMove][CASTLING][5] == NoRights ||
13059 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
13062 CopyBoard(flipBoard, soughtBoard);
13063 CopyBoard(rotateBoard, reverseBoard);
13064 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13065 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
13066 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
13069 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
13070 if(appData.searchMode >= 5) {
13071 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
13072 MakePieceList(soughtBoard, minSought);
13073 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
13075 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
13076 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
13079 GameInfo dummyInfo;
13080 static int creatingBook;
13083 GameContainsPosition (FILE *f, ListGame *lg)
13085 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
13086 int fromX, fromY, toX, toY;
13088 static int initDone=FALSE;
13090 // weed out games based on numerical tag comparison
13091 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
13092 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
13093 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
13094 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
13096 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
13099 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
13100 else CopyBoard(boards[scratch], initialPosition); // default start position
13103 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
13104 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
13107 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13108 fseek(f, lg->offset, 0);
13111 yyboardindex = scratch;
13112 quickFlag = plyNr+1;
13117 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13123 if(plyNr) return -1; // after we have seen moves, this is for new game
13126 case AmbiguousMove: // we cannot reconstruct the game beyond these two
13127 case ImpossibleMove:
13128 case WhiteWins: // game ends here with these four
13131 case GameUnfinished:
13135 if(appData.testLegality) return -1;
13136 case WhiteCapturesEnPassant:
13137 case BlackCapturesEnPassant:
13138 case WhitePromotion:
13139 case BlackPromotion:
13140 case WhiteNonPromotion:
13141 case BlackNonPromotion:
13144 case WhiteKingSideCastle:
13145 case WhiteQueenSideCastle:
13146 case BlackKingSideCastle:
13147 case BlackQueenSideCastle:
13148 case WhiteKingSideCastleWild:
13149 case WhiteQueenSideCastleWild:
13150 case BlackKingSideCastleWild:
13151 case BlackQueenSideCastleWild:
13152 case WhiteHSideCastleFR:
13153 case WhiteASideCastleFR:
13154 case BlackHSideCastleFR:
13155 case BlackASideCastleFR:
13156 fromX = currentMoveString[0] - AAA;
13157 fromY = currentMoveString[1] - ONE;
13158 toX = currentMoveString[2] - AAA;
13159 toY = currentMoveString[3] - ONE;
13160 promoChar = currentMoveString[4];
13164 fromX = next == WhiteDrop ?
13165 (int) CharToPiece(ToUpper(currentMoveString[0])) :
13166 (int) CharToPiece(ToLower(currentMoveString[0]));
13168 toX = currentMoveString[2] - AAA;
13169 toY = currentMoveString[3] - ONE;
13173 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13175 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13176 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13177 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13178 if(appData.findMirror) {
13179 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13180 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13185 /* Load the nth game from open file f */
13187 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13191 int gn = gameNumber;
13192 ListGame *lg = NULL;
13193 int numPGNTags = 0, i;
13195 GameMode oldGameMode;
13196 VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13197 char oldName[MSG_SIZ];
13199 safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13201 if (appData.debugMode)
13202 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13204 if (gameMode == Training )
13205 SetTrainingModeOff();
13207 oldGameMode = gameMode;
13208 if (gameMode != BeginningOfGame) {
13209 Reset(FALSE, TRUE);
13211 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13214 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13215 fclose(lastLoadGameFP);
13219 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13222 fseek(f, lg->offset, 0);
13223 GameListHighlight(gameNumber);
13224 pos = lg->position;
13228 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13229 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13231 DisplayError(_("Game number out of range"), 0);
13236 if (fseek(f, 0, 0) == -1) {
13237 if (f == lastLoadGameFP ?
13238 gameNumber == lastLoadGameNumber + 1 :
13242 DisplayError(_("Can't seek on game file"), 0);
13247 lastLoadGameFP = f;
13248 lastLoadGameNumber = gameNumber;
13249 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13250 lastLoadGameUseList = useList;
13254 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13255 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13256 lg->gameInfo.black);
13258 } else if (*title != NULLCHAR) {
13259 if (gameNumber > 1) {
13260 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13263 DisplayTitle(title);
13267 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13268 gameMode = PlayFromGameFile;
13272 currentMove = forwardMostMove = backwardMostMove = 0;
13273 CopyBoard(boards[0], initialPosition);
13277 * Skip the first gn-1 games in the file.
13278 * Also skip over anything that precedes an identifiable
13279 * start of game marker, to avoid being confused by
13280 * garbage at the start of the file. Currently
13281 * recognized start of game markers are the move number "1",
13282 * the pattern "gnuchess .* game", the pattern
13283 * "^[#;%] [^ ]* game file", and a PGN tag block.
13284 * A game that starts with one of the latter two patterns
13285 * will also have a move number 1, possibly
13286 * following a position diagram.
13287 * 5-4-02: Let's try being more lenient and allowing a game to
13288 * start with an unnumbered move. Does that break anything?
13290 cm = lastLoadGameStart = EndOfFile;
13292 yyboardindex = forwardMostMove;
13293 cm = (ChessMove) Myylex();
13296 if (cmailMsgLoaded) {
13297 nCmailGames = CMAIL_MAX_GAMES - gn;
13300 DisplayError(_("Game not found in file"), 0);
13307 lastLoadGameStart = cm;
13310 case MoveNumberOne:
13311 switch (lastLoadGameStart) {
13316 case MoveNumberOne:
13318 gn--; /* count this game */
13319 lastLoadGameStart = cm;
13328 switch (lastLoadGameStart) {
13331 case MoveNumberOne:
13333 gn--; /* count this game */
13334 lastLoadGameStart = cm;
13337 lastLoadGameStart = cm; /* game counted already */
13345 yyboardindex = forwardMostMove;
13346 cm = (ChessMove) Myylex();
13347 } while (cm == PGNTag || cm == Comment);
13354 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13355 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
13356 != CMAIL_OLD_RESULT) {
13358 cmailResult[ CMAIL_MAX_GAMES
13359 - gn - 1] = CMAIL_OLD_RESULT;
13366 /* Only a NormalMove can be at the start of a game
13367 * without a position diagram. */
13368 if (lastLoadGameStart == EndOfFile ) {
13370 lastLoadGameStart = MoveNumberOne;
13379 if (appData.debugMode)
13380 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13382 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13384 if (cm == XBoardGame) {
13385 /* Skip any header junk before position diagram and/or move 1 */
13387 yyboardindex = forwardMostMove;
13388 cm = (ChessMove) Myylex();
13390 if (cm == EndOfFile ||
13391 cm == GNUChessGame || cm == XBoardGame) {
13392 /* Empty game; pretend end-of-file and handle later */
13397 if (cm == MoveNumberOne || cm == PositionDiagram ||
13398 cm == PGNTag || cm == Comment)
13401 } else if (cm == GNUChessGame) {
13402 if (gameInfo.event != NULL) {
13403 free(gameInfo.event);
13405 gameInfo.event = StrSave(yy_text);
13408 startedFromSetupPosition = startedFromPositionFile; // [HGM]
13409 while (cm == PGNTag) {
13410 if (appData.debugMode)
13411 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13412 err = ParsePGNTag(yy_text, &gameInfo);
13413 if (!err) numPGNTags++;
13415 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13416 if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13417 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13418 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13419 InitPosition(TRUE);
13420 oldVariant = gameInfo.variant;
13421 if (appData.debugMode)
13422 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13426 if (gameInfo.fen != NULL) {
13427 Board initial_position;
13428 startedFromSetupPosition = TRUE;
13429 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13431 DisplayError(_("Bad FEN position in file"), 0);
13434 CopyBoard(boards[0], initial_position);
13435 if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13436 CopyBoard(initialPosition, initial_position);
13437 if (blackPlaysFirst) {
13438 currentMove = forwardMostMove = backwardMostMove = 1;
13439 CopyBoard(boards[1], initial_position);
13440 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13441 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13442 timeRemaining[0][1] = whiteTimeRemaining;
13443 timeRemaining[1][1] = blackTimeRemaining;
13444 if (commentList[0] != NULL) {
13445 commentList[1] = commentList[0];
13446 commentList[0] = NULL;
13449 currentMove = forwardMostMove = backwardMostMove = 0;
13451 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13453 initialRulePlies = FENrulePlies;
13454 for( i=0; i< nrCastlingRights; i++ )
13455 initialRights[i] = initial_position[CASTLING][i];
13457 yyboardindex = forwardMostMove;
13458 free(gameInfo.fen);
13459 gameInfo.fen = NULL;
13462 yyboardindex = forwardMostMove;
13463 cm = (ChessMove) Myylex();
13465 /* Handle comments interspersed among the tags */
13466 while (cm == Comment) {
13468 if (appData.debugMode)
13469 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13471 AppendComment(currentMove, p, FALSE);
13472 yyboardindex = forwardMostMove;
13473 cm = (ChessMove) Myylex();
13477 /* don't rely on existence of Event tag since if game was
13478 * pasted from clipboard the Event tag may not exist
13480 if (numPGNTags > 0){
13482 if (gameInfo.variant == VariantNormal) {
13483 VariantClass v = StringToVariant(gameInfo.event);
13484 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13485 if(v < VariantShogi) gameInfo.variant = v;
13488 if( appData.autoDisplayTags ) {
13489 tags = PGNTags(&gameInfo);
13490 TagsPopUp(tags, CmailMsg());
13495 /* Make something up, but don't display it now */
13500 if (cm == PositionDiagram) {
13503 Board initial_position;
13505 if (appData.debugMode)
13506 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13508 if (!startedFromSetupPosition) {
13510 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13511 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13522 initial_position[i][j++] = CharToPiece(*p);
13525 while (*p == ' ' || *p == '\t' ||
13526 *p == '\n' || *p == '\r') p++;
13528 if (strncmp(p, "black", strlen("black"))==0)
13529 blackPlaysFirst = TRUE;
13531 blackPlaysFirst = FALSE;
13532 startedFromSetupPosition = TRUE;
13534 CopyBoard(boards[0], initial_position);
13535 if (blackPlaysFirst) {
13536 currentMove = forwardMostMove = backwardMostMove = 1;
13537 CopyBoard(boards[1], initial_position);
13538 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13539 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13540 timeRemaining[0][1] = whiteTimeRemaining;
13541 timeRemaining[1][1] = blackTimeRemaining;
13542 if (commentList[0] != NULL) {
13543 commentList[1] = commentList[0];
13544 commentList[0] = NULL;
13547 currentMove = forwardMostMove = backwardMostMove = 0;
13550 yyboardindex = forwardMostMove;
13551 cm = (ChessMove) Myylex();
13554 if(!creatingBook) {
13555 if (first.pr == NoProc) {
13556 StartChessProgram(&first);
13558 InitChessProgram(&first, FALSE);
13559 if(gameInfo.variant == VariantUnknown && *oldName) {
13560 safeStrCpy(engineVariant, oldName, MSG_SIZ);
13561 gameInfo.variant = v;
13563 SendToProgram("force\n", &first);
13564 if (startedFromSetupPosition) {
13565 SendBoard(&first, forwardMostMove);
13566 if (appData.debugMode) {
13567 fprintf(debugFP, "Load Game\n");
13569 DisplayBothClocks();
13573 /* [HGM] server: flag to write setup moves in broadcast file as one */
13574 loadFlag = appData.suppressLoadMoves;
13576 while (cm == Comment) {
13578 if (appData.debugMode)
13579 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13581 AppendComment(currentMove, p, FALSE);
13582 yyboardindex = forwardMostMove;
13583 cm = (ChessMove) Myylex();
13586 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13587 cm == WhiteWins || cm == BlackWins ||
13588 cm == GameIsDrawn || cm == GameUnfinished) {
13589 DisplayMessage("", _("No moves in game"));
13590 if (cmailMsgLoaded) {
13591 if (appData.debugMode)
13592 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13596 DrawPosition(FALSE, boards[currentMove]);
13597 DisplayBothClocks();
13598 gameMode = EditGame;
13605 // [HGM] PV info: routine tests if comment empty
13606 if (!matchMode && (pausing || appData.timeDelay != 0)) {
13607 DisplayComment(currentMove - 1, commentList[currentMove]);
13609 if (!matchMode && appData.timeDelay != 0)
13610 DrawPosition(FALSE, boards[currentMove]);
13612 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13613 programStats.ok_to_send = 1;
13616 /* if the first token after the PGN tags is a move
13617 * and not move number 1, retrieve it from the parser
13619 if (cm != MoveNumberOne)
13620 LoadGameOneMove(cm);
13622 /* load the remaining moves from the file */
13623 while (LoadGameOneMove(EndOfFile)) {
13624 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13625 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13628 /* rewind to the start of the game */
13629 currentMove = backwardMostMove;
13631 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13633 if (oldGameMode == AnalyzeFile) {
13634 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13635 AnalyzeFileEvent();
13637 if (oldGameMode == AnalyzeMode) {
13638 AnalyzeFileEvent();
13641 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13642 long int w, b; // [HGM] adjourn: restore saved clock times
13643 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13644 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13645 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13646 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13650 if(creatingBook) return TRUE;
13651 if (!matchMode && pos > 0) {
13652 ToNrEvent(pos); // [HGM] no autoplay if selected on position
13654 if (matchMode || appData.timeDelay == 0) {
13656 } else if (appData.timeDelay > 0) {
13657 AutoPlayGameLoop();
13660 if (appData.debugMode)
13661 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13663 loadFlag = 0; /* [HGM] true game starts */
13667 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13669 ReloadPosition (int offset)
13671 int positionNumber = lastLoadPositionNumber + offset;
13672 if (lastLoadPositionFP == NULL) {
13673 DisplayError(_("No position has been loaded yet"), 0);
13676 if (positionNumber <= 0) {
13677 DisplayError(_("Can't back up any further"), 0);
13680 return LoadPosition(lastLoadPositionFP, positionNumber,
13681 lastLoadPositionTitle);
13684 /* Load the nth position from the given file */
13686 LoadPositionFromFile (char *filename, int n, char *title)
13691 if (strcmp(filename, "-") == 0) {
13692 return LoadPosition(stdin, n, "stdin");
13694 f = fopen(filename, "rb");
13696 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13697 DisplayError(buf, errno);
13700 return LoadPosition(f, n, title);
13705 /* Load the nth position from the given open file, and close it */
13707 LoadPosition (FILE *f, int positionNumber, char *title)
13709 char *p, line[MSG_SIZ];
13710 Board initial_position;
13711 int i, j, fenMode, pn;
13713 if (gameMode == Training )
13714 SetTrainingModeOff();
13716 if (gameMode != BeginningOfGame) {
13717 Reset(FALSE, TRUE);
13719 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13720 fclose(lastLoadPositionFP);
13722 if (positionNumber == 0) positionNumber = 1;
13723 lastLoadPositionFP = f;
13724 lastLoadPositionNumber = positionNumber;
13725 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13726 if (first.pr == NoProc && !appData.noChessProgram) {
13727 StartChessProgram(&first);
13728 InitChessProgram(&first, FALSE);
13730 pn = positionNumber;
13731 if (positionNumber < 0) {
13732 /* Negative position number means to seek to that byte offset */
13733 if (fseek(f, -positionNumber, 0) == -1) {
13734 DisplayError(_("Can't seek on position file"), 0);
13739 if (fseek(f, 0, 0) == -1) {
13740 if (f == lastLoadPositionFP ?
13741 positionNumber == lastLoadPositionNumber + 1 :
13742 positionNumber == 1) {
13745 DisplayError(_("Can't seek on position file"), 0);
13750 /* See if this file is FEN or old-style xboard */
13751 if (fgets(line, MSG_SIZ, f) == NULL) {
13752 DisplayError(_("Position not found in file"), 0);
13755 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13756 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13759 if (fenMode || line[0] == '#') pn--;
13761 /* skip positions before number pn */
13762 if (fgets(line, MSG_SIZ, f) == NULL) {
13764 DisplayError(_("Position not found in file"), 0);
13767 if (fenMode || line[0] == '#') pn--;
13773 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13774 DisplayError(_("Bad FEN position in file"), 0);
13777 if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13778 sscanf(p+4, "%[^;]", bestMove);
13779 } else *bestMove = NULLCHAR;
13780 if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13781 sscanf(p+4, "%[^;]", avoidMove);
13782 } else *avoidMove = NULLCHAR;
13784 (void) fgets(line, MSG_SIZ, f);
13785 (void) fgets(line, MSG_SIZ, f);
13787 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13788 (void) fgets(line, MSG_SIZ, f);
13789 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13792 initial_position[i][j++] = CharToPiece(*p);
13796 blackPlaysFirst = FALSE;
13798 (void) fgets(line, MSG_SIZ, f);
13799 if (strncmp(line, "black", strlen("black"))==0)
13800 blackPlaysFirst = TRUE;
13803 startedFromSetupPosition = TRUE;
13805 CopyBoard(boards[0], initial_position);
13806 if (blackPlaysFirst) {
13807 currentMove = forwardMostMove = backwardMostMove = 1;
13808 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13809 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13810 CopyBoard(boards[1], initial_position);
13811 DisplayMessage("", _("Black to play"));
13813 currentMove = forwardMostMove = backwardMostMove = 0;
13814 DisplayMessage("", _("White to play"));
13816 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13817 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13818 SendToProgram("force\n", &first);
13819 SendBoard(&first, forwardMostMove);
13821 if (appData.debugMode) {
13823 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13824 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13825 fprintf(debugFP, "Load Position\n");
13828 if (positionNumber > 1) {
13829 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13830 DisplayTitle(line);
13832 DisplayTitle(title);
13834 gameMode = EditGame;
13837 timeRemaining[0][1] = whiteTimeRemaining;
13838 timeRemaining[1][1] = blackTimeRemaining;
13839 DrawPosition(FALSE, boards[currentMove]);
13846 CopyPlayerNameIntoFileName (char **dest, char *src)
13848 while (*src != NULLCHAR && *src != ',') {
13853 *(*dest)++ = *src++;
13859 DefaultFileName (char *ext)
13861 static char def[MSG_SIZ];
13864 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13866 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13868 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13870 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13877 /* Save the current game to the given file */
13879 SaveGameToFile (char *filename, int append)
13883 int result, i, t,tot=0;
13885 if (strcmp(filename, "-") == 0) {
13886 return SaveGame(stdout, 0, NULL);
13888 for(i=0; i<10; i++) { // upto 10 tries
13889 f = fopen(filename, append ? "a" : "w");
13890 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13891 if(f || errno != 13) break;
13892 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13896 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13897 DisplayError(buf, errno);
13900 safeStrCpy(buf, lastMsg, MSG_SIZ);
13901 DisplayMessage(_("Waiting for access to save file"), "");
13902 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13903 DisplayMessage(_("Saving game"), "");
13904 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13905 result = SaveGame(f, 0, NULL);
13906 DisplayMessage(buf, "");
13913 SavePart (char *str)
13915 static char buf[MSG_SIZ];
13918 p = strchr(str, ' ');
13919 if (p == NULL) return str;
13920 strncpy(buf, str, p - str);
13921 buf[p - str] = NULLCHAR;
13925 #define PGN_MAX_LINE 75
13927 #define PGN_SIDE_WHITE 0
13928 #define PGN_SIDE_BLACK 1
13931 FindFirstMoveOutOfBook (int side)
13935 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13936 int index = backwardMostMove;
13937 int has_book_hit = 0;
13939 if( (index % 2) != side ) {
13943 while( index < forwardMostMove ) {
13944 /* Check to see if engine is in book */
13945 int depth = pvInfoList[index].depth;
13946 int score = pvInfoList[index].score;
13952 else if( score == 0 && depth == 63 ) {
13953 in_book = 1; /* Zappa */
13955 else if( score == 2 && depth == 99 ) {
13956 in_book = 1; /* Abrok */
13959 has_book_hit += in_book;
13975 GetOutOfBookInfo (char * buf)
13979 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13981 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13982 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13986 if( oob[0] >= 0 || oob[1] >= 0 ) {
13987 for( i=0; i<2; i++ ) {
13991 if( i > 0 && oob[0] >= 0 ) {
13992 strcat( buf, " " );
13995 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13996 sprintf( buf+strlen(buf), "%s%.2f",
13997 pvInfoList[idx].score >= 0 ? "+" : "",
13998 pvInfoList[idx].score / 100.0 );
14004 /* Save game in PGN style */
14006 SaveGamePGN2 (FILE *f)
14008 int i, offset, linelen, newblock;
14011 int movelen, numlen, blank;
14012 char move_buffer[100]; /* [AS] Buffer for move+PV info */
14014 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14016 PrintPGNTags(f, &gameInfo);
14018 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
14020 if (backwardMostMove > 0 || startedFromSetupPosition) {
14021 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
14022 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
14023 fprintf(f, "\n{--------------\n");
14024 PrintPosition(f, backwardMostMove);
14025 fprintf(f, "--------------}\n");
14029 /* [AS] Out of book annotation */
14030 if( appData.saveOutOfBookInfo ) {
14033 GetOutOfBookInfo( buf );
14035 if( buf[0] != '\0' ) {
14036 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
14043 i = backwardMostMove;
14047 while (i < forwardMostMove) {
14048 /* Print comments preceding this move */
14049 if (commentList[i] != NULL) {
14050 if (linelen > 0) fprintf(f, "\n");
14051 fprintf(f, "%s", commentList[i]);
14056 /* Format move number */
14058 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
14061 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
14063 numtext[0] = NULLCHAR;
14065 numlen = strlen(numtext);
14068 /* Print move number */
14069 blank = linelen > 0 && numlen > 0;
14070 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
14079 fprintf(f, "%s", numtext);
14083 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
14084 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
14087 blank = linelen > 0 && movelen > 0;
14088 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14097 fprintf(f, "%s", move_buffer);
14098 linelen += movelen;
14100 /* [AS] Add PV info if present */
14101 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
14102 /* [HGM] add time */
14103 char buf[MSG_SIZ]; int seconds;
14105 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
14111 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
14114 seconds = (seconds + 4)/10; // round to full seconds
14116 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14118 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14121 if(appData.cumulativeTimePGN) {
14122 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14125 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14126 pvInfoList[i].score >= 0 ? "+" : "",
14127 pvInfoList[i].score / 100.0,
14128 pvInfoList[i].depth,
14131 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14133 /* Print score/depth */
14134 blank = linelen > 0 && movelen > 0;
14135 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14144 fprintf(f, "%s", move_buffer);
14145 linelen += movelen;
14151 /* Start a new line */
14152 if (linelen > 0) fprintf(f, "\n");
14154 /* Print comments after last move */
14155 if (commentList[i] != NULL) {
14156 fprintf(f, "%s\n", commentList[i]);
14160 if (gameInfo.resultDetails != NULL &&
14161 gameInfo.resultDetails[0] != NULLCHAR) {
14162 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14163 if(gameInfo.result == GameUnfinished && appData.clockMode &&
14164 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14165 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14166 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14168 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14172 /* Save game in PGN style and close the file */
14174 SaveGamePGN (FILE *f)
14178 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14182 /* Save game in old style and close the file */
14184 SaveGameOldStyle (FILE *f)
14189 tm = time((time_t *) NULL);
14191 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14194 if (backwardMostMove > 0 || startedFromSetupPosition) {
14195 fprintf(f, "\n[--------------\n");
14196 PrintPosition(f, backwardMostMove);
14197 fprintf(f, "--------------]\n");
14202 i = backwardMostMove;
14203 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14205 while (i < forwardMostMove) {
14206 if (commentList[i] != NULL) {
14207 fprintf(f, "[%s]\n", commentList[i]);
14210 if ((i % 2) == 1) {
14211 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
14214 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
14216 if (commentList[i] != NULL) {
14220 if (i >= forwardMostMove) {
14224 fprintf(f, "%s\n", parseList[i]);
14229 if (commentList[i] != NULL) {
14230 fprintf(f, "[%s]\n", commentList[i]);
14233 /* This isn't really the old style, but it's close enough */
14234 if (gameInfo.resultDetails != NULL &&
14235 gameInfo.resultDetails[0] != NULLCHAR) {
14236 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14237 gameInfo.resultDetails);
14239 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14246 /* Save the current game to open file f and close the file */
14248 SaveGame (FILE *f, int dummy, char *dummy2)
14250 if (gameMode == EditPosition) EditPositionDone(TRUE);
14251 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14252 if (appData.oldSaveStyle)
14253 return SaveGameOldStyle(f);
14255 return SaveGamePGN(f);
14258 /* Save the current position to the given file */
14260 SavePositionToFile (char *filename)
14265 if (strcmp(filename, "-") == 0) {
14266 return SavePosition(stdout, 0, NULL);
14268 f = fopen(filename, "a");
14270 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14271 DisplayError(buf, errno);
14274 safeStrCpy(buf, lastMsg, MSG_SIZ);
14275 DisplayMessage(_("Waiting for access to save file"), "");
14276 flock(fileno(f), LOCK_EX); // [HGM] lock
14277 DisplayMessage(_("Saving position"), "");
14278 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
14279 SavePosition(f, 0, NULL);
14280 DisplayMessage(buf, "");
14286 /* Save the current position to the given open file and close the file */
14288 SavePosition (FILE *f, int dummy, char *dummy2)
14293 if (gameMode == EditPosition) EditPositionDone(TRUE);
14294 if (appData.oldSaveStyle) {
14295 tm = time((time_t *) NULL);
14297 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14299 fprintf(f, "[--------------\n");
14300 PrintPosition(f, currentMove);
14301 fprintf(f, "--------------]\n");
14303 fen = PositionToFEN(currentMove, NULL, 1);
14304 fprintf(f, "%s\n", fen);
14312 ReloadCmailMsgEvent (int unregister)
14315 static char *inFilename = NULL;
14316 static char *outFilename;
14318 struct stat inbuf, outbuf;
14321 /* Any registered moves are unregistered if unregister is set, */
14322 /* i.e. invoked by the signal handler */
14324 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14325 cmailMoveRegistered[i] = FALSE;
14326 if (cmailCommentList[i] != NULL) {
14327 free(cmailCommentList[i]);
14328 cmailCommentList[i] = NULL;
14331 nCmailMovesRegistered = 0;
14334 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14335 cmailResult[i] = CMAIL_NOT_RESULT;
14339 if (inFilename == NULL) {
14340 /* Because the filenames are static they only get malloced once */
14341 /* and they never get freed */
14342 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14343 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14345 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14346 sprintf(outFilename, "%s.out", appData.cmailGameName);
14349 status = stat(outFilename, &outbuf);
14351 cmailMailedMove = FALSE;
14353 status = stat(inFilename, &inbuf);
14354 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14357 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14358 counts the games, notes how each one terminated, etc.
14360 It would be nice to remove this kludge and instead gather all
14361 the information while building the game list. (And to keep it
14362 in the game list nodes instead of having a bunch of fixed-size
14363 parallel arrays.) Note this will require getting each game's
14364 termination from the PGN tags, as the game list builder does
14365 not process the game moves. --mann
14367 cmailMsgLoaded = TRUE;
14368 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14370 /* Load first game in the file or popup game menu */
14371 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14373 #endif /* !WIN32 */
14381 char string[MSG_SIZ];
14383 if ( cmailMailedMove
14384 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14385 return TRUE; /* Allow free viewing */
14388 /* Unregister move to ensure that we don't leave RegisterMove */
14389 /* with the move registered when the conditions for registering no */
14391 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14392 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14393 nCmailMovesRegistered --;
14395 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14397 free(cmailCommentList[lastLoadGameNumber - 1]);
14398 cmailCommentList[lastLoadGameNumber - 1] = NULL;
14402 if (cmailOldMove == -1) {
14403 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14407 if (currentMove > cmailOldMove + 1) {
14408 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14412 if (currentMove < cmailOldMove) {
14413 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14417 if (forwardMostMove > currentMove) {
14418 /* Silently truncate extra moves */
14422 if ( (currentMove == cmailOldMove + 1)
14423 || ( (currentMove == cmailOldMove)
14424 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14425 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14426 if (gameInfo.result != GameUnfinished) {
14427 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14430 if (commentList[currentMove] != NULL) {
14431 cmailCommentList[lastLoadGameNumber - 1]
14432 = StrSave(commentList[currentMove]);
14434 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14436 if (appData.debugMode)
14437 fprintf(debugFP, "Saving %s for game %d\n",
14438 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14440 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14442 f = fopen(string, "w");
14443 if (appData.oldSaveStyle) {
14444 SaveGameOldStyle(f); /* also closes the file */
14446 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14447 f = fopen(string, "w");
14448 SavePosition(f, 0, NULL); /* also closes the file */
14450 fprintf(f, "{--------------\n");
14451 PrintPosition(f, currentMove);
14452 fprintf(f, "--------------}\n\n");
14454 SaveGame(f, 0, NULL); /* also closes the file*/
14457 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14458 nCmailMovesRegistered ++;
14459 } else if (nCmailGames == 1) {
14460 DisplayError(_("You have not made a move yet"), 0);
14471 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14472 FILE *commandOutput;
14473 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14474 int nBytes = 0; /* Suppress warnings on uninitialized variables */
14480 if (! cmailMsgLoaded) {
14481 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14485 if (nCmailGames == nCmailResults) {
14486 DisplayError(_("No unfinished games"), 0);
14490 #if CMAIL_PROHIBIT_REMAIL
14491 if (cmailMailedMove) {
14492 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);
14493 DisplayError(msg, 0);
14498 if (! (cmailMailedMove || RegisterMove())) return;
14500 if ( cmailMailedMove
14501 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14502 snprintf(string, MSG_SIZ, partCommandString,
14503 appData.debugMode ? " -v" : "", appData.cmailGameName);
14504 commandOutput = popen(string, "r");
14506 if (commandOutput == NULL) {
14507 DisplayError(_("Failed to invoke cmail"), 0);
14509 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14510 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14512 if (nBuffers > 1) {
14513 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14514 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14515 nBytes = MSG_SIZ - 1;
14517 (void) memcpy(msg, buffer, nBytes);
14519 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14521 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14522 cmailMailedMove = TRUE; /* Prevent >1 moves */
14525 for (i = 0; i < nCmailGames; i ++) {
14526 if (cmailResult[i] == CMAIL_NOT_RESULT) {
14531 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14533 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14535 appData.cmailGameName,
14537 LoadGameFromFile(buffer, 1, buffer, FALSE);
14538 cmailMsgLoaded = FALSE;
14542 DisplayInformation(msg);
14543 pclose(commandOutput);
14546 if ((*cmailMsg) != '\0') {
14547 DisplayInformation(cmailMsg);
14552 #endif /* !WIN32 */
14561 int prependComma = 0;
14563 char string[MSG_SIZ]; /* Space for game-list */
14566 if (!cmailMsgLoaded) return "";
14568 if (cmailMailedMove) {
14569 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14571 /* Create a list of games left */
14572 snprintf(string, MSG_SIZ, "[");
14573 for (i = 0; i < nCmailGames; i ++) {
14574 if (! ( cmailMoveRegistered[i]
14575 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14576 if (prependComma) {
14577 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14579 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14583 strcat(string, number);
14586 strcat(string, "]");
14588 if (nCmailMovesRegistered + nCmailResults == 0) {
14589 switch (nCmailGames) {
14591 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14595 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14599 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14604 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14606 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14611 if (nCmailResults == nCmailGames) {
14612 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14614 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14619 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14631 if (gameMode == Training)
14632 SetTrainingModeOff();
14635 cmailMsgLoaded = FALSE;
14636 if (appData.icsActive) {
14637 SendToICS(ics_prefix);
14638 SendToICS("refresh\n");
14643 ExitEvent (int status)
14647 /* Give up on clean exit */
14651 /* Keep trying for clean exit */
14655 if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14656 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14658 if (telnetISR != NULL) {
14659 RemoveInputSource(telnetISR);
14661 if (icsPR != NoProc) {
14662 DestroyChildProcess(icsPR, TRUE);
14665 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14666 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14668 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14669 /* make sure this other one finishes before killing it! */
14670 if(endingGame) { int count = 0;
14671 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14672 while(endingGame && count++ < 10) DoSleep(1);
14673 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14676 /* Kill off chess programs */
14677 if (first.pr != NoProc) {
14680 DoSleep( appData.delayBeforeQuit );
14681 SendToProgram("quit\n", &first);
14682 DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14684 if (second.pr != NoProc) {
14685 DoSleep( appData.delayBeforeQuit );
14686 SendToProgram("quit\n", &second);
14687 DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14689 if (first.isr != NULL) {
14690 RemoveInputSource(first.isr);
14692 if (second.isr != NULL) {
14693 RemoveInputSource(second.isr);
14696 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14697 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14699 ShutDownFrontEnd();
14704 PauseEngine (ChessProgramState *cps)
14706 SendToProgram("pause\n", cps);
14711 UnPauseEngine (ChessProgramState *cps)
14713 SendToProgram("resume\n", cps);
14720 if (appData.debugMode)
14721 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14725 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14727 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14728 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14729 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14731 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14732 HandleMachineMove(stashedInputMove, stalledEngine);
14733 stalledEngine = NULL;
14736 if (gameMode == MachinePlaysWhite ||
14737 gameMode == TwoMachinesPlay ||
14738 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14739 if(first.pause) UnPauseEngine(&first);
14740 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14741 if(second.pause) UnPauseEngine(&second);
14742 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14745 DisplayBothClocks();
14747 if (gameMode == PlayFromGameFile) {
14748 if (appData.timeDelay >= 0)
14749 AutoPlayGameLoop();
14750 } else if (gameMode == IcsExamining && pauseExamInvalid) {
14751 Reset(FALSE, TRUE);
14752 SendToICS(ics_prefix);
14753 SendToICS("refresh\n");
14754 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14755 ForwardInner(forwardMostMove);
14757 pauseExamInvalid = FALSE;
14759 switch (gameMode) {
14763 pauseExamForwardMostMove = forwardMostMove;
14764 pauseExamInvalid = FALSE;
14767 case IcsPlayingWhite:
14768 case IcsPlayingBlack:
14772 case PlayFromGameFile:
14773 (void) StopLoadGameTimer();
14777 case BeginningOfGame:
14778 if (appData.icsActive) return;
14779 /* else fall through */
14780 case MachinePlaysWhite:
14781 case MachinePlaysBlack:
14782 case TwoMachinesPlay:
14783 if (forwardMostMove == 0)
14784 return; /* don't pause if no one has moved */
14785 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14786 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14787 if(onMove->pause) { // thinking engine can be paused
14788 PauseEngine(onMove); // do it
14789 if(onMove->other->pause) // pondering opponent can always be paused immediately
14790 PauseEngine(onMove->other);
14792 SendToProgram("easy\n", onMove->other);
14794 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14795 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14797 PauseEngine(&first);
14799 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14800 } else { // human on move, pause pondering by either method
14802 PauseEngine(&first);
14803 else if(appData.ponderNextMove)
14804 SendToProgram("easy\n", &first);
14807 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14817 EditCommentEvent ()
14819 char title[MSG_SIZ];
14821 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14822 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14824 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14825 WhiteOnMove(currentMove - 1) ? " " : ".. ",
14826 parseList[currentMove - 1]);
14829 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14836 char *tags = PGNTags(&gameInfo);
14838 EditTagsPopUp(tags, NULL);
14845 if(WaitForEngine(&second, StartSecond)) return;
14846 InitChessProgram(&second, FALSE);
14847 FeedMovesToProgram(&second, currentMove);
14849 SendToProgram("analyze\n", &second);
14850 second.analyzing = TRUE;
14857 if(second.analyzing) {
14858 SendToProgram("exit\n", &second);
14859 second.analyzing = FALSE;
14865 /* Toggle ShowThinking */
14867 ToggleShowThinking()
14869 appData.showThinking = !appData.showThinking;
14870 ShowThinkingEvent();
14874 AnalyzeModeEvent ()
14878 if (!first.analysisSupport) {
14879 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14880 DisplayError(buf, 0);
14883 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14884 if (appData.icsActive) {
14885 if (gameMode != IcsObserving) {
14886 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14887 DisplayError(buf, 0);
14889 if (appData.icsEngineAnalyze) {
14890 if (appData.debugMode)
14891 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14897 /* if enable, user wants to disable icsEngineAnalyze */
14898 if (appData.icsEngineAnalyze) {
14903 appData.icsEngineAnalyze = TRUE;
14904 if (appData.debugMode)
14905 fprintf(debugFP, "ICS engine analyze starting... \n");
14908 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14909 if (appData.noChessProgram || gameMode == AnalyzeMode)
14912 if (gameMode != AnalyzeFile) {
14913 if (!appData.icsEngineAnalyze) {
14915 if (gameMode != EditGame) return 0;
14917 if (!appData.showThinking) ToggleShowThinking();
14918 ResurrectChessProgram();
14919 SendToProgram("analyze\n", &first);
14920 first.analyzing = TRUE;
14921 /*first.maybeThinking = TRUE;*/
14922 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14923 EngineOutputPopUp();
14925 if (!appData.icsEngineAnalyze) {
14926 gameMode = AnalyzeMode;
14927 ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14933 StartAnalysisClock();
14934 GetTimeMark(&lastNodeCountTime);
14940 AnalyzeFileEvent ()
14942 if (appData.noChessProgram || gameMode == AnalyzeFile)
14945 if (!first.analysisSupport) {
14947 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14948 DisplayError(buf, 0);
14952 if (gameMode != AnalyzeMode) {
14953 keepInfo = 1; // mere annotating should not alter PGN tags
14956 if (gameMode != EditGame) return;
14957 if (!appData.showThinking) ToggleShowThinking();
14958 ResurrectChessProgram();
14959 SendToProgram("analyze\n", &first);
14960 first.analyzing = TRUE;
14961 /*first.maybeThinking = TRUE;*/
14962 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14963 EngineOutputPopUp();
14965 gameMode = AnalyzeFile;
14969 StartAnalysisClock();
14970 GetTimeMark(&lastNodeCountTime);
14972 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14973 AnalysisPeriodicEvent(1);
14977 MachineWhiteEvent ()
14980 char *bookHit = NULL;
14982 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14986 if (gameMode == PlayFromGameFile ||
14987 gameMode == TwoMachinesPlay ||
14988 gameMode == Training ||
14989 gameMode == AnalyzeMode ||
14990 gameMode == EndOfGame)
14993 if (gameMode == EditPosition)
14994 EditPositionDone(TRUE);
14996 if (!WhiteOnMove(currentMove)) {
14997 DisplayError(_("It is not White's turn"), 0);
15001 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
15004 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15005 gameMode == AnalyzeFile)
15008 ResurrectChessProgram(); /* in case it isn't running */
15009 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
15010 gameMode = MachinePlaysWhite;
15013 gameMode = MachinePlaysWhite;
15017 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15019 if (first.sendName) {
15020 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
15021 SendToProgram(buf, &first);
15023 if (first.sendTime) {
15024 if (first.useColors) {
15025 SendToProgram("black\n", &first); /*gnu kludge*/
15027 SendTimeRemaining(&first, TRUE);
15029 if (first.useColors) {
15030 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
15032 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15033 SetMachineThinkingEnables();
15034 first.maybeThinking = TRUE;
15038 if (appData.autoFlipView && !flipView) {
15039 flipView = !flipView;
15040 DrawPosition(FALSE, NULL);
15041 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
15044 if(bookHit) { // [HGM] book: simulate book reply
15045 static char bookMove[MSG_SIZ]; // a bit generous?
15047 programStats.nodes = programStats.depth = programStats.time =
15048 programStats.score = programStats.got_only_move = 0;
15049 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15051 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15052 strcat(bookMove, bookHit);
15053 savedMessage = bookMove; // args for deferred call
15054 savedState = &first;
15055 ScheduleDelayedEvent(DeferredBookMove, 1);
15060 MachineBlackEvent ()
15063 char *bookHit = NULL;
15065 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
15069 if (gameMode == PlayFromGameFile ||
15070 gameMode == TwoMachinesPlay ||
15071 gameMode == Training ||
15072 gameMode == AnalyzeMode ||
15073 gameMode == EndOfGame)
15076 if (gameMode == EditPosition)
15077 EditPositionDone(TRUE);
15079 if (WhiteOnMove(currentMove)) {
15080 DisplayError(_("It is not Black's turn"), 0);
15084 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
15087 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15088 gameMode == AnalyzeFile)
15091 ResurrectChessProgram(); /* in case it isn't running */
15092 gameMode = MachinePlaysBlack;
15096 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15098 if (first.sendName) {
15099 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
15100 SendToProgram(buf, &first);
15102 if (first.sendTime) {
15103 if (first.useColors) {
15104 SendToProgram("white\n", &first); /*gnu kludge*/
15106 SendTimeRemaining(&first, FALSE);
15108 if (first.useColors) {
15109 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
15111 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15112 SetMachineThinkingEnables();
15113 first.maybeThinking = TRUE;
15116 if (appData.autoFlipView && flipView) {
15117 flipView = !flipView;
15118 DrawPosition(FALSE, NULL);
15119 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
15121 if(bookHit) { // [HGM] book: simulate book reply
15122 static char bookMove[MSG_SIZ]; // a bit generous?
15124 programStats.nodes = programStats.depth = programStats.time =
15125 programStats.score = programStats.got_only_move = 0;
15126 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15128 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15129 strcat(bookMove, bookHit);
15130 savedMessage = bookMove; // args for deferred call
15131 savedState = &first;
15132 ScheduleDelayedEvent(DeferredBookMove, 1);
15138 DisplayTwoMachinesTitle ()
15141 if (appData.matchGames > 0) {
15142 if(appData.tourneyFile[0]) {
15143 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15144 gameInfo.white, _("vs."), gameInfo.black,
15145 nextGame+1, appData.matchGames+1,
15146 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15148 if (first.twoMachinesColor[0] == 'w') {
15149 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15150 gameInfo.white, _("vs."), gameInfo.black,
15151 first.matchWins, second.matchWins,
15152 matchGame - 1 - (first.matchWins + second.matchWins));
15154 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15155 gameInfo.white, _("vs."), gameInfo.black,
15156 second.matchWins, first.matchWins,
15157 matchGame - 1 - (first.matchWins + second.matchWins));
15160 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15166 SettingsMenuIfReady ()
15168 if (second.lastPing != second.lastPong) {
15169 DisplayMessage("", _("Waiting for second chess program"));
15170 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15174 DisplayMessage("", "");
15175 SettingsPopUp(&second);
15179 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15182 if (cps->pr == NoProc) {
15183 StartChessProgram(cps);
15184 if (cps->protocolVersion == 1) {
15186 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15188 /* kludge: allow timeout for initial "feature" command */
15189 if(retry != TwoMachinesEventIfReady) FreezeUI();
15190 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15191 DisplayMessage("", buf);
15192 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15200 TwoMachinesEvent P((void))
15202 int i, move = forwardMostMove;
15204 ChessProgramState *onmove;
15205 char *bookHit = NULL;
15206 static int stalling = 0;
15210 if (appData.noChessProgram) return;
15212 switch (gameMode) {
15213 case TwoMachinesPlay:
15215 case MachinePlaysWhite:
15216 case MachinePlaysBlack:
15217 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15218 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15222 case BeginningOfGame:
15223 case PlayFromGameFile:
15226 if (gameMode != EditGame) return;
15229 EditPositionDone(TRUE);
15240 // forwardMostMove = currentMove;
15241 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15242 startingEngine = TRUE;
15244 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15246 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15247 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15248 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15252 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15254 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15255 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15256 startingEngine = matchMode = FALSE;
15257 DisplayError("second engine does not play this", 0);
15258 gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15259 EditGameEvent(); // switch back to EditGame mode
15264 InitChessProgram(&second, FALSE); // unbalances ping of second engine
15265 SendToProgram("force\n", &second);
15267 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15271 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15272 if(appData.matchPause>10000 || appData.matchPause<10)
15273 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15274 wait = SubtractTimeMarks(&now, &pauseStart);
15275 if(wait < appData.matchPause) {
15276 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15279 // we are now committed to starting the game
15281 DisplayMessage("", "");
15283 if (startedFromSetupPosition) {
15284 SendBoard(&second, backwardMostMove);
15285 if (appData.debugMode) {
15286 fprintf(debugFP, "Two Machines\n");
15289 for (i = backwardMostMove; i < forwardMostMove; i++) {
15290 SendMoveToProgram(i, &second);
15294 gameMode = TwoMachinesPlay;
15295 pausing = startingEngine = FALSE;
15296 ModeHighlight(); // [HGM] logo: this triggers display update of logos
15298 DisplayTwoMachinesTitle();
15300 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15305 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15306 SendToProgram(first.computerString, &first);
15307 if (first.sendName) {
15308 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15309 SendToProgram(buf, &first);
15312 SendToProgram(second.computerString, &second);
15313 if (second.sendName) {
15314 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15315 SendToProgram(buf, &second);
15319 if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15321 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15322 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15324 if (onmove->sendTime) {
15325 if (onmove->useColors) {
15326 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15328 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15330 if (onmove->useColors) {
15331 SendToProgram(onmove->twoMachinesColor, onmove);
15333 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15334 // SendToProgram("go\n", onmove);
15335 onmove->maybeThinking = TRUE;
15336 SetMachineThinkingEnables();
15340 if(bookHit) { // [HGM] book: simulate book reply
15341 static char bookMove[MSG_SIZ]; // a bit generous?
15343 programStats.nodes = programStats.depth = programStats.time =
15344 programStats.score = programStats.got_only_move = 0;
15345 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15347 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15348 strcat(bookMove, bookHit);
15349 savedMessage = bookMove; // args for deferred call
15350 savedState = onmove;
15351 ScheduleDelayedEvent(DeferredBookMove, 1);
15358 if (gameMode == Training) {
15359 SetTrainingModeOff();
15360 gameMode = PlayFromGameFile;
15361 DisplayMessage("", _("Training mode off"));
15363 gameMode = Training;
15364 animateTraining = appData.animate;
15366 /* make sure we are not already at the end of the game */
15367 if (currentMove < forwardMostMove) {
15368 SetTrainingModeOn();
15369 DisplayMessage("", _("Training mode on"));
15371 gameMode = PlayFromGameFile;
15372 DisplayError(_("Already at end of game"), 0);
15381 if (!appData.icsActive) return;
15382 switch (gameMode) {
15383 case IcsPlayingWhite:
15384 case IcsPlayingBlack:
15387 case BeginningOfGame:
15395 EditPositionDone(TRUE);
15408 gameMode = IcsIdle;
15418 switch (gameMode) {
15420 SetTrainingModeOff();
15422 case MachinePlaysWhite:
15423 case MachinePlaysBlack:
15424 case BeginningOfGame:
15425 SendToProgram("force\n", &first);
15426 if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15427 if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15429 abortEngineThink = TRUE;
15430 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15431 SendToProgram(buf, &first);
15432 DisplayMessage("Aborting engine think", "");
15436 SetUserThinkingEnables();
15438 case PlayFromGameFile:
15439 (void) StopLoadGameTimer();
15440 if (gameFileFP != NULL) {
15445 EditPositionDone(TRUE);
15450 SendToProgram("force\n", &first);
15452 case TwoMachinesPlay:
15453 GameEnds(EndOfFile, NULL, GE_PLAYER);
15454 ResurrectChessProgram();
15455 SetUserThinkingEnables();
15458 ResurrectChessProgram();
15460 case IcsPlayingBlack:
15461 case IcsPlayingWhite:
15462 DisplayError(_("Warning: You are still playing a game"), 0);
15465 DisplayError(_("Warning: You are still observing a game"), 0);
15468 DisplayError(_("Warning: You are still examining a game"), 0);
15479 first.offeredDraw = second.offeredDraw = 0;
15481 if (gameMode == PlayFromGameFile) {
15482 whiteTimeRemaining = timeRemaining[0][currentMove];
15483 blackTimeRemaining = timeRemaining[1][currentMove];
15487 if (gameMode == MachinePlaysWhite ||
15488 gameMode == MachinePlaysBlack ||
15489 gameMode == TwoMachinesPlay ||
15490 gameMode == EndOfGame) {
15491 i = forwardMostMove;
15492 while (i > currentMove) {
15493 SendToProgram("undo\n", &first);
15496 if(!adjustedClock) {
15497 whiteTimeRemaining = timeRemaining[0][currentMove];
15498 blackTimeRemaining = timeRemaining[1][currentMove];
15499 DisplayBothClocks();
15501 if (whiteFlag || blackFlag) {
15502 whiteFlag = blackFlag = 0;
15507 gameMode = EditGame;
15513 EditPositionEvent ()
15516 if (gameMode == EditPosition) {
15522 if (gameMode != EditGame) return;
15524 gameMode = EditPosition;
15527 CopyBoard(rightsBoard, nullBoard);
15528 if (currentMove > 0)
15529 CopyBoard(boards[0], boards[currentMove]);
15530 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15531 rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15533 blackPlaysFirst = !WhiteOnMove(currentMove);
15535 currentMove = forwardMostMove = backwardMostMove = 0;
15536 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15538 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15544 /* [DM] icsEngineAnalyze - possible call from other functions */
15545 if (appData.icsEngineAnalyze) {
15546 appData.icsEngineAnalyze = FALSE;
15548 DisplayMessage("",_("Close ICS engine analyze..."));
15550 if (first.analysisSupport && first.analyzing) {
15551 SendToBoth("exit\n");
15552 first.analyzing = second.analyzing = FALSE;
15554 thinkOutput[0] = NULLCHAR;
15558 EditPositionDone (Boolean fakeRights)
15560 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15562 startedFromSetupPosition = TRUE;
15563 InitChessProgram(&first, FALSE);
15564 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15566 boards[0][EP_STATUS] = EP_NONE;
15567 for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15568 for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15569 if(rightsBoard[r][f]) {
15570 ChessSquare p = boards[0][r][f];
15571 if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15572 else if(p == king) boards[0][CASTLING][2] = f;
15573 else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15574 else rightsBoard[r][f] = 2; // mark for second pass
15577 for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15578 if(rightsBoard[r][f] == 2) {
15579 ChessSquare p = boards[0][r][f];
15580 if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15581 if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15585 SendToProgram("force\n", &first);
15586 if (blackPlaysFirst) {
15587 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15588 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15589 currentMove = forwardMostMove = backwardMostMove = 1;
15590 CopyBoard(boards[1], boards[0]);
15592 currentMove = forwardMostMove = backwardMostMove = 0;
15594 SendBoard(&first, forwardMostMove);
15595 if (appData.debugMode) {
15596 fprintf(debugFP, "EditPosDone\n");
15599 DisplayMessage("", "");
15600 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15601 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15602 gameMode = EditGame;
15604 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15605 ClearHighlights(); /* [AS] */
15608 /* Pause for `ms' milliseconds */
15609 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15611 TimeDelay (long ms)
15618 } while (SubtractTimeMarks(&m2, &m1) < ms);
15621 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15623 SendMultiLineToICS (char *buf)
15625 char temp[MSG_SIZ+1], *p;
15632 strncpy(temp, buf, len);
15637 if (*p == '\n' || *p == '\r')
15642 strcat(temp, "\n");
15644 SendToPlayer(temp, strlen(temp));
15648 SetWhiteToPlayEvent ()
15650 if (gameMode == EditPosition) {
15651 blackPlaysFirst = FALSE;
15652 DisplayBothClocks(); /* works because currentMove is 0 */
15653 } else if (gameMode == IcsExamining) {
15654 SendToICS(ics_prefix);
15655 SendToICS("tomove white\n");
15660 SetBlackToPlayEvent ()
15662 if (gameMode == EditPosition) {
15663 blackPlaysFirst = TRUE;
15664 currentMove = 1; /* kludge */
15665 DisplayBothClocks();
15667 } else if (gameMode == IcsExamining) {
15668 SendToICS(ics_prefix);
15669 SendToICS("tomove black\n");
15674 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15677 ChessSquare piece = boards[0][y][x];
15678 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15679 static int lastVariant;
15680 int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15682 if (gameMode != EditPosition && gameMode != IcsExamining) return;
15684 switch (selection) {
15686 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15687 MarkTargetSquares(1);
15688 CopyBoard(currentBoard, boards[0]);
15689 CopyBoard(menuBoard, initialPosition);
15690 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15691 SendToICS(ics_prefix);
15692 SendToICS("bsetup clear\n");
15693 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15694 SendToICS(ics_prefix);
15695 SendToICS("clearboard\n");
15698 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15699 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15700 for (y = 0; y < BOARD_HEIGHT; y++) {
15701 if (gameMode == IcsExamining) {
15702 if (boards[currentMove][y][x] != EmptySquare) {
15703 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15707 } else if(boards[0][y][x] != DarkSquare) {
15708 if(boards[0][y][x] != p) nonEmpty++;
15709 boards[0][y][x] = p;
15713 CopyBoard(rightsBoard, nullBoard);
15714 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15716 for(r = 0; r < BOARD_HEIGHT; r++) {
15717 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
15718 ChessSquare p = menuBoard[r][x];
15719 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15722 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15723 DisplayMessage("Clicking clock again restores position", "");
15724 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15725 if(!nonEmpty) { // asked to clear an empty board
15726 CopyBoard(boards[0], menuBoard);
15728 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15729 CopyBoard(boards[0], initialPosition);
15731 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15732 && !CompareBoards(nullBoard, erasedBoard)) {
15733 CopyBoard(boards[0], erasedBoard);
15735 CopyBoard(erasedBoard, currentBoard);
15737 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15738 rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15741 if (gameMode == EditPosition) {
15742 DrawPosition(FALSE, boards[0]);
15747 SetWhiteToPlayEvent();
15751 SetBlackToPlayEvent();
15755 if (gameMode == IcsExamining) {
15756 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15757 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15760 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15761 if(x == BOARD_LEFT-2) {
15762 if(y < handSize-1-gameInfo.holdingsSize) break;
15763 boards[0][y][1] = 0;
15765 if(x == BOARD_RGHT+1) {
15766 if(y >= gameInfo.holdingsSize) break;
15767 boards[0][y][BOARD_WIDTH-2] = 0;
15770 boards[0][y][x] = EmptySquare;
15771 DrawPosition(FALSE, boards[0]);
15776 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15777 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
15778 selection = (ChessSquare) (PROMOTED(piece));
15779 } else if(piece == EmptySquare) selection = WhiteSilver;
15780 else selection = (ChessSquare)((int)piece - 1);
15784 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15785 piece > (int)BlackMan && piece <= (int)BlackKing ) {
15786 selection = (ChessSquare) (DEMOTED(piece));
15787 } else if(piece == EmptySquare) selection = BlackSilver;
15788 else selection = (ChessSquare)((int)piece + 1);
15793 if(gameInfo.variant == VariantShatranj ||
15794 gameInfo.variant == VariantXiangqi ||
15795 gameInfo.variant == VariantCourier ||
15796 gameInfo.variant == VariantASEAN ||
15797 gameInfo.variant == VariantMakruk )
15798 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15804 if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15805 if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15811 if(gameInfo.variant == VariantXiangqi)
15812 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15813 if(gameInfo.variant == VariantKnightmate)
15814 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15815 if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15818 if (gameMode == IcsExamining) {
15819 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15820 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15821 PieceToChar(selection), AAA + x, ONE + y);
15824 rightsBoard[y][x] = hasRights;
15825 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15827 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15828 n = PieceToNumber(selection - BlackPawn);
15829 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15830 boards[0][handSize-1-n][0] = selection;
15831 boards[0][handSize-1-n][1]++;
15833 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15834 n = PieceToNumber(selection);
15835 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15836 boards[0][n][BOARD_WIDTH-1] = selection;
15837 boards[0][n][BOARD_WIDTH-2]++;
15840 boards[0][y][x] = selection;
15841 DrawPosition(TRUE, boards[0]);
15843 fromX = fromY = -1;
15851 DropMenuEvent (ChessSquare selection, int x, int y)
15853 ChessMove moveType;
15855 switch (gameMode) {
15856 case IcsPlayingWhite:
15857 case MachinePlaysBlack:
15858 if (!WhiteOnMove(currentMove)) {
15859 DisplayMoveError(_("It is Black's turn"));
15862 moveType = WhiteDrop;
15864 case IcsPlayingBlack:
15865 case MachinePlaysWhite:
15866 if (WhiteOnMove(currentMove)) {
15867 DisplayMoveError(_("It is White's turn"));
15870 moveType = BlackDrop;
15873 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15879 if (moveType == BlackDrop && selection < BlackPawn) {
15880 selection = (ChessSquare) ((int) selection
15881 + (int) BlackPawn - (int) WhitePawn);
15883 if (boards[currentMove][y][x] != EmptySquare) {
15884 DisplayMoveError(_("That square is occupied"));
15888 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15894 /* Accept a pending offer of any kind from opponent */
15896 if (appData.icsActive) {
15897 SendToICS(ics_prefix);
15898 SendToICS("accept\n");
15899 } else if (cmailMsgLoaded) {
15900 if (currentMove == cmailOldMove &&
15901 commentList[cmailOldMove] != NULL &&
15902 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15903 "Black offers a draw" : "White offers a draw")) {
15905 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15906 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15908 DisplayError(_("There is no pending offer on this move"), 0);
15909 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15912 /* Not used for offers from chess program */
15919 /* Decline a pending offer of any kind from opponent */
15921 if (appData.icsActive) {
15922 SendToICS(ics_prefix);
15923 SendToICS("decline\n");
15924 } else if (cmailMsgLoaded) {
15925 if (currentMove == cmailOldMove &&
15926 commentList[cmailOldMove] != NULL &&
15927 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15928 "Black offers a draw" : "White offers a draw")) {
15930 AppendComment(cmailOldMove, "Draw declined", TRUE);
15931 DisplayComment(cmailOldMove - 1, "Draw declined");
15934 DisplayError(_("There is no pending offer on this move"), 0);
15937 /* Not used for offers from chess program */
15944 /* Issue ICS rematch command */
15945 if (appData.icsActive) {
15946 SendToICS(ics_prefix);
15947 SendToICS("rematch\n");
15954 /* Call your opponent's flag (claim a win on time) */
15955 if (appData.icsActive) {
15956 SendToICS(ics_prefix);
15957 SendToICS("flag\n");
15959 switch (gameMode) {
15962 case MachinePlaysWhite:
15965 GameEnds(GameIsDrawn, "Both players ran out of time",
15968 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15970 DisplayError(_("Your opponent is not out of time"), 0);
15973 case MachinePlaysBlack:
15976 GameEnds(GameIsDrawn, "Both players ran out of time",
15979 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15981 DisplayError(_("Your opponent is not out of time"), 0);
15989 ClockClick (int which)
15990 { // [HGM] code moved to back-end from winboard.c
15991 if(which) { // black clock
15992 if (gameMode == EditPosition || gameMode == IcsExamining) {
15993 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15994 SetBlackToPlayEvent();
15995 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15996 gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15997 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15998 } else if (shiftKey) {
15999 AdjustClock(which, -1);
16000 } else if (gameMode == IcsPlayingWhite ||
16001 gameMode == MachinePlaysBlack) {
16004 } else { // white clock
16005 if (gameMode == EditPosition || gameMode == IcsExamining) {
16006 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
16007 SetWhiteToPlayEvent();
16008 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
16009 gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
16010 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
16011 } else if (shiftKey) {
16012 AdjustClock(which, -1);
16013 } else if (gameMode == IcsPlayingBlack ||
16014 gameMode == MachinePlaysWhite) {
16023 /* Offer draw or accept pending draw offer from opponent */
16025 if (appData.icsActive) {
16026 /* Note: tournament rules require draw offers to be
16027 made after you make your move but before you punch
16028 your clock. Currently ICS doesn't let you do that;
16029 instead, you immediately punch your clock after making
16030 a move, but you can offer a draw at any time. */
16032 SendToICS(ics_prefix);
16033 SendToICS("draw\n");
16034 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
16035 } else if (cmailMsgLoaded) {
16036 if (currentMove == cmailOldMove &&
16037 commentList[cmailOldMove] != NULL &&
16038 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
16039 "Black offers a draw" : "White offers a draw")) {
16040 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
16041 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
16042 } else if (currentMove == cmailOldMove + 1) {
16043 char *offer = WhiteOnMove(cmailOldMove) ?
16044 "White offers a draw" : "Black offers a draw";
16045 AppendComment(currentMove, offer, TRUE);
16046 DisplayComment(currentMove - 1, offer);
16047 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
16049 DisplayError(_("You must make your move before offering a draw"), 0);
16050 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
16052 } else if (first.offeredDraw) {
16053 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
16055 if (first.sendDrawOffers) {
16056 SendToProgram("draw\n", &first);
16057 userOfferedDraw = TRUE;
16065 /* Offer Adjourn or accept pending Adjourn offer from opponent */
16067 if (appData.icsActive) {
16068 SendToICS(ics_prefix);
16069 SendToICS("adjourn\n");
16071 /* Currently GNU Chess doesn't offer or accept Adjourns */
16079 /* Offer Abort or accept pending Abort offer from opponent */
16081 if (appData.icsActive) {
16082 SendToICS(ics_prefix);
16083 SendToICS("abort\n");
16085 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
16092 /* Resign. You can do this even if it's not your turn. */
16094 if (appData.icsActive) {
16095 SendToICS(ics_prefix);
16096 SendToICS("resign\n");
16098 switch (gameMode) {
16099 case MachinePlaysWhite:
16100 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16102 case MachinePlaysBlack:
16103 GameEnds(BlackWins, "White resigns", GE_PLAYER);
16106 if (cmailMsgLoaded) {
16108 if (WhiteOnMove(cmailOldMove)) {
16109 GameEnds(BlackWins, "White resigns", GE_PLAYER);
16111 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16113 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
16124 StopObservingEvent ()
16126 /* Stop observing current games */
16127 SendToICS(ics_prefix);
16128 SendToICS("unobserve\n");
16132 StopExaminingEvent ()
16134 /* Stop observing current game */
16135 SendToICS(ics_prefix);
16136 SendToICS("unexamine\n");
16140 ForwardInner (int target)
16142 int limit; int oldSeekGraphUp = seekGraphUp;
16144 if (appData.debugMode)
16145 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16146 target, currentMove, forwardMostMove);
16148 if (gameMode == EditPosition)
16151 seekGraphUp = FALSE;
16152 MarkTargetSquares(1);
16153 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16155 if (gameMode == PlayFromGameFile && !pausing)
16158 if (gameMode == IcsExamining && pausing)
16159 limit = pauseExamForwardMostMove;
16161 limit = forwardMostMove;
16163 if (target > limit) target = limit;
16165 if (target > 0 && moveList[target - 1][0]) {
16166 int fromX, fromY, toX, toY;
16167 toX = moveList[target - 1][2] - AAA;
16168 toY = moveList[target - 1][3] - ONE;
16169 if (moveList[target - 1][1] == '@') {
16170 if (appData.highlightLastMove) {
16171 SetHighlights(-1, -1, toX, toY);
16174 fromX = moveList[target - 1][0] - AAA;
16175 fromY = moveList[target - 1][1] - ONE;
16176 if (target == currentMove + 1) {
16177 if(moveList[target - 1][4] == ';') { // multi-leg
16178 killX = moveList[target - 1][5] - AAA;
16179 killY = moveList[target - 1][6] - ONE;
16181 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16182 killX = killY = -1;
16184 if (appData.highlightLastMove) {
16185 SetHighlights(fromX, fromY, toX, toY);
16189 if (gameMode == EditGame || gameMode == AnalyzeMode ||
16190 gameMode == Training || gameMode == PlayFromGameFile ||
16191 gameMode == AnalyzeFile) {
16192 while (currentMove < target) {
16193 if(second.analyzing) SendMoveToProgram(currentMove, &second);
16194 SendMoveToProgram(currentMove++, &first);
16197 currentMove = target;
16200 if (gameMode == EditGame || gameMode == EndOfGame) {
16201 whiteTimeRemaining = timeRemaining[0][currentMove];
16202 blackTimeRemaining = timeRemaining[1][currentMove];
16204 DisplayBothClocks();
16205 DisplayMove(currentMove - 1);
16206 DrawPosition(oldSeekGraphUp, boards[currentMove]);
16207 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16208 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16209 DisplayComment(currentMove - 1, commentList[currentMove]);
16211 ClearMap(); // [HGM] exclude: invalidate map
16218 if (gameMode == IcsExamining && !pausing) {
16219 SendToICS(ics_prefix);
16220 SendToICS("forward\n");
16222 ForwardInner(currentMove + 1);
16229 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16230 /* to optimze, we temporarily turn off analysis mode while we feed
16231 * the remaining moves to the engine. Otherwise we get analysis output
16234 if (first.analysisSupport) {
16235 SendToProgram("exit\nforce\n", &first);
16236 first.analyzing = FALSE;
16240 if (gameMode == IcsExamining && !pausing) {
16241 SendToICS(ics_prefix);
16242 SendToICS("forward 999999\n");
16244 ForwardInner(forwardMostMove);
16247 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16248 /* we have fed all the moves, so reactivate analysis mode */
16249 SendToProgram("analyze\n", &first);
16250 first.analyzing = TRUE;
16251 /*first.maybeThinking = TRUE;*/
16252 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16257 BackwardInner (int target)
16259 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16261 if (appData.debugMode)
16262 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16263 target, currentMove, forwardMostMove);
16265 if (gameMode == EditPosition) return;
16266 seekGraphUp = FALSE;
16267 MarkTargetSquares(1);
16268 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16269 if (currentMove <= backwardMostMove) {
16271 DrawPosition(full_redraw, boards[currentMove]);
16274 if (gameMode == PlayFromGameFile && !pausing)
16277 if (moveList[target][0]) {
16278 int fromX, fromY, toX, toY;
16279 toX = moveList[target][2] - AAA;
16280 toY = moveList[target][3] - ONE;
16281 if (moveList[target][1] == '@') {
16282 if (appData.highlightLastMove) {
16283 SetHighlights(-1, -1, toX, toY);
16286 fromX = moveList[target][0] - AAA;
16287 fromY = moveList[target][1] - ONE;
16288 if (target == currentMove - 1) {
16289 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16291 if (appData.highlightLastMove) {
16292 SetHighlights(fromX, fromY, toX, toY);
16296 if (gameMode == EditGame || gameMode==AnalyzeMode ||
16297 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16298 while (currentMove > target) {
16299 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16300 // null move cannot be undone. Reload program with move history before it.
16302 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16303 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16305 SendBoard(&first, i);
16306 if(second.analyzing) SendBoard(&second, i);
16307 for(currentMove=i; currentMove<target; currentMove++) {
16308 SendMoveToProgram(currentMove, &first);
16309 if(second.analyzing) SendMoveToProgram(currentMove, &second);
16313 SendToBoth("undo\n");
16317 currentMove = target;
16320 if (gameMode == EditGame || gameMode == EndOfGame) {
16321 whiteTimeRemaining = timeRemaining[0][currentMove];
16322 blackTimeRemaining = timeRemaining[1][currentMove];
16324 DisplayBothClocks();
16325 DisplayMove(currentMove - 1);
16326 DrawPosition(full_redraw, boards[currentMove]);
16327 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16328 // [HGM] PV info: routine tests if comment empty
16329 DisplayComment(currentMove - 1, commentList[currentMove]);
16330 ClearMap(); // [HGM] exclude: invalidate map
16336 if (gameMode == IcsExamining && !pausing) {
16337 SendToICS(ics_prefix);
16338 SendToICS("backward\n");
16340 BackwardInner(currentMove - 1);
16347 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16348 /* to optimize, we temporarily turn off analysis mode while we undo
16349 * all the moves. Otherwise we get analysis output after each undo.
16351 if (first.analysisSupport) {
16352 SendToProgram("exit\nforce\n", &first);
16353 first.analyzing = FALSE;
16357 if (gameMode == IcsExamining && !pausing) {
16358 SendToICS(ics_prefix);
16359 SendToICS("backward 999999\n");
16361 BackwardInner(backwardMostMove);
16364 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16365 /* we have fed all the moves, so reactivate analysis mode */
16366 SendToProgram("analyze\n", &first);
16367 first.analyzing = TRUE;
16368 /*first.maybeThinking = TRUE;*/
16369 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16376 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16377 if (to >= forwardMostMove) to = forwardMostMove;
16378 if (to <= backwardMostMove) to = backwardMostMove;
16379 if (to < currentMove) {
16387 RevertEvent (Boolean annotate)
16389 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16392 if (gameMode != IcsExamining) {
16393 DisplayError(_("You are not examining a game"), 0);
16397 DisplayError(_("You can't revert while pausing"), 0);
16400 SendToICS(ics_prefix);
16401 SendToICS("revert\n");
16405 RetractMoveEvent ()
16407 switch (gameMode) {
16408 case MachinePlaysWhite:
16409 case MachinePlaysBlack:
16410 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16411 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16414 if (forwardMostMove < 2) return;
16415 currentMove = forwardMostMove = forwardMostMove - 2;
16416 whiteTimeRemaining = timeRemaining[0][currentMove];
16417 blackTimeRemaining = timeRemaining[1][currentMove];
16418 DisplayBothClocks();
16419 DisplayMove(currentMove - 1);
16420 ClearHighlights();/*!! could figure this out*/
16421 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16422 SendToProgram("remove\n", &first);
16423 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16426 case BeginningOfGame:
16430 case IcsPlayingWhite:
16431 case IcsPlayingBlack:
16432 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16433 SendToICS(ics_prefix);
16434 SendToICS("takeback 2\n");
16436 SendToICS(ics_prefix);
16437 SendToICS("takeback 1\n");
16446 ChessProgramState *cps;
16448 switch (gameMode) {
16449 case MachinePlaysWhite:
16450 if (!WhiteOnMove(forwardMostMove)) {
16451 DisplayError(_("It is your turn"), 0);
16456 case MachinePlaysBlack:
16457 if (WhiteOnMove(forwardMostMove)) {
16458 DisplayError(_("It is your turn"), 0);
16463 case TwoMachinesPlay:
16464 if (WhiteOnMove(forwardMostMove) ==
16465 (first.twoMachinesColor[0] == 'w')) {
16471 case BeginningOfGame:
16475 SendToProgram("?\n", cps);
16479 TruncateGameEvent ()
16482 if (gameMode != EditGame) return;
16489 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16490 if (forwardMostMove > currentMove) {
16491 if (gameInfo.resultDetails != NULL) {
16492 free(gameInfo.resultDetails);
16493 gameInfo.resultDetails = NULL;
16494 gameInfo.result = GameUnfinished;
16496 forwardMostMove = currentMove;
16497 HistorySet(parseList, backwardMostMove, forwardMostMove,
16505 if (appData.noChessProgram) return;
16506 switch (gameMode) {
16507 case MachinePlaysWhite:
16508 if (WhiteOnMove(forwardMostMove)) {
16509 DisplayError(_("Wait until your turn."), 0);
16513 case BeginningOfGame:
16514 case MachinePlaysBlack:
16515 if (!WhiteOnMove(forwardMostMove)) {
16516 DisplayError(_("Wait until your turn."), 0);
16521 DisplayError(_("No hint available"), 0);
16524 SendToProgram("hint\n", &first);
16525 hintRequested = TRUE;
16529 SaveSelected (FILE *g, int dummy, char *dummy2)
16531 ListGame * lg = (ListGame *) gameList.head;
16535 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16536 DisplayError(_("Game list not loaded or empty"), 0);
16540 creatingBook = TRUE; // suppresses stuff during load game
16542 /* Get list size */
16543 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16544 if(lg->position >= 0) { // selected?
16545 LoadGame(f, nItem, "", TRUE);
16546 SaveGamePGN2(g); // leaves g open
16549 lg = (ListGame *) lg->node.succ;
16553 creatingBook = FALSE;
16561 ListGame * lg = (ListGame *) gameList.head;
16564 static int secondTime = FALSE;
16566 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16567 DisplayError(_("Game list not loaded or empty"), 0);
16571 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16574 DisplayNote(_("Book file exists! Try again for overwrite."));
16578 creatingBook = TRUE;
16579 secondTime = FALSE;
16581 /* Get list size */
16582 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16583 if(lg->position >= 0) {
16584 LoadGame(f, nItem, "", TRUE);
16585 AddGameToBook(TRUE);
16588 lg = (ListGame *) lg->node.succ;
16591 creatingBook = FALSE;
16598 if (appData.noChessProgram) return;
16599 switch (gameMode) {
16600 case MachinePlaysWhite:
16601 if (WhiteOnMove(forwardMostMove)) {
16602 DisplayError(_("Wait until your turn."), 0);
16606 case BeginningOfGame:
16607 case MachinePlaysBlack:
16608 if (!WhiteOnMove(forwardMostMove)) {
16609 DisplayError(_("Wait until your turn."), 0);
16614 EditPositionDone(TRUE);
16616 case TwoMachinesPlay:
16621 SendToProgram("bk\n", &first);
16622 bookOutput[0] = NULLCHAR;
16623 bookRequested = TRUE;
16629 char *tags = PGNTags(&gameInfo);
16630 TagsPopUp(tags, CmailMsg());
16634 /* end button procedures */
16637 PrintPosition (FILE *fp, int move)
16641 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16642 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16643 char c = PieceToChar(boards[move][i][j]);
16644 fputc(c == '?' ? '.' : c, fp);
16645 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16648 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16649 fprintf(fp, "white to play\n");
16651 fprintf(fp, "black to play\n");
16655 PrintOpponents (FILE *fp)
16657 if (gameInfo.white != NULL) {
16658 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16664 /* Find last component of program's own name, using some heuristics */
16666 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16669 int local = (strcmp(host, "localhost") == 0);
16670 while (!local && (p = strchr(prog, ';')) != NULL) {
16672 while (*p == ' ') p++;
16675 if (*prog == '"' || *prog == '\'') {
16676 q = strchr(prog + 1, *prog);
16678 q = strchr(prog, ' ');
16680 if (q == NULL) q = prog + strlen(prog);
16682 while (p >= prog && *p != '/' && *p != '\\') p--;
16684 if(p == prog && *p == '"') p++;
16686 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16687 memcpy(buf, p, q - p);
16688 buf[q - p] = NULLCHAR;
16696 TimeControlTagValue ()
16699 if (!appData.clockMode) {
16700 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16701 } else if (movesPerSession > 0) {
16702 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16703 } else if (timeIncrement == 0) {
16704 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16706 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16708 return StrSave(buf);
16714 /* This routine is used only for certain modes */
16715 VariantClass v = gameInfo.variant;
16716 ChessMove r = GameUnfinished;
16719 if(keepInfo) return;
16721 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16722 r = gameInfo.result;
16723 p = gameInfo.resultDetails;
16724 gameInfo.resultDetails = NULL;
16726 ClearGameInfo(&gameInfo);
16727 gameInfo.variant = v;
16729 switch (gameMode) {
16730 case MachinePlaysWhite:
16731 gameInfo.event = StrSave( appData.pgnEventHeader );
16732 gameInfo.site = StrSave(HostName());
16733 gameInfo.date = PGNDate();
16734 gameInfo.round = StrSave("-");
16735 gameInfo.white = StrSave(first.tidy);
16736 gameInfo.black = StrSave(UserName());
16737 gameInfo.timeControl = TimeControlTagValue();
16740 case MachinePlaysBlack:
16741 gameInfo.event = StrSave( appData.pgnEventHeader );
16742 gameInfo.site = StrSave(HostName());
16743 gameInfo.date = PGNDate();
16744 gameInfo.round = StrSave("-");
16745 gameInfo.white = StrSave(UserName());
16746 gameInfo.black = StrSave(first.tidy);
16747 gameInfo.timeControl = TimeControlTagValue();
16750 case TwoMachinesPlay:
16751 gameInfo.event = StrSave( appData.pgnEventHeader );
16752 gameInfo.site = StrSave(HostName());
16753 gameInfo.date = PGNDate();
16756 snprintf(buf, MSG_SIZ, "%d", roundNr);
16757 gameInfo.round = StrSave(buf);
16759 gameInfo.round = StrSave("-");
16761 if (first.twoMachinesColor[0] == 'w') {
16762 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16763 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16765 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16766 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16768 gameInfo.timeControl = TimeControlTagValue();
16772 gameInfo.event = StrSave("Edited game");
16773 gameInfo.site = StrSave(HostName());
16774 gameInfo.date = PGNDate();
16775 gameInfo.round = StrSave("-");
16776 gameInfo.white = StrSave("-");
16777 gameInfo.black = StrSave("-");
16778 gameInfo.result = r;
16779 gameInfo.resultDetails = p;
16783 gameInfo.event = StrSave("Edited position");
16784 gameInfo.site = StrSave(HostName());
16785 gameInfo.date = PGNDate();
16786 gameInfo.round = StrSave("-");
16787 gameInfo.white = StrSave("-");
16788 gameInfo.black = StrSave("-");
16791 case IcsPlayingWhite:
16792 case IcsPlayingBlack:
16797 case PlayFromGameFile:
16798 gameInfo.event = StrSave("Game from non-PGN file");
16799 gameInfo.site = StrSave(HostName());
16800 gameInfo.date = PGNDate();
16801 gameInfo.round = StrSave("-");
16802 gameInfo.white = StrSave("?");
16803 gameInfo.black = StrSave("?");
16812 ReplaceComment (int index, char *text)
16818 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16819 pvInfoList[index-1].depth == len &&
16820 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16821 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16822 while (*text == '\n') text++;
16823 len = strlen(text);
16824 while (len > 0 && text[len - 1] == '\n') len--;
16826 if (commentList[index] != NULL)
16827 free(commentList[index]);
16830 commentList[index] = NULL;
16833 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16834 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16835 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16836 commentList[index] = (char *) malloc(len + 2);
16837 strncpy(commentList[index], text, len);
16838 commentList[index][len] = '\n';
16839 commentList[index][len + 1] = NULLCHAR;
16841 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16843 commentList[index] = (char *) malloc(len + 7);
16844 safeStrCpy(commentList[index], "{\n", 3);
16845 safeStrCpy(commentList[index]+2, text, len+1);
16846 commentList[index][len+2] = NULLCHAR;
16847 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16848 strcat(commentList[index], "\n}\n");
16853 CrushCRs (char *text)
16861 if (ch == '\r') continue;
16863 } while (ch != '\0');
16867 AppendComment (int index, char *text, Boolean addBraces)
16868 /* addBraces tells if we should add {} */
16873 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16874 if(addBraces == 3) addBraces = 0; else // force appending literally
16875 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16878 while (*text == '\n') text++;
16879 len = strlen(text);
16880 while (len > 0 && text[len - 1] == '\n') len--;
16881 text[len] = NULLCHAR;
16883 if (len == 0) return;
16885 if (commentList[index] != NULL) {
16886 Boolean addClosingBrace = addBraces;
16887 old = commentList[index];
16888 oldlen = strlen(old);
16889 while(commentList[index][oldlen-1] == '\n')
16890 commentList[index][--oldlen] = NULLCHAR;
16891 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16892 safeStrCpy(commentList[index], old, oldlen + len + 6);
16894 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16895 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16896 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16897 while (*text == '\n') { text++; len--; }
16898 commentList[index][--oldlen] = NULLCHAR;
16900 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16901 else strcat(commentList[index], "\n");
16902 strcat(commentList[index], text);
16903 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16904 else strcat(commentList[index], "\n");
16906 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16908 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16909 else commentList[index][0] = NULLCHAR;
16910 strcat(commentList[index], text);
16911 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16912 if(addBraces == TRUE) strcat(commentList[index], "}\n");
16917 FindStr (char * text, char * sub_text)
16919 char * result = strstr( text, sub_text );
16921 if( result != NULL ) {
16922 result += strlen( sub_text );
16928 /* [AS] Try to extract PV info from PGN comment */
16929 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16931 GetInfoFromComment (int index, char * text)
16933 char * sep = text, *p;
16935 if( text != NULL && index > 0 ) {
16938 int time = -1, sec = 0, deci;
16939 char * s_eval = FindStr( text, "[%eval " );
16940 char * s_emt = FindStr( text, "[%emt " );
16942 if( s_eval != NULL || s_emt != NULL ) {
16944 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16949 if( s_eval != NULL ) {
16950 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16954 if( delim != ']' ) {
16959 if( s_emt != NULL ) {
16964 /* We expect something like: [+|-]nnn.nn/dd */
16967 if(*text != '{') return text; // [HGM] braces: must be normal comment
16969 sep = strchr( text, '/' );
16970 if( sep == NULL || sep < (text+4) ) {
16975 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16976 if(p[1] == '(') { // comment starts with PV
16977 p = strchr(p, ')'); // locate end of PV
16978 if(p == NULL || sep < p+5) return text;
16979 // at this point we have something like "{(.*) +0.23/6 ..."
16980 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16981 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16982 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16984 time = -1; sec = -1; deci = -1;
16985 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16986 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16987 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16988 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16992 if( score_lo < 0 || score_lo >= 100 ) {
16996 if(sec >= 0) time = 600*time + 10*sec; else
16997 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16999 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
17001 /* [HGM] PV time: now locate end of PV info */
17002 while( *++sep >= '0' && *sep <= '9'); // strip depth
17004 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
17006 while( *++sep >= '0' && *sep <= '9'); // strip seconds
17008 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
17009 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
17020 pvInfoList[index-1].depth = depth;
17021 pvInfoList[index-1].score = score;
17022 pvInfoList[index-1].time = 10*time; // centi-sec
17023 if(*sep == '}') *sep = 0; else *--sep = '{';
17025 while(*p++ = *sep++)
17028 } // squeeze out space between PV and comment, and return both
17034 SendToProgram (char *message, ChessProgramState *cps)
17036 int count, outCount, error;
17039 if (cps->pr == NoProc) return;
17042 if (appData.debugMode) {
17045 fprintf(debugFP, "%ld >%-6s: %s",
17046 SubtractTimeMarks(&now, &programStartTime),
17047 cps->which, message);
17049 fprintf(serverFP, "%ld >%-6s: %s",
17050 SubtractTimeMarks(&now, &programStartTime),
17051 cps->which, message), fflush(serverFP);
17054 count = strlen(message);
17055 outCount = OutputToProcess(cps->pr, message, count, &error);
17056 if (outCount < count && !exiting
17057 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
17058 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
17059 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
17060 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17061 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17062 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17063 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17064 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17066 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17067 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17068 gameInfo.result = res;
17070 gameInfo.resultDetails = StrSave(buf);
17072 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17073 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17078 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
17082 ChessProgramState *cps = (ChessProgramState *)closure;
17084 if (isr != cps->isr) return; /* Killed intentionally */
17087 RemoveInputSource(cps->isr);
17088 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
17089 _(cps->which), cps->program);
17090 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
17091 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17092 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17093 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17094 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17095 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17097 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17098 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17099 gameInfo.result = res;
17101 gameInfo.resultDetails = StrSave(buf);
17103 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17104 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
17106 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
17107 _(cps->which), cps->program);
17108 RemoveInputSource(cps->isr);
17110 /* [AS] Program is misbehaving badly... kill it */
17111 if( count == -2 ) {
17112 DestroyChildProcess( cps->pr, 9 );
17116 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17121 if ((end_str = strchr(message, '\r')) != NULL)
17122 *end_str = NULLCHAR;
17123 if ((end_str = strchr(message, '\n')) != NULL)
17124 *end_str = NULLCHAR;
17126 if (appData.debugMode) {
17127 TimeMark now; int print = 1;
17128 char *quote = ""; char c; int i;
17130 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17131 char start = message[0];
17132 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17133 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17134 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
17135 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17136 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17137 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
17138 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17139 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
17140 sscanf(message, "hint: %c", &c)!=1 &&
17141 sscanf(message, "pong %c", &c)!=1 && start != '#') {
17142 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17143 print = (appData.engineComments >= 2);
17145 message[0] = start; // restore original message
17149 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17150 SubtractTimeMarks(&now, &programStartTime), cps->which,
17154 fprintf(serverFP, "%ld <%-6s: %s%s\n",
17155 SubtractTimeMarks(&now, &programStartTime), cps->which,
17157 message), fflush(serverFP);
17161 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17162 if (appData.icsEngineAnalyze) {
17163 if (strstr(message, "whisper") != NULL ||
17164 strstr(message, "kibitz") != NULL ||
17165 strstr(message, "tellics") != NULL) return;
17168 HandleMachineMove(message, cps);
17173 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17178 if( timeControl_2 > 0 ) {
17179 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17180 tc = timeControl_2;
17183 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17184 inc /= cps->timeOdds;
17185 st /= cps->timeOdds;
17187 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17190 /* Set exact time per move, normally using st command */
17191 if (cps->stKludge) {
17192 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17194 if (seconds == 0) {
17195 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17197 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17200 snprintf(buf, MSG_SIZ, "st %d\n", st);
17203 /* Set conventional or incremental time control, using level command */
17204 if (seconds == 0) {
17205 /* Note old gnuchess bug -- minutes:seconds used to not work.
17206 Fixed in later versions, but still avoid :seconds
17207 when seconds is 0. */
17208 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17210 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17211 seconds, inc/1000.);
17214 SendToProgram(buf, cps);
17216 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17217 /* Orthogonally, limit search to given depth */
17219 if (cps->sdKludge) {
17220 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17222 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17224 SendToProgram(buf, cps);
17227 if(cps->nps >= 0) { /* [HGM] nps */
17228 if(cps->supportsNPS == FALSE)
17229 cps->nps = -1; // don't use if engine explicitly says not supported!
17231 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17232 SendToProgram(buf, cps);
17237 ChessProgramState *
17239 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17241 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17242 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17248 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17250 char message[MSG_SIZ];
17253 /* Note: this routine must be called when the clocks are stopped
17254 or when they have *just* been set or switched; otherwise
17255 it will be off by the time since the current tick started.
17257 if (machineWhite) {
17258 time = whiteTimeRemaining / 10;
17259 otime = blackTimeRemaining / 10;
17261 time = blackTimeRemaining / 10;
17262 otime = whiteTimeRemaining / 10;
17264 /* [HGM] translate opponent's time by time-odds factor */
17265 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17267 if (time <= 0) time = 1;
17268 if (otime <= 0) otime = 1;
17270 snprintf(message, MSG_SIZ, "time %ld\n", time);
17271 SendToProgram(message, cps);
17273 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17274 SendToProgram(message, cps);
17278 EngineDefinedVariant (ChessProgramState *cps, int n)
17279 { // return name of n-th unknown variant that engine supports
17280 static char buf[MSG_SIZ];
17281 char *p, *s = cps->variants;
17282 if(!s) return NULL;
17283 do { // parse string from variants feature
17285 p = strchr(s, ',');
17286 if(p) *p = NULLCHAR;
17287 v = StringToVariant(s);
17288 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17289 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17290 if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17291 !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17292 !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17293 !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17294 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17297 if(n < 0) return buf;
17303 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17306 int len = strlen(name);
17309 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17311 sscanf(*p, "%d", &val);
17313 while (**p && **p != ' ')
17315 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17316 SendToProgram(buf, cps);
17323 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17326 int len = strlen(name);
17327 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17329 sscanf(*p, "%d", loc);
17330 while (**p && **p != ' ') (*p)++;
17331 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17332 SendToProgram(buf, cps);
17339 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17342 int len = strlen(name);
17343 if (strncmp((*p), name, len) == 0
17344 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17346 len = strlen(*p) + 1; if(len < MSG_SIZ && !strcmp(name, "option")) len = MSG_SIZ; // make sure string options have enough space to change their value
17347 FREE(*loc); *loc = malloc(len);
17348 strncpy(*loc, *p, len);
17349 sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17350 while (**p && **p != '\"') (*p)++;
17351 if (**p == '\"') (*p)++;
17352 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17353 SendToProgram(buf, cps);
17360 ParseOption (Option *opt, ChessProgramState *cps)
17361 // [HGM] options: process the string that defines an engine option, and determine
17362 // name, type, default value, and allowed value range
17364 char *p, *q, buf[MSG_SIZ];
17365 int n, min = (-1)<<31, max = 1<<31, def;
17367 opt->target = &opt->value; // OK for spin/slider and checkbox
17368 if(p = strstr(opt->name, " -spin ")) {
17369 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17370 if(max < min) max = min; // enforce consistency
17371 if(def < min) def = min;
17372 if(def > max) def = max;
17377 } else if((p = strstr(opt->name, " -slider "))) {
17378 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17379 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17380 if(max < min) max = min; // enforce consistency
17381 if(def < min) def = min;
17382 if(def > max) def = max;
17386 opt->type = Spin; // Slider;
17387 } else if((p = strstr(opt->name, " -string "))) {
17388 opt->textValue = p+9;
17389 opt->type = TextBox;
17390 opt->target = &opt->textValue;
17391 } else if((p = strstr(opt->name, " -file "))) {
17392 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17393 opt->target = opt->textValue = p+7;
17394 opt->type = FileName; // FileName;
17395 opt->target = &opt->textValue;
17396 } else if((p = strstr(opt->name, " -path "))) {
17397 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17398 opt->target = opt->textValue = p+7;
17399 opt->type = PathName; // PathName;
17400 opt->target = &opt->textValue;
17401 } else if(p = strstr(opt->name, " -check ")) {
17402 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17403 opt->value = (def != 0);
17404 opt->type = CheckBox;
17405 } else if(p = strstr(opt->name, " -combo ")) {
17406 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17407 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17408 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17409 opt->value = n = 0;
17410 while(q = StrStr(q, " /// ")) {
17411 n++; *q = 0; // count choices, and null-terminate each of them
17413 if(*q == '*') { // remember default, which is marked with * prefix
17417 cps->comboList[cps->comboCnt++] = q;
17419 cps->comboList[cps->comboCnt++] = NULL;
17421 opt->type = ComboBox;
17422 } else if(p = strstr(opt->name, " -button")) {
17423 opt->type = Button;
17424 } else if(p = strstr(opt->name, " -save")) {
17425 opt->type = SaveButton;
17426 } else return FALSE;
17427 *p = 0; // terminate option name
17428 *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17429 if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17430 // now look if the command-line options define a setting for this engine option.
17431 if(cps->optionSettings && cps->optionSettings[0])
17432 p = strstr(cps->optionSettings, opt->name); else p = NULL;
17433 if(p && (p == cps->optionSettings || p[-1] == ',')) {
17434 snprintf(buf, MSG_SIZ, "option %s", p);
17435 if(p = strstr(buf, ",")) *p = 0;
17436 if(q = strchr(buf, '=')) switch(opt->type) {
17438 for(n=0; n<opt->max; n++)
17439 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17444 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17448 opt->value = atoi(q+1);
17453 SendToProgram(buf, cps);
17459 FeatureDone (ChessProgramState *cps, int val)
17461 DelayedEventCallback cb = GetDelayedEvent();
17462 if ((cb == InitBackEnd3 && cps == &first) ||
17463 (cb == SettingsMenuIfReady && cps == &second) ||
17464 (cb == LoadEngine) || (cb == StartSecond) ||
17465 (cb == TwoMachinesEventIfReady)) {
17466 CancelDelayedEvent();
17467 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17468 } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17469 cps->initDone = val;
17470 if(val) cps->reload = FALSE, RefreshSettingsDialog(cps, val);
17473 /* Parse feature command from engine */
17475 ParseFeatures (char *args, ChessProgramState *cps)
17483 while (*p == ' ') p++;
17484 if (*p == NULLCHAR) return;
17486 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17487 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17488 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17489 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17490 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17491 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17492 if (BoolFeature(&p, "reuse", &val, cps)) {
17493 /* Engine can disable reuse, but can't enable it if user said no */
17494 if (!val) cps->reuse = FALSE;
17497 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17498 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17499 if (gameMode == TwoMachinesPlay) {
17500 DisplayTwoMachinesTitle();
17506 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17507 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17508 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17509 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17510 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17511 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17512 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17513 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17514 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17515 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17516 if (IntFeature(&p, "done", &val, cps)) {
17517 FeatureDone(cps, val);
17520 /* Added by Tord: */
17521 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17522 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17523 /* End of additions by Tord */
17525 /* [HGM] added features: */
17526 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17527 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17528 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17529 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17530 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17531 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17532 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17533 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17534 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17535 if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17536 FREE(cps->option[cps->nrOptions].name);
17537 cps->option[cps->nrOptions].name = q; q = NULL;
17538 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17539 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17540 SendToProgram(buf, cps);
17543 if(cps->nrOptions >= MAX_OPTIONS) {
17545 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17546 DisplayError(buf, 0);
17550 /* End of additions by HGM */
17552 /* unknown feature: complain and skip */
17554 while (*q && *q != '=') q++;
17555 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17556 SendToProgram(buf, cps);
17562 while (*p && *p != '\"') p++;
17563 if (*p == '\"') p++;
17565 while (*p && *p != ' ') p++;
17573 PeriodicUpdatesEvent (int newState)
17575 if (newState == appData.periodicUpdates)
17578 appData.periodicUpdates=newState;
17580 /* Display type changes, so update it now */
17581 // DisplayAnalysis();
17583 /* Get the ball rolling again... */
17585 AnalysisPeriodicEvent(1);
17586 StartAnalysisClock();
17591 PonderNextMoveEvent (int newState)
17593 if (newState == appData.ponderNextMove) return;
17594 if (gameMode == EditPosition) EditPositionDone(TRUE);
17596 SendToProgram("hard\n", &first);
17597 if (gameMode == TwoMachinesPlay) {
17598 SendToProgram("hard\n", &second);
17601 SendToProgram("easy\n", &first);
17602 thinkOutput[0] = NULLCHAR;
17603 if (gameMode == TwoMachinesPlay) {
17604 SendToProgram("easy\n", &second);
17607 appData.ponderNextMove = newState;
17611 NewSettingEvent (int option, int *feature, char *command, int value)
17615 if (gameMode == EditPosition) EditPositionDone(TRUE);
17616 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17617 if(feature == NULL || *feature) SendToProgram(buf, &first);
17618 if (gameMode == TwoMachinesPlay) {
17619 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17624 ShowThinkingEvent ()
17625 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17627 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17628 int newState = appData.showThinking
17629 // [HGM] thinking: other features now need thinking output as well
17630 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17632 if (oldState == newState) return;
17633 oldState = newState;
17634 if (gameMode == EditPosition) EditPositionDone(TRUE);
17636 SendToProgram("post\n", &first);
17637 if (gameMode == TwoMachinesPlay) {
17638 SendToProgram("post\n", &second);
17641 SendToProgram("nopost\n", &first);
17642 thinkOutput[0] = NULLCHAR;
17643 if (gameMode == TwoMachinesPlay) {
17644 SendToProgram("nopost\n", &second);
17647 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17651 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17653 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17654 if (pr == NoProc) return;
17655 AskQuestion(title, question, replyPrefix, pr);
17659 TypeInEvent (char firstChar)
17661 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17662 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17663 gameMode == AnalyzeMode || gameMode == EditGame ||
17664 gameMode == EditPosition || gameMode == IcsExamining ||
17665 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17666 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17667 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17668 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
17669 gameMode == Training) PopUpMoveDialog(firstChar);
17673 TypeInDoneEvent (char *move)
17676 int n, fromX, fromY, toX, toY;
17678 ChessMove moveType;
17681 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17682 EditPositionPasteFEN(move);
17685 // [HGM] movenum: allow move number to be typed in any mode
17686 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17690 // undocumented kludge: allow command-line option to be typed in!
17691 // (potentially fatal, and does not implement the effect of the option.)
17692 // should only be used for options that are values on which future decisions will be made,
17693 // and definitely not on options that would be used during initialization.
17694 if(strstr(move, "!!! -") == move) {
17695 ParseArgsFromString(move+4);
17699 if (gameMode != EditGame && currentMove != forwardMostMove &&
17700 gameMode != Training) {
17701 DisplayMoveError(_("Displayed move is not current"));
17703 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17704 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17705 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17706 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17707 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17708 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17710 DisplayMoveError(_("Could not parse move"));
17716 DisplayMove (int moveNumber)
17718 char message[MSG_SIZ];
17720 char cpThinkOutput[MSG_SIZ];
17722 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17724 if (moveNumber == forwardMostMove - 1 ||
17725 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17727 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17729 if (strchr(cpThinkOutput, '\n')) {
17730 *strchr(cpThinkOutput, '\n') = NULLCHAR;
17733 *cpThinkOutput = NULLCHAR;
17736 /* [AS] Hide thinking from human user */
17737 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17738 *cpThinkOutput = NULLCHAR;
17739 if( thinkOutput[0] != NULLCHAR ) {
17742 for( i=0; i<=hiddenThinkOutputState; i++ ) {
17743 cpThinkOutput[i] = '.';
17745 cpThinkOutput[i] = NULLCHAR;
17746 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17750 if (moveNumber == forwardMostMove - 1 &&
17751 gameInfo.resultDetails != NULL) {
17752 if (gameInfo.resultDetails[0] == NULLCHAR) {
17753 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17755 snprintf(res, MSG_SIZ, " {%s} %s",
17756 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17762 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17763 DisplayMessage(res, cpThinkOutput);
17765 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17766 WhiteOnMove(moveNumber) ? " " : ".. ",
17767 parseList[moveNumber], res);
17768 DisplayMessage(message, cpThinkOutput);
17773 DisplayComment (int moveNumber, char *text)
17775 char title[MSG_SIZ];
17777 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17778 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17780 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17781 WhiteOnMove(moveNumber) ? " " : ".. ",
17782 parseList[moveNumber]);
17784 if (text != NULL && (appData.autoDisplayComment || commentUp))
17785 CommentPopUp(title, text);
17788 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17789 * might be busy thinking or pondering. It can be omitted if your
17790 * gnuchess is configured to stop thinking immediately on any user
17791 * input. However, that gnuchess feature depends on the FIONREAD
17792 * ioctl, which does not work properly on some flavors of Unix.
17795 Attention (ChessProgramState *cps)
17798 if (!cps->useSigint) return;
17799 if (appData.noChessProgram || (cps->pr == NoProc)) return;
17800 switch (gameMode) {
17801 case MachinePlaysWhite:
17802 case MachinePlaysBlack:
17803 case TwoMachinesPlay:
17804 case IcsPlayingWhite:
17805 case IcsPlayingBlack:
17808 /* Skip if we know it isn't thinking */
17809 if (!cps->maybeThinking) return;
17810 if (appData.debugMode)
17811 fprintf(debugFP, "Interrupting %s\n", cps->which);
17812 InterruptChildProcess(cps->pr);
17813 cps->maybeThinking = FALSE;
17818 #endif /*ATTENTION*/
17824 if (whiteTimeRemaining <= 0) {
17827 if (appData.icsActive) {
17828 if (appData.autoCallFlag &&
17829 gameMode == IcsPlayingBlack && !blackFlag) {
17830 SendToICS(ics_prefix);
17831 SendToICS("flag\n");
17835 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17837 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17838 if (appData.autoCallFlag) {
17839 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17846 if (blackTimeRemaining <= 0) {
17849 if (appData.icsActive) {
17850 if (appData.autoCallFlag &&
17851 gameMode == IcsPlayingWhite && !whiteFlag) {
17852 SendToICS(ics_prefix);
17853 SendToICS("flag\n");
17857 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17859 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17860 if (appData.autoCallFlag) {
17861 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17872 CheckTimeControl ()
17874 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17875 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17878 * add time to clocks when time control is achieved ([HGM] now also used for increment)
17880 if ( !WhiteOnMove(forwardMostMove) ) {
17881 /* White made time control */
17882 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17883 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17884 /* [HGM] time odds: correct new time quota for time odds! */
17885 / WhitePlayer()->timeOdds;
17886 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17888 lastBlack -= blackTimeRemaining;
17889 /* Black made time control */
17890 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17891 / WhitePlayer()->other->timeOdds;
17892 lastWhite = whiteTimeRemaining;
17897 DisplayBothClocks ()
17899 int wom = gameMode == EditPosition ?
17900 !blackPlaysFirst : WhiteOnMove(currentMove);
17901 DisplayWhiteClock(whiteTimeRemaining, wom);
17902 DisplayBlackClock(blackTimeRemaining, !wom);
17906 /* Timekeeping seems to be a portability nightmare. I think everyone
17907 has ftime(), but I'm really not sure, so I'm including some ifdefs
17908 to use other calls if you don't. Clocks will be less accurate if
17909 you have neither ftime nor gettimeofday.
17912 /* VS 2008 requires the #include outside of the function */
17913 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17914 #include <sys/timeb.h>
17917 /* Get the current time as a TimeMark */
17919 GetTimeMark (TimeMark *tm)
17921 #if HAVE_GETTIMEOFDAY
17923 struct timeval timeVal;
17924 struct timezone timeZone;
17926 gettimeofday(&timeVal, &timeZone);
17927 tm->sec = (long) timeVal.tv_sec;
17928 tm->ms = (int) (timeVal.tv_usec / 1000L);
17930 #else /*!HAVE_GETTIMEOFDAY*/
17933 // include <sys/timeb.h> / moved to just above start of function
17934 struct timeb timeB;
17937 tm->sec = (long) timeB.time;
17938 tm->ms = (int) timeB.millitm;
17940 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17941 tm->sec = (long) time(NULL);
17947 /* Return the difference in milliseconds between two
17948 time marks. We assume the difference will fit in a long!
17951 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17953 return 1000L*(tm2->sec - tm1->sec) +
17954 (long) (tm2->ms - tm1->ms);
17959 * Code to manage the game clocks.
17961 * In tournament play, black starts the clock and then white makes a move.
17962 * We give the human user a slight advantage if he is playing white---the
17963 * clocks don't run until he makes his first move, so it takes zero time.
17964 * Also, we don't account for network lag, so we could get out of sync
17965 * with GNU Chess's clock -- but then, referees are always right.
17968 static TimeMark tickStartTM;
17969 static long intendedTickLength;
17972 NextTickLength (long timeRemaining)
17974 long nominalTickLength, nextTickLength;
17976 if (timeRemaining > 0L && timeRemaining <= 10000L)
17977 nominalTickLength = 100L;
17979 nominalTickLength = 1000L;
17980 nextTickLength = timeRemaining % nominalTickLength;
17981 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17983 return nextTickLength;
17986 /* Adjust clock one minute up or down */
17988 AdjustClock (Boolean which, int dir)
17990 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17991 if(which) blackTimeRemaining += 60000*dir;
17992 else whiteTimeRemaining += 60000*dir;
17993 DisplayBothClocks();
17994 adjustedClock = TRUE;
17997 /* Stop clocks and reset to a fresh time control */
18001 (void) StopClockTimer();
18002 if (appData.icsActive) {
18003 whiteTimeRemaining = blackTimeRemaining = 0;
18004 } else if (searchTime) {
18005 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18006 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18007 } else { /* [HGM] correct new time quote for time odds */
18008 whiteTC = blackTC = fullTimeControlString;
18009 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
18010 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
18012 if (whiteFlag || blackFlag) {
18014 whiteFlag = blackFlag = FALSE;
18016 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
18017 DisplayBothClocks();
18018 adjustedClock = FALSE;
18021 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
18023 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
18025 /* Decrement running clock by amount of time that has passed */
18030 long lastTickLength, fudge;
18033 if (!appData.clockMode) return;
18034 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
18038 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18040 /* Fudge if we woke up a little too soon */
18041 fudge = intendedTickLength - lastTickLength;
18042 if (fudge < 0 || fudge > FUDGE) fudge = 0;
18044 if (WhiteOnMove(forwardMostMove)) {
18045 if(whiteNPS >= 0) lastTickLength = 0;
18046 tRemaining = whiteTimeRemaining -= lastTickLength;
18047 if( tRemaining < 0 && !appData.icsActive) {
18048 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
18049 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
18050 whiteStartMove = forwardMostMove; whiteTC = nextSession;
18051 lastWhite= tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
18054 if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
18055 DisplayWhiteClock(whiteTimeRemaining - fudge,
18056 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18059 if(blackNPS >= 0) lastTickLength = 0;
18060 tRemaining = blackTimeRemaining -= lastTickLength;
18061 if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
18062 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
18064 blackStartMove = forwardMostMove;
18065 lastBlack = tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
18068 if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
18069 DisplayBlackClock(blackTimeRemaining - fudge,
18070 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18073 if (CheckFlags()) return;
18075 if(twoBoards) { // count down secondary board's clocks as well
18076 activePartnerTime -= lastTickLength;
18078 if(activePartner == 'W')
18079 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
18081 DisplayBlackClock(activePartnerTime, TRUE);
18086 intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
18087 StartClockTimer(intendedTickLength);
18089 /* if the time remaining has fallen below the alarm threshold, sound the
18090 * alarm. if the alarm has sounded and (due to a takeback or time control
18091 * with increment) the time remaining has increased to a level above the
18092 * threshold, reset the alarm so it can sound again.
18095 if (appData.icsActive && appData.icsAlarm) {
18097 /* make sure we are dealing with the user's clock */
18098 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
18099 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
18102 if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
18103 alarmSounded = FALSE;
18104 } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
18106 alarmSounded = TRUE;
18112 /* A player has just moved, so stop the previously running
18113 clock and (if in clock mode) start the other one.
18114 We redisplay both clocks in case we're in ICS mode, because
18115 ICS gives us an update to both clocks after every move.
18116 Note that this routine is called *after* forwardMostMove
18117 is updated, so the last fractional tick must be subtracted
18118 from the color that is *not* on move now.
18121 SwitchClocks (int newMoveNr)
18123 long lastTickLength;
18125 int flagged = FALSE;
18129 if (StopClockTimer() && appData.clockMode) {
18130 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18131 if (!WhiteOnMove(forwardMostMove)) {
18132 if(blackNPS >= 0) lastTickLength = 0;
18133 blackTimeRemaining -= lastTickLength;
18134 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18135 // if(pvInfoList[forwardMostMove].time == -1)
18136 pvInfoList[forwardMostMove].time = // use GUI time
18137 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18139 if(whiteNPS >= 0) lastTickLength = 0;
18140 whiteTimeRemaining -= lastTickLength;
18141 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18142 // if(pvInfoList[forwardMostMove].time == -1)
18143 pvInfoList[forwardMostMove].time =
18144 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18146 flagged = CheckFlags();
18148 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18149 CheckTimeControl();
18151 if (flagged || !appData.clockMode) return;
18153 switch (gameMode) {
18154 case MachinePlaysBlack:
18155 case MachinePlaysWhite:
18156 case BeginningOfGame:
18157 if (pausing) return;
18161 case PlayFromGameFile:
18169 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18170 if(WhiteOnMove(forwardMostMove))
18171 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18172 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18176 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18177 whiteTimeRemaining : blackTimeRemaining);
18178 StartClockTimer(intendedTickLength);
18182 /* Stop both clocks */
18186 long lastTickLength;
18189 if (!StopClockTimer()) return;
18190 if (!appData.clockMode) return;
18194 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18195 if (WhiteOnMove(forwardMostMove)) {
18196 if(whiteNPS >= 0) lastTickLength = 0;
18197 whiteTimeRemaining -= lastTickLength;
18198 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18200 if(blackNPS >= 0) lastTickLength = 0;
18201 blackTimeRemaining -= lastTickLength;
18202 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18207 /* Start clock of player on move. Time may have been reset, so
18208 if clock is already running, stop and restart it. */
18212 (void) StopClockTimer(); /* in case it was running already */
18213 DisplayBothClocks();
18214 if (CheckFlags()) return;
18216 if (!appData.clockMode) return;
18217 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18219 GetTimeMark(&tickStartTM);
18220 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18221 whiteTimeRemaining : blackTimeRemaining);
18223 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18224 whiteNPS = blackNPS = -1;
18225 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18226 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18227 whiteNPS = first.nps;
18228 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18229 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18230 blackNPS = first.nps;
18231 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18232 whiteNPS = second.nps;
18233 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18234 blackNPS = second.nps;
18235 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18237 StartClockTimer(intendedTickLength);
18241 TimeString (long ms)
18243 long second, minute, hour, day;
18245 static char buf[40], moveTime[8];
18247 if (ms > 0 && ms <= 9900) {
18248 /* convert milliseconds to tenths, rounding up */
18249 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18251 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18255 /* convert milliseconds to seconds, rounding up */
18256 /* use floating point to avoid strangeness of integer division
18257 with negative dividends on many machines */
18258 second = (long) floor(((double) (ms + 999L)) / 1000.0);
18265 day = second / (60 * 60 * 24);
18266 second = second % (60 * 60 * 24);
18267 hour = second / (60 * 60);
18268 second = second % (60 * 60);
18269 minute = second / 60;
18270 second = second % 60;
18272 if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18273 else *moveTime = NULLCHAR;
18276 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18277 sign, day, hour, minute, second, moveTime);
18279 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18281 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18288 * This is necessary because some C libraries aren't ANSI C compliant yet.
18291 StrStr (char *string, char *match)
18295 length = strlen(match);
18297 for (i = strlen(string) - length; i >= 0; i--, string++)
18298 if (!strncmp(match, string, length))
18305 StrCaseStr (char *string, char *match)
18309 length = strlen(match);
18311 for (i = strlen(string) - length; i >= 0; i--, string++) {
18312 for (j = 0; j < length; j++) {
18313 if (ToLower(match[j]) != ToLower(string[j]))
18316 if (j == length) return string;
18324 StrCaseCmp (char *s1, char *s2)
18329 c1 = ToLower(*s1++);
18330 c2 = ToLower(*s2++);
18331 if (c1 > c2) return 1;
18332 if (c1 < c2) return -1;
18333 if (c1 == NULLCHAR) return 0;
18341 return isupper(c) ? tolower(c) : c;
18348 return islower(c) ? toupper(c) : c;
18350 #endif /* !_amigados */
18357 if ((ret = (char *) malloc(strlen(s) + 1)))
18359 safeStrCpy(ret, s, strlen(s)+1);
18365 StrSavePtr (char *s, char **savePtr)
18370 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18371 safeStrCpy(*savePtr, s, strlen(s)+1);
18383 clock = time((time_t *)NULL);
18384 tm = localtime(&clock);
18385 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18386 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18387 return StrSave(buf);
18392 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18394 int i, j, fromX, fromY, toX, toY;
18395 int whiteToPlay, haveRights = nrCastlingRights;
18401 whiteToPlay = (gameMode == EditPosition) ?
18402 !blackPlaysFirst : (move % 2 == 0);
18405 /* Piece placement data */
18406 for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18407 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18409 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18410 if (boards[move][i][j] == EmptySquare) {
18412 } else { ChessSquare piece = boards[move][i][j];
18413 if (emptycount > 0) {
18414 if(emptycount<10) /* [HGM] can be >= 10 */
18415 *p++ = '0' + emptycount;
18416 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18419 if(PieceToChar(piece) == '+') {
18420 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18422 piece = (ChessSquare)(CHUDEMOTED(piece));
18424 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18425 if(*p = PieceSuffix(piece)) p++;
18427 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18428 p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18433 if (emptycount > 0) {
18434 if(emptycount<10) /* [HGM] can be >= 10 */
18435 *p++ = '0' + emptycount;
18436 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18443 /* [HGM] print Crazyhouse or Shogi holdings */
18444 if( gameInfo.holdingsWidth ) {
18445 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18447 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18448 piece = boards[move][i][BOARD_WIDTH-1];
18449 if( piece != EmptySquare )
18450 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18451 *p++ = PieceToChar(piece);
18453 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18454 piece = boards[move][handSize-i-1][0];
18455 if( piece != EmptySquare )
18456 for(j=0; j<(int) boards[move][handSize-i-1][1]; j++)
18457 *p++ = PieceToChar(piece);
18460 if( q == p ) *p++ = '-';
18466 *p++ = whiteToPlay ? 'w' : 'b';
18469 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18470 haveRights = 0; q = p;
18471 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18472 piece = boards[move][0][i];
18473 if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18474 if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18477 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18478 piece = boards[move][BOARD_HEIGHT-1][i];
18479 if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18480 if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18483 if(p == q) *p++ = '-';
18487 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18490 if(q != overrideCastling+1) p[-1] = ' '; else --p;
18493 int handW=0, handB=0;
18494 if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18495 for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18496 for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18499 if(appData.fischerCastling) {
18500 if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18501 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18502 if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18504 /* [HGM] write directly from rights */
18505 if(boards[move][CASTLING][2] != NoRights &&
18506 boards[move][CASTLING][0] != NoRights )
18507 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18508 if(boards[move][CASTLING][2] != NoRights &&
18509 boards[move][CASTLING][1] != NoRights )
18510 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18513 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18514 if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18516 if(boards[move][CASTLING][5] != NoRights &&
18517 boards[move][CASTLING][3] != NoRights )
18518 *p++ = boards[move][CASTLING][3] + AAA;
18519 if(boards[move][CASTLING][5] != NoRights &&
18520 boards[move][CASTLING][4] != NoRights )
18521 *p++ = boards[move][CASTLING][4] + AAA;
18525 /* [HGM] write true castling rights */
18526 if( nrCastlingRights == 6 ) {
18528 if(boards[move][CASTLING][0] != NoRights &&
18529 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
18530 q = (boards[move][CASTLING][1] != NoRights &&
18531 boards[move][CASTLING][2] != NoRights );
18532 if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18533 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18534 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18535 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18539 if(boards[move][CASTLING][3] != NoRights &&
18540 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
18541 q = (boards[move][CASTLING][4] != NoRights &&
18542 boards[move][CASTLING][5] != NoRights );
18544 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18545 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18546 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18551 if (q == p) *p++ = '-'; /* No castling rights */
18555 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18556 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18557 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18558 /* En passant target square */
18559 if (move > backwardMostMove) {
18560 fromX = moveList[move - 1][0] - AAA;
18561 fromY = moveList[move - 1][1] - ONE;
18562 toX = moveList[move - 1][2] - AAA;
18563 toY = moveList[move - 1][3] - ONE;
18564 if ((whiteToPlay ? toY < fromY - 1 : toY > fromY + 1) &&
18565 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) ) {
18566 /* 2-square pawn move just happened */
18567 *p++ = (3*toX + 5*fromX + 4)/8 + AAA;
18568 *p++ = (3*toY + 5*fromY + 4)/8 + ONE;
18569 if(gameInfo.variant == VariantBerolina) {
18576 } else if(move == backwardMostMove) {
18577 // [HGM] perhaps we should always do it like this, and forget the above?
18578 if((signed char)boards[move][EP_STATUS] >= 0) {
18579 *p++ = boards[move][EP_STATUS] + AAA;
18580 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18591 i = boards[move][CHECK_COUNT];
18593 sprintf(p, "%d+%d ", i&255, i>>8);
18598 { int i = 0, j=move;
18600 /* [HGM] find reversible plies */
18601 if (appData.debugMode) { int k;
18602 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18603 for(k=backwardMostMove; k<=forwardMostMove; k++)
18604 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18608 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18609 if( j == backwardMostMove ) i += initialRulePlies;
18610 sprintf(p, "%d ", i);
18611 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18613 /* Fullmove number */
18614 sprintf(p, "%d", (move / 2) + 1);
18615 } else *--p = NULLCHAR;
18617 return StrSave(buf);
18621 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18623 int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18625 int emptycount, virgin[BOARD_FILES];
18626 ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18630 for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18632 /* Piece placement data */
18633 for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18636 if (*p == '/' || *p == ' ' || *p == '[' ) {
18638 emptycount = gameInfo.boardWidth - j;
18639 while (emptycount--)
18640 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18641 if (*p == '/') p++;
18642 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18643 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18644 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18646 appData.NrRanks = gameInfo.boardHeight - i; i=0;
18649 #if(BOARD_FILES >= 10)*0
18650 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18651 p++; emptycount=10;
18652 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18653 while (emptycount--)
18654 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18656 } else if (*p == '*') {
18657 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18658 } else if (isdigit(*p)) {
18659 emptycount = *p++ - '0';
18660 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18661 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18662 while (emptycount--)
18663 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18664 } else if (*p == '<') {
18665 if(i == BOARD_HEIGHT-1) shuffle = 1;
18666 else if (i != 0 || !shuffle) return FALSE;
18668 } else if (shuffle && *p == '>') {
18669 p++; // for now ignore closing shuffle range, and assume rank-end
18670 } else if (*p == '?') {
18671 if (j >= gameInfo.boardWidth) return FALSE;
18672 if (i != 0 && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18673 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18674 } else if (*p == '+' || isalpha(*p)) {
18675 char *q, *s = SUFFIXES;
18676 if (j >= gameInfo.boardWidth) return FALSE;
18679 if(q = strchr(s, p[1])) p++;
18680 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18681 if(piece == EmptySquare) return FALSE; /* unknown piece */
18682 piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18683 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18686 if(q = strchr(s, *p)) p++;
18687 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18690 if(piece==EmptySquare) return FALSE; /* unknown piece */
18691 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18692 piece = (ChessSquare) (PROMOTED(piece));
18693 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18696 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18697 if(piece == king) wKingRank = i;
18698 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18704 while (*p == '/' || *p == ' ') p++;
18706 if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18708 /* [HGM] by default clear Crazyhouse holdings, if present */
18709 if(gameInfo.holdingsWidth) {
18710 for(i=0; i<handSize; i++) {
18711 board[i][0] = EmptySquare; /* black holdings */
18712 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18713 board[i][1] = (ChessSquare) 0; /* black counts */
18714 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18718 /* [HGM] look for Crazyhouse holdings here */
18719 while(*p==' ') p++;
18720 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18721 int swap=0, wcnt=0, bcnt=0;
18723 if(*p == '<') swap++, p++;
18724 if(*p == '-' ) p++; /* empty holdings */ else {
18725 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18726 /* if we would allow FEN reading to set board size, we would */
18727 /* have to add holdings and shift the board read so far here */
18728 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18730 if((int) piece >= (int) BlackPawn ) {
18731 i = (int)piece - (int)BlackPawn;
18732 i = PieceToNumber((ChessSquare)i);
18733 if( i >= gameInfo.holdingsSize ) return FALSE;
18734 board[handSize-1-i][0] = piece; /* black holdings */
18735 board[handSize-1-i][1]++; /* black counts */
18738 i = (int)piece - (int)WhitePawn;
18739 i = PieceToNumber((ChessSquare)i);
18740 if( i >= gameInfo.holdingsSize ) return FALSE;
18741 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
18742 board[i][BOARD_WIDTH-2]++; /* black holdings */
18746 if(subst) { // substitute back-rank question marks by holdings pieces
18747 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18748 int k, m, n = bcnt + 1;
18749 if(board[0][j] == ClearBoard) {
18750 if(!wcnt) return FALSE;
18752 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18753 board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18754 if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18758 if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18759 if(!bcnt) return FALSE;
18760 if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18761 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[handSize-1-k][1]) < 0) {
18762 board[BOARD_HEIGHT-1][j] = board[handSize-1-k][0]; bcnt--;
18763 if(--board[handSize-1-k][1] == 0) board[handSize-1-k][0] = EmptySquare;
18774 if(subst) return FALSE; // substitution requested, but no holdings
18776 while(*p == ' ') p++;
18780 if(appData.colorNickNames) {
18781 if( c == appData.colorNickNames[0] ) c = 'w'; else
18782 if( c == appData.colorNickNames[1] ) c = 'b';
18786 *blackPlaysFirst = FALSE;
18789 *blackPlaysFirst = TRUE;
18795 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18796 /* return the extra info in global variiables */
18798 while(*p==' ') p++;
18800 if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18801 if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18802 if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18805 /* set defaults in case FEN is incomplete */
18806 board[EP_STATUS] = EP_UNKNOWN;
18807 board[TOUCHED_W] = board[TOUCHED_B] = 0;
18808 for(i=0; i<nrCastlingRights; i++ ) {
18809 board[CASTLING][i] =
18810 appData.fischerCastling ? NoRights : initialRights[i];
18811 } /* assume possible unless obviously impossible */
18812 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18813 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18814 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18815 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18816 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18817 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18818 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18819 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18822 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18825 while(isalpha(*p)) {
18826 if(isupper(*p)) w |= 1 << (*p++ - 'A');
18827 if(islower(*p)) b |= 1 << (*p++ - 'a');
18831 board[TOUCHED_W] = ~w;
18832 board[TOUCHED_B] = ~b;
18833 while(*p == ' ') p++;
18837 if(nrCastlingRights) {
18839 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18840 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18841 /* castling indicator present, so default becomes no castlings */
18842 for(i=0; i<nrCastlingRights; i++ ) {
18843 board[CASTLING][i] = NoRights;
18846 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18847 (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18848 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18849 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
18850 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18852 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18853 if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18854 if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18856 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18857 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18858 if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18859 && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18860 if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18861 && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18864 for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18865 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18866 board[CASTLING][2] = whiteKingFile;
18867 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18868 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18869 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18872 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18873 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18874 board[CASTLING][2] = whiteKingFile;
18875 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18876 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18877 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18880 for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18881 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18882 board[CASTLING][5] = blackKingFile;
18883 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18884 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18885 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18888 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18889 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18890 board[CASTLING][5] = blackKingFile;
18891 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18892 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18893 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18896 default: /* FRC castlings */
18897 if(c >= 'a') { /* black rights */
18898 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18899 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18900 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18901 if(i == BOARD_RGHT) break;
18902 board[CASTLING][5] = i;
18904 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
18905 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
18907 board[CASTLING][3] = c;
18909 board[CASTLING][4] = c;
18910 } else { /* white rights */
18911 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18912 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18913 if(board[0][i] == WhiteKing) break;
18914 if(i == BOARD_RGHT) break;
18915 board[CASTLING][2] = i;
18916 c -= AAA - 'a' + 'A';
18917 if(board[0][c] >= WhiteKing) break;
18919 board[CASTLING][0] = c;
18921 board[CASTLING][1] = c;
18925 for(i=0; i<nrCastlingRights; i++)
18926 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18927 if(gameInfo.variant == VariantSChess)
18928 for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18929 if(fischer && shuffle) appData.fischerCastling = TRUE;
18930 if (appData.debugMode) {
18931 fprintf(debugFP, "FEN castling rights:");
18932 for(i=0; i<nrCastlingRights; i++)
18933 fprintf(debugFP, " %d", board[CASTLING][i]);
18934 fprintf(debugFP, "\n");
18937 while(*p==' ') p++;
18940 if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18942 /* read e.p. field in games that know e.p. capture */
18943 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18944 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18945 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18947 p++; board[EP_STATUS] = EP_NONE;
18949 int d, r, c = *p - AAA;
18951 if(c >= BOARD_LEFT && c < BOARD_RGHT) {
18953 board[EP_STATUS] = board[EP_FILE] = c; r = 0;
18954 if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18955 d = (r < BOARD_HEIGHT << 1 ? 1 : -1); // assume double-push (P next to e.p. square nearer center)
18956 if(board[r+d][c] == EmptySquare) d *= 2; // but if no Pawn there, triple push
18957 board[LAST_TO] = 256*(r + d) + c;
18959 if(c >= BOARD_LEFT && c < BOARD_RGHT) { // mover explicitly mentioned
18960 if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18961 board[LAST_TO] = 256*r + c;
18962 if(!(board[EP_RANK]-r & 1)) board[EP_RANK] |= 128;
18968 while(*p == ' ') p++;
18970 board[CHECK_COUNT] = 0; // [HGM] 3check: check-count field
18971 if(sscanf(p, "%d+%d", &i, &j) == 2) {
18972 board[CHECK_COUNT] = i + 256*j;
18973 while(*p && *p != ' ') p++;
18976 c = sscanf(p, "%d%*d +%d+%d", &i, &j, &k);
18978 FENrulePlies = i; /* 50-move ply counter */
18979 /* (The move number is still ignored) */
18980 if(c == 3 && !board[CHECK_COUNT]) board[CHECK_COUNT] = (3 - j) + 256*(3 - k); // SCIDB-style check count
18987 EditPositionPasteFEN (char *fen)
18990 Board initial_position;
18992 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18993 DisplayError(_("Bad FEN position in clipboard"), 0);
18996 int savedBlackPlaysFirst = blackPlaysFirst;
18997 EditPositionEvent();
18998 blackPlaysFirst = savedBlackPlaysFirst;
18999 CopyBoard(boards[0], initial_position);
19000 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
19001 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
19002 DisplayBothClocks();
19003 DrawPosition(FALSE, boards[currentMove]);
19008 static char cseq[12] = "\\ ";
19011 set_cont_sequence (char *new_seq)
19016 // handle bad attempts to set the sequence
19018 return 0; // acceptable error - no debug
19020 len = strlen(new_seq);
19021 ret = (len > 0) && (len < sizeof(cseq));
19023 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
19024 else if (appData.debugMode)
19025 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
19030 reformat a source message so words don't cross the width boundary. internal
19031 newlines are not removed. returns the wrapped size (no null character unless
19032 included in source message). If dest is NULL, only calculate the size required
19033 for the dest buffer. lp argument indicats line position upon entry, and it's
19034 passed back upon exit.
19037 wrap (char *dest, char *src, int count, int width, int *lp)
19039 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
19041 cseq_len = strlen(cseq);
19042 old_line = line = *lp;
19043 ansi = len = clen = 0;
19045 for (i=0; i < count; i++)
19047 if (src[i] == '\033')
19050 // if we hit the width, back up
19051 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
19053 // store i & len in case the word is too long
19054 old_i = i, old_len = len;
19056 // find the end of the last word
19057 while (i && src[i] != ' ' && src[i] != '\n')
19063 // word too long? restore i & len before splitting it
19064 if ((old_i-i+clen) >= width)
19071 if (i && src[i-1] == ' ')
19074 if (src[i] != ' ' && src[i] != '\n')
19081 // now append the newline and continuation sequence
19086 strncpy(dest+len, cseq, cseq_len);
19094 dest[len] = src[i];
19098 if (src[i] == '\n')
19103 if (dest && appData.debugMode)
19105 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
19106 count, width, line, len, *lp);
19107 show_bytes(debugFP, src, count);
19108 fprintf(debugFP, "\ndest: ");
19109 show_bytes(debugFP, dest, len);
19110 fprintf(debugFP, "\n");
19112 *lp = dest ? line : old_line;
19117 // [HGM] vari: routines for shelving variations
19118 Boolean modeRestore = FALSE;
19121 PushInner (int firstMove, int lastMove)
19123 int i, j, nrMoves = lastMove - firstMove;
19125 // push current tail of game on stack
19126 savedResult[storedGames] = gameInfo.result;
19127 savedDetails[storedGames] = gameInfo.resultDetails;
19128 gameInfo.resultDetails = NULL;
19129 savedFirst[storedGames] = firstMove;
19130 savedLast [storedGames] = lastMove;
19131 savedFramePtr[storedGames] = framePtr;
19132 framePtr -= nrMoves; // reserve space for the boards
19133 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
19134 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
19135 for(j=0; j<MOVE_LEN; j++)
19136 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
19137 for(j=0; j<2*MOVE_LEN; j++)
19138 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
19139 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
19140 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
19141 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
19142 pvInfoList[firstMove+i-1].depth = 0;
19143 commentList[framePtr+i] = commentList[firstMove+i];
19144 commentList[firstMove+i] = NULL;
19148 forwardMostMove = firstMove; // truncate game so we can start variation
19152 PushTail (int firstMove, int lastMove)
19154 if(appData.icsActive) { // only in local mode
19155 forwardMostMove = currentMove; // mimic old ICS behavior
19158 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19160 PushInner(firstMove, lastMove);
19161 if(storedGames == 1) GreyRevert(FALSE);
19162 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19166 PopInner (Boolean annotate)
19169 char buf[8000], moveBuf[20];
19171 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19172 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19173 nrMoves = savedLast[storedGames] - currentMove;
19176 if(!WhiteOnMove(currentMove))
19177 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19178 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19179 for(i=currentMove; i<forwardMostMove; i++) {
19181 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19182 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19183 strcat(buf, moveBuf);
19184 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19185 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19189 for(i=1; i<=nrMoves; i++) { // copy last variation back
19190 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19191 for(j=0; j<MOVE_LEN; j++)
19192 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19193 for(j=0; j<2*MOVE_LEN; j++)
19194 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19195 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19196 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19197 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19198 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19199 commentList[currentMove+i] = commentList[framePtr+i];
19200 commentList[framePtr+i] = NULL;
19202 if(annotate) AppendComment(currentMove+1, buf, FALSE);
19203 framePtr = savedFramePtr[storedGames];
19204 gameInfo.result = savedResult[storedGames];
19205 if(gameInfo.resultDetails != NULL) {
19206 free(gameInfo.resultDetails);
19208 gameInfo.resultDetails = savedDetails[storedGames];
19209 forwardMostMove = currentMove + nrMoves;
19213 PopTail (Boolean annotate)
19215 if(appData.icsActive) return FALSE; // only in local mode
19216 if(!storedGames) return FALSE; // sanity
19217 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19219 PopInner(annotate);
19220 if(currentMove < forwardMostMove) ForwardEvent(); else
19221 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19223 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19229 { // remove all shelved variations
19231 for(i=0; i<storedGames; i++) {
19232 if(savedDetails[i])
19233 free(savedDetails[i]);
19234 savedDetails[i] = NULL;
19236 for(i=framePtr; i<MAX_MOVES; i++) {
19237 if(commentList[i]) free(commentList[i]);
19238 commentList[i] = NULL;
19240 framePtr = MAX_MOVES-1;
19245 LoadVariation (int index, char *text)
19246 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19247 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19248 int level = 0, move;
19250 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19251 // first find outermost bracketing variation
19252 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19253 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19254 if(*p == '{') wait = '}'; else
19255 if(*p == '[') wait = ']'; else
19256 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19257 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19259 if(*p == wait) wait = NULLCHAR; // closing ]} found
19262 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19263 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19264 end[1] = NULLCHAR; // clip off comment beyond variation
19265 ToNrEvent(currentMove-1);
19266 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19267 // kludge: use ParsePV() to append variation to game
19268 move = currentMove;
19269 ParsePV(start, TRUE, TRUE);
19270 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19271 ClearPremoveHighlights();
19273 ToNrEvent(currentMove+1);
19276 int transparency[2];
19281 #define BUF_SIZ (2*MSG_SIZ)
19282 char *p, *q, buf[BUF_SIZ];
19283 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19284 snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19285 ParseArgsFromString(buf);
19286 ActivateTheme(TRUE); // also redo colors
19290 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19293 q = appData.themeNames;
19294 snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19295 if(appData.useBitmaps) {
19296 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19297 Shorten(appData.liteBackTextureFile));
19298 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19299 Shorten(appData.darkBackTextureFile),
19300 appData.liteBackTextureMode,
19301 appData.darkBackTextureMode );
19303 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19305 if(!appData.useBitmaps || transparency[0]) {
19306 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19308 if(!appData.useBitmaps || transparency[1]) {
19309 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19311 if(appData.useBorder) {
19312 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19315 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19317 if(appData.useFont) {
19318 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19319 appData.renderPiecesWithFont,
19320 appData.fontToPieceTable,
19321 Col2Text(9), // appData.fontBackColorWhite
19322 Col2Text(10) ); // appData.fontForeColorBlack
19324 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19325 if(appData.pieceDirectory[0]) {
19326 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19327 if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19328 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19330 if(!appData.pieceDirectory[0] || !appData.trueColors)
19331 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19332 Col2Text(0), // whitePieceColor
19333 Col2Text(1) ); // blackPieceColor
19335 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19336 Col2Text(4), // highlightSquareColor
19337 Col2Text(5) ); // premoveHighlightColor
19338 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19339 if(insert != q) insert[-1] = NULLCHAR;
19340 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19343 ActivateTheme(FALSE);